feat(text): valueFormatter for easy and flexible input auto-formatting (#10264)

https://github.com/NativeScript/NativeScript/issues/10249
This commit is contained in:
Nathan Walker
2023-04-12 08:45:21 -07:00
committed by GitHub
parent f54966707d
commit b3abc5f5ae
6 changed files with 125 additions and 26 deletions

View File

@@ -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)}`;
}

View File

@@ -10,6 +10,16 @@
<Label text="{{ textInput }}" marginTop="6" />
<Label text="SSN Formatted input:" fontWeight="bold" marginTop="12" />
<TextField hint="XXX-XX-XXXX" style.placeholderColor="silver" textChange="{{textChangeSSN}}" keyboardType="number" color="black" width="80%" borderColor="silver" borderWidth="1" height="40" textAlignment="center" borderRadius="4" valueFormatter="{{valueFormatterSSN}}" padding="5" maxLength="11">
</TextField>
<Label text="{{ formattedSSNInput }}" marginTop="6" />
<Label text="Phone Formatted input:" fontWeight="bold" marginTop="12" />
<TextField hint="XXX-XXX-XXXX" style.placeholderColor="silver" textChange="{{textChangePhone}}" keyboardType="number" color="black" width="80%" borderColor="silver" borderWidth="1" height="40" textAlignment="center" borderRadius="4" valueFormatter="{{valueFormatterPhone}}" padding="5" maxLength="12">
</TextField>
<Label text="{{ formattedPhoneInput }}" marginTop="6" />
</StackLayout>
</ScrollView>
</Page>

View File

@@ -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;

View File

@@ -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);

View File

@@ -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.
*/

View File

@@ -190,6 +190,20 @@ export class TextField extends TextFieldBase {
}
if (this.updateTextTrigger === 'textChanged') {
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 {
// 1. secureTextEntry with firstEdit should not replace
// 2. emoji's should not replace value
// 3. convenient keyboard shortcuts should not replace value (eg, '.com')
@@ -203,6 +217,7 @@ export class TextField extends TextFieldBase {
}
}
}
}
if (this.formattedText) {
_updateCharactersInRangeReplacementString(this.formattedText, range.location, range.length, replacementString);