mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
feat(text): valueFormatter for easy and flexible input auto-formatting (#10264)
https://github.com/NativeScript/NativeScript/issues/10249
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { Page, Observable, EventData } from '@nativescript/core';
|
import { Page, Observable, EventData, TextField, PropertyChangeData } from '@nativescript/core';
|
||||||
|
|
||||||
let page: Page;
|
let page: Page;
|
||||||
|
|
||||||
@@ -9,8 +9,71 @@ export function navigatingTo(args: EventData) {
|
|||||||
|
|
||||||
export class SampleData extends Observable {
|
export class SampleData extends Observable {
|
||||||
textInput = '';
|
textInput = '';
|
||||||
textChange(args) {
|
formattedSSNInput = '';
|
||||||
console.log(args.object.text);
|
formattedPhoneInput = '';
|
||||||
this.notifyPropertyChange('textInput', args.object.text);
|
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)}`;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,15 +1,25 @@
|
|||||||
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page">
|
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page">
|
||||||
<Page.actionBar>
|
<Page.actionBar>
|
||||||
<ActionBar title="Labels and TextView" class="action-bar">
|
<ActionBar title="Labels and TextView" class="action-bar">
|
||||||
</ActionBar>
|
</ActionBar>
|
||||||
</Page.actionBar>
|
</Page.actionBar>
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
<StackLayout padding="20">
|
<StackLayout padding="20">
|
||||||
<Label text="TextField:" fontWeight="bold" />
|
<Label text="TextField:" fontWeight="bold" />
|
||||||
<TextField textChange="{{ textChange }}" marginTop="6" backgroundColor="#efefef" padding="8" fontSize="18" keyboardType="url" />
|
<TextField textChange="{{ textChange }}" marginTop="6" backgroundColor="#efefef" padding="8" fontSize="18" keyboardType="url" />
|
||||||
|
|
||||||
<Label text="{{ textInput }}" marginTop="6" />
|
<Label text="{{ textInput }}" marginTop="6" />
|
||||||
|
|
||||||
</StackLayout>
|
<Label text="SSN Formatted input:" fontWeight="bold" marginTop="12" />
|
||||||
</ScrollView>
|
<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>
|
</Page>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export abstract class EditableTextBase extends TextBase implements EditableTextB
|
|||||||
public autocorrect: boolean;
|
public autocorrect: boolean;
|
||||||
public hint: string;
|
public hint: string;
|
||||||
public maxLength: number;
|
public maxLength: number;
|
||||||
|
public valueFormatter: (value: string) => string;
|
||||||
|
|
||||||
public abstract dismissSoftInput();
|
public abstract dismissSoftInput();
|
||||||
public abstract _setInputType(inputType: number): void;
|
public abstract _setInputType(inputType: number): void;
|
||||||
|
|||||||
@@ -493,6 +493,10 @@ export abstract class EditableTextBase extends EditableTextBaseCommon {
|
|||||||
|
|
||||||
public onTextChanged(text: string, start: number, before: number, count: number): void {
|
public onTextChanged(text: string, start: number, before: number, count: number): void {
|
||||||
// called by android.text.TextWatcher
|
// called by android.text.TextWatcher
|
||||||
|
if (this.valueFormatter) {
|
||||||
|
this.text = this.valueFormatter(text.toString());
|
||||||
|
this.android.setSelection((this.text || '').length);
|
||||||
|
}
|
||||||
// const owner = this.owner;
|
// const owner = this.owner;
|
||||||
// let selectionStart = owner.android.getSelectionStart();
|
// let selectionStart = owner.android.getSelectionStart();
|
||||||
// owner.android.removeTextChangedListener(owner._editTextListeners);
|
// owner.android.removeTextChangedListener(owner._editTextListeners);
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export class EditableTextBase extends TextBase {
|
|||||||
/**
|
/**
|
||||||
* Gets or sets the autofill type.
|
* Gets or sets the autofill type.
|
||||||
*/
|
*/
|
||||||
autofillType: CoreTypes.AutofillType;
|
autofillType: CoreTypes.AutofillType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets or sets whether the instance is editable.
|
* Gets or sets whether the instance is editable.
|
||||||
@@ -58,6 +58,12 @@ export class EditableTextBase extends TextBase {
|
|||||||
*/
|
*/
|
||||||
maxLength: number;
|
maxLength: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format input values
|
||||||
|
* Note: useful for input masking/formatting
|
||||||
|
*/
|
||||||
|
valueFormatter: (value: string) => string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hides the soft input method, ususally a soft keyboard.
|
* Hides the soft input method, ususally a soft keyboard.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -190,16 +190,31 @@ export class TextField extends TextFieldBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.updateTextTrigger === 'textChanged') {
|
if (this.updateTextTrigger === 'textChanged') {
|
||||||
// 1. secureTextEntry with firstEdit should not replace
|
if (this.valueFormatter) {
|
||||||
// 2. emoji's should not replace value
|
// format/replace
|
||||||
// 3. convenient keyboard shortcuts should not replace value (eg, '.com')
|
let currentValue = textField.text;
|
||||||
const shouldReplaceString = (textField.secureTextEntry && this.firstEdit) || (delta > 1 && !isEmoji(replacementString) && delta !== replacementString.length);
|
let nativeValueChange = `${textField.text}${replacementString}`;
|
||||||
if (shouldReplaceString) {
|
if (replacementString === '') {
|
||||||
textProperty.nativeValueChange(this, replacementString);
|
// clearing when empty
|
||||||
|
nativeValueChange = currentValue.slice(0, delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
const formattedValue = this.valueFormatter(nativeValueChange);
|
||||||
|
textField.text = formattedValue;
|
||||||
|
textProperty.nativeValueChange(this, formattedValue);
|
||||||
|
return false;
|
||||||
} else {
|
} else {
|
||||||
if (range.location <= textField.text.length) {
|
// 1. secureTextEntry with firstEdit should not replace
|
||||||
const newText = NSString.stringWithString(textField.text).stringByReplacingCharactersInRangeWithString(range, replacementString);
|
// 2. emoji's should not replace value
|
||||||
textProperty.nativeValueChange(this, newText);
|
// 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user