diff --git a/apps/toolbox/src/pages/forms.ts b/apps/toolbox/src/pages/forms.ts index cbf927466..dc4ea36a8 100644 --- a/apps/toolbox/src/pages/forms.ts +++ b/apps/toolbox/src/pages/forms.ts @@ -1,4 +1,4 @@ -import { Page, Observable, EventData } from '@nativescript/core'; +import { Page, Observable, EventData, TextField, PropertyChangeData } from '@nativescript/core'; let page: Page; @@ -9,8 +9,71 @@ export function navigatingTo(args: EventData) { export class SampleData extends Observable { textInput = ''; - textChange(args) { - console.log(args.object.text); - this.notifyPropertyChange('textInput', args.object.text); + formattedSSNInput = ''; + formattedPhoneInput = ''; + valueFormatterSSN = formatSSN; + valueFormatterPhone = formatPhoneNumber; + + textChange(args: PropertyChangeData) { + console.log(args.value); + this.notifyPropertyChange('textInput', args.value); + } + + textChangeSSN(args: PropertyChangeData) { + console.log(args.value); + this.notifyPropertyChange('formattedSSNInput', args.value); + } + + textChangePhone(args: PropertyChangeData) { + console.log(args.value); + this.notifyPropertyChange('formattedPhoneInput', args.value); } } + +function formatPhoneNumber(value: string, useParens?: boolean) { + value = value.replace(/\D/g, ''); + var size = value.length; + if (useParens) { + if (size > 0) { + value = '(' + value; + } + if (size > 3) { + value = value.slice(0, 4) + ') ' + value.slice(4, 11); + } + if (size > 6) { + value = value.slice(0, 9) + '-' + value.slice(9); + } + } else { + if (size > 3) { + value = value.slice(0, 3) + '-' + value.slice(3, 10); + } + if (size > 6) { + value = value.slice(0, 7) + '-' + value.slice(7); + } + } + return value; +} + +function formatSSN(value: string) { + // if input value is falsy eg if the user deletes the input, then just return + if (!value) return value; + + // clean the input for any non-digit values + const ssn = value.replace(/[^\d]/g, ''); + + // ssnLength is used to know when to apply our formatting for the ssn + const ssnLength = ssn.length; + + // we need to return the value with no formatting if its less then four digits + if (ssnLength < 4) return ssn; + + // if ssnLength is greater than 4 and less the 6 we start to return + // the formatted number + if (ssnLength < 6) { + return `${ssn.slice(0, 3)}-${ssn.slice(3)}`; + } + + // finally, if the ssnLength is greater then 6, we add the last + // bit of formatting and return it. + return `${ssn.slice(0, 3)}-${ssn.slice(3, 5)}-${ssn.slice(5, 9)}`; +} diff --git a/apps/toolbox/src/pages/forms.xml b/apps/toolbox/src/pages/forms.xml index dddddfedd..6b55050d9 100644 --- a/apps/toolbox/src/pages/forms.xml +++ b/apps/toolbox/src/pages/forms.xml @@ -1,15 +1,25 @@ - - - - - - - + diff --git a/packages/core/ui/editable-text-base/editable-text-base-common.ts b/packages/core/ui/editable-text-base/editable-text-base-common.ts index a4713f5f8..03c8d324c 100644 --- a/packages/core/ui/editable-text-base/editable-text-base-common.ts +++ b/packages/core/ui/editable-text-base/editable-text-base-common.ts @@ -21,6 +21,7 @@ export abstract class EditableTextBase extends TextBase implements EditableTextB public autocorrect: boolean; public hint: string; public maxLength: number; + public valueFormatter: (value: string) => string; public abstract dismissSoftInput(); public abstract _setInputType(inputType: number): void; diff --git a/packages/core/ui/editable-text-base/index.android.ts b/packages/core/ui/editable-text-base/index.android.ts index 48d5af0d0..9c02d90ad 100644 --- a/packages/core/ui/editable-text-base/index.android.ts +++ b/packages/core/ui/editable-text-base/index.android.ts @@ -493,6 +493,10 @@ export abstract class EditableTextBase extends EditableTextBaseCommon { public onTextChanged(text: string, start: number, before: number, count: number): void { // called by android.text.TextWatcher + if (this.valueFormatter) { + this.text = this.valueFormatter(text.toString()); + this.android.setSelection((this.text || '').length); + } // const owner = this.owner; // let selectionStart = owner.android.getSelectionStart(); // owner.android.removeTextChangedListener(owner._editTextListeners); diff --git a/packages/core/ui/editable-text-base/index.d.ts b/packages/core/ui/editable-text-base/index.d.ts index 6e17dd449..4c00bf0f9 100644 --- a/packages/core/ui/editable-text-base/index.d.ts +++ b/packages/core/ui/editable-text-base/index.d.ts @@ -36,7 +36,7 @@ export class EditableTextBase extends TextBase { /** * Gets or sets the autofill type. */ - autofillType: CoreTypes.AutofillType; + autofillType: CoreTypes.AutofillType; /** * Gets or sets whether the instance is editable. @@ -58,6 +58,12 @@ export class EditableTextBase extends TextBase { */ maxLength: number; + /** + * Format input values + * Note: useful for input masking/formatting + */ + valueFormatter: (value: string) => string; + /** * Hides the soft input method, ususally a soft keyboard. */ diff --git a/packages/core/ui/text-field/index.ios.ts b/packages/core/ui/text-field/index.ios.ts index 57ec45248..a6866dbaf 100644 --- a/packages/core/ui/text-field/index.ios.ts +++ b/packages/core/ui/text-field/index.ios.ts @@ -190,16 +190,31 @@ export class TextField extends TextFieldBase { } if (this.updateTextTrigger === 'textChanged') { - // 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); + if (this.valueFormatter) { + // format/replace + let currentValue = textField.text; + let nativeValueChange = `${textField.text}${replacementString}`; + if (replacementString === '') { + // clearing when empty + nativeValueChange = currentValue.slice(0, delta); + } + + const formattedValue = this.valueFormatter(nativeValueChange); + textField.text = formattedValue; + textProperty.nativeValueChange(this, formattedValue); + return false; } else { - if (range.location <= textField.text.length) { - const newText = NSString.stringWithString(textField.text).stringByReplacingCharactersInRangeWithString(range, replacementString); - textProperty.nativeValueChange(this, newText); + // 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 { + if (range.location <= textField.text.length) { + const newText = NSString.stringWithString(textField.text).stringByReplacingCharactersInRangeWithString(range, replacementString); + textProperty.nativeValueChange(this, newText); + } } } }