diff --git a/apps/toolbox/src/main-page.xml b/apps/toolbox/src/main-page.xml
index 28f53ca08..d4d7766e8 100644
--- a/apps/toolbox/src/main-page.xml
+++ b/apps/toolbox/src/main-page.xml
@@ -10,6 +10,7 @@
+
diff --git a/apps/toolbox/src/pages/forms.ts b/apps/toolbox/src/pages/forms.ts
new file mode 100644
index 000000000..cbf927466
--- /dev/null
+++ b/apps/toolbox/src/pages/forms.ts
@@ -0,0 +1,16 @@
+import { Page, Observable, EventData } from '@nativescript/core';
+
+let page: Page;
+
+export function navigatingTo(args: EventData) {
+ page = args.object;
+ page.bindingContext = new SampleData();
+}
+
+export class SampleData extends Observable {
+ textInput = '';
+ textChange(args) {
+ console.log(args.object.text);
+ this.notifyPropertyChange('textInput', args.object.text);
+ }
+}
diff --git a/apps/toolbox/src/pages/forms.xml b/apps/toolbox/src/pages/forms.xml
new file mode 100644
index 000000000..dddddfedd
--- /dev/null
+++ b/apps/toolbox/src/pages/forms.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/package.json b/package.json
index 1b005e3fa..af0a5d984 100644
--- a/package.json
+++ b/package.json
@@ -44,6 +44,7 @@
"css": "^3.0.0",
"css-tree": "^1.1.2",
"dotenv": "10.0.0",
+ "emoji-regex": "^10.2.1",
"eslint": "7.22.0",
"eslint-config-prettier": "^8.1.0",
"gonzales": "^1.0.7",
diff --git a/packages/core/index.d.ts b/packages/core/index.d.ts
index 94f9f6f35..74ffa7fba 100644
--- a/packages/core/index.d.ts
+++ b/packages/core/index.d.ts
@@ -105,7 +105,7 @@ export type { InstrumentationMode, TimerInfo } from './profiling';
export { encoding } from './text';
export * from './trace';
export * from './ui';
-import { GC, isFontIconURI, isDataURI, isFileOrResourcePath, executeOnMainThread, mainThreadify, isMainThread, dispatchToMainThread, executeOnUIThread, releaseNativeObject, getModuleName, openFile, openUrl, isRealDevice, layout, ad as androidUtils, iOSNativeHelper as iosUtils, Source, escapeRegexSymbols, convertString, dismissSoftInput, dismissKeyboard, queueMacrotask, queueGC, throttle, debounce, dataSerialize, dataDeserialize, copyToClipboard, getFileExtension } from './utils';
+import { GC, isFontIconURI, isDataURI, isFileOrResourcePath, executeOnMainThread, mainThreadify, isMainThread, dispatchToMainThread, executeOnUIThread, releaseNativeObject, getModuleName, openFile, openUrl, isRealDevice, layout, ad as androidUtils, iOSNativeHelper as iosUtils, Source, escapeRegexSymbols, convertString, dismissSoftInput, dismissKeyboard, queueMacrotask, queueGC, throttle, debounce, dataSerialize, dataDeserialize, copyToClipboard, getFileExtension, isEmoji } from './utils';
import { SDK_VERSION } from './utils/constants';
import { ClassInfo, getClass, getBaseClasses, getClassInfo, isBoolean, isDefined, isFunction, isNullOrUndefined, isNumber, isObject, isString, isUndefined, toUIString, verifyCallback, numberHasDecimals, numberIs64Bit } from './utils/types';
export declare const Utils: {
@@ -163,5 +163,6 @@ export declare const Utils: {
dismissSoftInput: typeof dismissSoftInput;
dismissKeyboard: typeof dismissKeyboard;
copyToClipboard: typeof copyToClipboard;
+ isEmoji: typeof isEmoji;
};
export { XmlParser, ParserEventType, ParserEvent } from './xml';
diff --git a/packages/core/index.ts b/packages/core/index.ts
index 8ffe4069a..3f8f78582 100644
--- a/packages/core/index.ts
+++ b/packages/core/index.ts
@@ -137,7 +137,7 @@ export * from './trace';
export * from './ui';
-import { GC, isFontIconURI, isDataURI, isFileOrResourcePath, executeOnMainThread, mainThreadify, isMainThread, dispatchToMainThread, executeOnUIThread, queueMacrotask, queueGC, debounce, throttle, releaseNativeObject, getModuleName, openFile, openUrl, isRealDevice, layout, ad as androidUtils, iOSNativeHelper as iosUtils, Source, RESOURCE_PREFIX, FILE_PREFIX, escapeRegexSymbols, convertString, dismissSoftInput, dismissKeyboard, dataDeserialize, dataSerialize, copyToClipboard, getFileExtension } from './utils';
+import { GC, isFontIconURI, isDataURI, isFileOrResourcePath, executeOnMainThread, mainThreadify, isMainThread, dispatchToMainThread, executeOnUIThread, queueMacrotask, queueGC, debounce, throttle, releaseNativeObject, getModuleName, openFile, openUrl, isRealDevice, layout, ad as androidUtils, iOSNativeHelper as iosUtils, Source, RESOURCE_PREFIX, FILE_PREFIX, escapeRegexSymbols, convertString, dismissSoftInput, dismissKeyboard, dataDeserialize, dataSerialize, copyToClipboard, getFileExtension, isEmoji } from './utils';
import { SDK_VERSION } from './utils/constants';
import { ClassInfo, getClass, getBaseClasses, getClassInfo, isBoolean, isDefined, isFunction, isNullOrUndefined, isNumber, isObject, isString, isUndefined, toUIString, verifyCallback, numberHasDecimals, numberIs64Bit } from './utils/types';
@@ -199,6 +199,7 @@ export const Utils = {
dismissSoftInput,
dismissKeyboard,
copyToClipboard,
+ isEmoji,
};
export { XmlParser, ParserEventType, ParserEvent } from './xml';
diff --git a/packages/core/package.json b/packages/core/package.json
index a41280745..f22c07f02 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -47,6 +47,7 @@
"@nativescript/hook": "~2.0.0",
"acorn": "^8.7.0",
"css-tree": "^1.1.2",
+ "emoji-regex": "^10.2.1",
"reduce-css-calc": "^2.1.7",
"tslib": "^2.0.0"
},
diff --git a/packages/core/ui/text-field/index.ios.ts b/packages/core/ui/text-field/index.ios.ts
index f55b3204c..5d08897b2 100644
--- a/packages/core/ui/text-field/index.ios.ts
+++ b/packages/core/ui/text-field/index.ios.ts
@@ -4,7 +4,7 @@ import { hintProperty, placeholderColorProperty, _updateCharactersInRangeReplace
import { CoreTypes } from '../../core-types';
import { Color } from '../../color';
import { colorProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty, paddingLeftProperty } from '../styling/style-properties';
-import { layout } from '../../utils';
+import { layout, isEmoji } from '../../utils';
import { profile } from '../../profiling';
export * from './text-field-common';
@@ -200,7 +200,10 @@ export class TextField extends TextFieldBase {
}
if (this.updateTextTrigger === 'textChanged') {
- const shouldReplaceString = (textField.secureTextEntry && this.firstEdit) || delta > 1;
+ // 1. secureTextEntry with firstEdit should not replace
+ // 2. emoji's should not replace value
+ // 3. convenient keyboard shortcuts should not replace value (eg, '.com')
+ const shouldReplaceString = (textField.secureTextEntry && this.firstEdit) || (delta > 1 && !isEmoji(replacementString) && delta !== replacementString.length);
if (shouldReplaceString) {
textProperty.nativeValueChange(this, replacementString);
} else {
diff --git a/packages/core/utils/common.ts b/packages/core/utils/common.ts
index a4cbc1028..17c3ad4d8 100644
--- a/packages/core/utils/common.ts
+++ b/packages/core/utils/common.ts
@@ -1,6 +1,7 @@
import * as types from './types';
import { dispatchToMainThread, dispatchToUIThread, isMainThread } from './mainthread-helper';
import { sanitizeModuleName } from '../ui/builder/module-name-sanitizer';
+import emojiRegex from 'emoji-regex';
import { GC } from './index';
@@ -203,3 +204,9 @@ export function queueGC(delay = 900, useThrottle?: boolean) {
debouncedGC.get(delay)();
}
}
+
+export function isEmoji(value: string): boolean {
+ // TODO: In a future runtime update, we can switch to using Unicode Property Escapes:
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Unicode_Property_Escapes
+ return emojiRegex().test(value);
+}