diff --git a/apps/app/ui-tests-app/css/main-page.ts b/apps/app/ui-tests-app/css/main-page.ts
index c892c71ea0..6c2e063580 100644
--- a/apps/app/ui-tests-app/css/main-page.ts
+++ b/apps/app/ui-tests-app/css/main-page.ts
@@ -45,5 +45,6 @@ export function loadExamples() {
examples.set("background-shorthand", "css/background-shorthand");
examples.set("background-image-linear-gradient", "css/background-image-linear-gradient");
examples.set("background-image", "css/background-image");
+ examples.set("tappable-span", "css/tappable-span");
return examples;
}
\ No newline at end of file
diff --git a/apps/app/ui-tests-app/css/tappable-span.ts b/apps/app/ui-tests-app/css/tappable-span.ts
new file mode 100644
index 0000000000..bb77c8b605
--- /dev/null
+++ b/apps/app/ui-tests-app/css/tappable-span.ts
@@ -0,0 +1,9 @@
+import { EventData, TextBase } from "tns-core-modules/ui/text-base";
+
+export function foxTap(args: EventData) {
+ console.log("foxTap");
+}
+
+export function dogTap(args: EventData) {
+ console.log("dogTap");
+}
diff --git a/apps/app/ui-tests-app/css/tappable-span.xml b/apps/app/ui-tests-app/css/tappable-span.xml
new file mode 100644
index 0000000000..0730daf9ad
--- /dev/null
+++ b/apps/app/ui-tests-app/css/tappable-span.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/tns-core-modules/text/span.d.ts b/tns-core-modules/text/span.d.ts
index 7cc9258ee1..9b2855e6ff 100644
--- a/tns-core-modules/text/span.d.ts
+++ b/tns-core-modules/text/span.d.ts
@@ -50,6 +50,16 @@ export class Span extends ViewBase {
* Gets or sets the text for the span.
*/
public text: string;
+
+ /**
+ * String value used when hooking to linkTap event.
+ */
+ public static linkTapEvent: string;
+
+ /**
+ * Gets if the span is tappable or not.
+ */
+ public readonly tappable: boolean;
//@private
/**
diff --git a/tns-core-modules/text/span.ts b/tns-core-modules/text/span.ts
index 55620609d6..b184683c68 100644
--- a/tns-core-modules/text/span.ts
+++ b/tns-core-modules/text/span.ts
@@ -2,10 +2,12 @@
import { Span as SpanDefinition } from "./span";
import { ViewBase } from "../ui/core/view";
import { FontStyle, FontWeight, } from "../ui/styling/font";
-import { TextDecoration } from "../ui/text-base";
+import { TextDecoration, EventData } from "../ui/text-base";
export class Span extends ViewBase implements SpanDefinition {
+ static linkTapEvent = "linkTap";
private _text: string;
+ private _tappable: boolean = false;
get fontFamily(): string {
return this.style.fontFamily;
@@ -68,7 +70,28 @@ export class Span extends ViewBase implements SpanDefinition {
}
}
+ get tappable(): boolean {
+ return this._tappable;
+ }
+
+ addEventListener(arg: string, callback: (data: EventData) => void, thisArg?: any) {
+ super.addEventListener(arg, callback, thisArg);
+ this._setTappable(this.hasListeners(Span.linkTapEvent));
+ }
+
+ removeEventListener(arg: string, callback?: any, thisArg?: any) {
+ super.removeEventListener(arg, callback, thisArg);
+ this._setTappable(this.hasListeners(Span.linkTapEvent));
+ }
+
_setTextInternal(value: string): void {
this._text = value;
}
+
+ private _setTappable(value: boolean): void {
+ if (this._tappable !== value) {
+ this._tappable = value;
+ this.notifyPropertyChange("tappable", value);
+ }
+ }
}
diff --git a/tns-core-modules/ui/text-base/text-base.android.ts b/tns-core-modules/ui/text-base/text-base.android.ts
index 9ae4eb3679..28afb3a0d8 100644
--- a/tns-core-modules/ui/text-base/text-base.android.ts
+++ b/tns-core-modules/ui/text-base/text-base.android.ts
@@ -50,6 +50,41 @@ function initializeTextTransformation(): void {
TextTransformation = TextTransformationImpl;
}
+interface ClickableSpan {
+ new (owner: Span): android.text.style.ClickableSpan;
+}
+
+let ClickableSpan: ClickableSpan;
+
+function initializeClickableSpan(): void {
+ if (ClickableSpan) {
+ return;
+ }
+
+ class ClickableSpanImpl extends android.text.style.ClickableSpan {
+ owner: WeakRef;
+
+ constructor(owner: Span) {
+ super();
+ this.owner = new WeakRef(owner);
+ return global.__native(this);
+ }
+ onClick(view: android.view.View): void {
+ const owner = this.owner.get();
+ if (owner) {
+ owner._emit(Span.linkTapEvent);
+ }
+ view.clearFocus();
+ view.invalidate();
+ }
+ updateDrawState(tp: android.text.TextPaint): void {
+ // don't style as link
+ }
+ }
+
+ ClickableSpan = ClickableSpanImpl;
+}
+
export class TextBase extends TextBaseCommon {
nativeViewProtected: android.widget.TextView;
nativeTextViewProtected: android.widget.TextView;
@@ -59,12 +94,15 @@ export class TextBase extends TextBaseCommon {
private _maxHeight: number;
private _minLines: number;
private _maxLines: number;
+ private _tappable: boolean = false;
+ private _defaultMovementMethod: android.text.method.MovementMethod;
public initNativeView(): void {
super.initNativeView();
initializeTextTransformation();
const nativeView = this.nativeTextViewProtected;
this._defaultTransformationMethod = nativeView.getTransformationMethod();
+ this._defaultMovementMethod = this.nativeView.getMovementMethod();
this._minHeight = nativeView.getMinHeight();
this._maxHeight = nativeView.getMaxHeight();
this._minLines = nativeView.getMinLines();
@@ -110,6 +148,7 @@ export class TextBase extends TextBaseCommon {
if (!reset && this.formattedText) {
return;
}
+ this._setTappableState(false);
this._setNativeText(reset);
}
@@ -130,6 +169,7 @@ export class TextBase extends TextBaseCommon {
const spannableStringBuilder = createSpannableStringBuilder(value);
nativeView.setText(spannableStringBuilder);
+ this._setTappableState(isStringTappable(value));
textProperty.nativeValueChange(this, (value === null || value === undefined) ? "" : value.toString());
@@ -312,6 +352,19 @@ export class TextBase extends TextBaseCommon {
this.nativeTextViewProtected.setText(transformedText);
}
+
+ _setTappableState(tappable: boolean) {
+ if (this._tappable !== tappable) {
+ this._tappable = tappable;
+ if (this._tappable) {
+ this.nativeViewProtected.setMovementMethod(android.text.method.LinkMovementMethod.getInstance());
+ this.nativeViewProtected.setHighlightColor(null);
+ }
+ else {
+ this.nativeViewProtected.setMovementMethod(this._defaultMovementMethod);
+ }
+ }
+ }
}
function getCapitalizedString(str: string): string {
@@ -343,6 +396,19 @@ export function getTransformedText(text: string, textTransform: TextTransform):
}
}
+function isStringTappable(formattedString: FormattedString) {
+ if (!formattedString) {
+ return false;
+ }
+ for (let i = 0, length = formattedString.spans.length; i < length; i++) {
+ const span = formattedString.spans.getItem(i);
+ if (span.tappable) {
+ return true;
+ }
+ }
+ return false;
+}
+
function createSpannableStringBuilder(formattedString: FormattedString): android.text.SpannableStringBuilder {
if (!formattedString || !formattedString.parent) {
return null;
@@ -441,6 +507,12 @@ function setSpanModifiers(ssb: android.text.SpannableStringBuilder, span: Span,
}
}
+ const tappable = span.tappable;
+ if (tappable) {
+ initializeClickableSpan();
+ ssb.setSpan(new ClickableSpan(span), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+
// TODO: Implement letterSpacing for Span here.
// const letterSpacing = formattedString.parent.style.letterSpacing;
// if (letterSpacing > 0) {