Nailed phone number input
This commit is contained in:
parent
585a4243f7
commit
07c85bb218
3 changed files with 52 additions and 19 deletions
|
@ -11,14 +11,14 @@ export class TextField extends InputElement<string> {
|
||||||
private readonly _isArea: boolean;
|
private readonly _isArea: boolean;
|
||||||
private readonly _textAreaRows: number;
|
private readonly _textAreaRows: number;
|
||||||
|
|
||||||
private readonly _isValid: (string) => boolean;
|
private readonly _isValid: (string, country) => boolean;
|
||||||
|
|
||||||
constructor(options?: {
|
constructor(options?: {
|
||||||
placeholder?: string | UIElement,
|
placeholder?: string | UIElement,
|
||||||
value?: UIEventSource<string>,
|
value?: UIEventSource<string>,
|
||||||
textArea?: boolean,
|
textArea?: boolean,
|
||||||
textAreaRows?: number,
|
textAreaRows?: number,
|
||||||
isValid?: ((s: string) => boolean)
|
isValid?: ((s: string, country?: string) => boolean)
|
||||||
}) {
|
}) {
|
||||||
super(undefined);
|
super(undefined);
|
||||||
const self = this;
|
const self = this;
|
||||||
|
@ -28,7 +28,7 @@ export class TextField extends InputElement<string> {
|
||||||
this.value = options?.value ?? new UIEventSource<string>(undefined);
|
this.value = options?.value ?? new UIEventSource<string>(undefined);
|
||||||
|
|
||||||
this._textAreaRows = options.textAreaRows;
|
this._textAreaRows = options.textAreaRows;
|
||||||
this._isValid = options.isValid ?? ((str) => true);
|
this._isValid = options.isValid ?? ((str, country) => true);
|
||||||
|
|
||||||
this._placeholder = Translations.W(options.placeholder ?? "");
|
this._placeholder = Translations.W(options.placeholder ?? "");
|
||||||
this.ListenTo(this._placeholder._source);
|
this.ListenTo(this._placeholder._source);
|
||||||
|
@ -37,6 +37,7 @@ export class TextField extends InputElement<string> {
|
||||||
self.IsSelected.setData(true)
|
self.IsSelected.setData(true)
|
||||||
});
|
});
|
||||||
this.value.addCallback((t) => {
|
this.value.addCallback((t) => {
|
||||||
|
console.log("Setting actual value to", t);
|
||||||
const field = document.getElementById("txt-"+this.id);
|
const field = document.getElementById("txt-"+this.id);
|
||||||
if (field === undefined || field === null) {
|
if (field === undefined || field === null) {
|
||||||
return;
|
return;
|
||||||
|
@ -74,10 +75,12 @@ export class TextField extends InputElement<string> {
|
||||||
const field = document.getElementById("txt-" + this.id);
|
const field = document.getElementById("txt-" + this.id);
|
||||||
const self = this;
|
const self = this;
|
||||||
field.oninput = () => {
|
field.oninput = () => {
|
||||||
|
|
||||||
|
// How much characters are on the right, not including spaces?
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const endDistance = field.value.length - field.selectionEnd;
|
const endDistance = field.value.substring(field.selectionEnd).replace(/ /g,'').length;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const val: string = field.value;
|
let val: string = field.value;
|
||||||
if (!self.IsValid(val)) {
|
if (!self.IsValid(val)) {
|
||||||
self.value.setData(undefined);
|
self.value.setData(undefined);
|
||||||
} else {
|
} else {
|
||||||
|
@ -86,8 +89,21 @@ export class TextField extends InputElement<string> {
|
||||||
// Setting the value might cause the value to be set again. We keep the distance _to the end_ stable, as phone number formatting might cause the start to change
|
// Setting the value might cause the value to be set again. We keep the distance _to the end_ stable, as phone number formatting might cause the start to change
|
||||||
// See https://github.com/pietervdvn/MapComplete/issues/103
|
// See https://github.com/pietervdvn/MapComplete/issues/103
|
||||||
// We reread the field value - it might have changed!
|
// We reread the field value - it might have changed!
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
self.SetCursorPosition(field.value.length - endDistance);
|
val = field.value;
|
||||||
|
let newCursorPos = endDistance;
|
||||||
|
while(newCursorPos >= 0 &&
|
||||||
|
// We count the number of _actual_ characters (non-space characters) on the right of the new value
|
||||||
|
// This count should become bigger then the end distance
|
||||||
|
val.substr(newCursorPos).replace(/ /g, '').length <= endDistance
|
||||||
|
){
|
||||||
|
newCursorPos --;
|
||||||
|
}
|
||||||
|
newCursorPos++;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
self.SetCursorPosition(newCursorPos);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.value.data !== undefined && this.value.data !== null) {
|
if (this.value.data !== undefined && this.value.data !== null) {
|
||||||
|
@ -127,7 +143,7 @@ export class TextField extends InputElement<string> {
|
||||||
if (t === undefined || t === null) {
|
if (t === undefined || t === null) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return this._isValid(t);
|
return this._isValid(t, undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,9 +140,7 @@ export default class ValidatedTextField {
|
||||||
}
|
}
|
||||||
return parsePhoneNumberFromString(str, country?.toUpperCase())?.isValid() ?? false
|
return parsePhoneNumberFromString(str, country?.toUpperCase())?.isValid() ?? false
|
||||||
},
|
},
|
||||||
(str, country: any) => {
|
(str, country: any) => parsePhoneNumberFromString(str, country?.toUpperCase()).formatInternational()
|
||||||
return parsePhoneNumberFromString(str, country?.toUpperCase()).formatInternational()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -170,21 +168,38 @@ export default class ValidatedTextField {
|
||||||
value?: UIEventSource<string>,
|
value?: UIEventSource<string>,
|
||||||
textArea?: boolean,
|
textArea?: boolean,
|
||||||
textAreaRows?: number,
|
textAreaRows?: number,
|
||||||
isValid?: ((s: string) => boolean)
|
isValid?: ((s: string, country: string) => boolean),
|
||||||
|
country?: string
|
||||||
}): InputElement<string> {
|
}): InputElement<string> {
|
||||||
options = options ?? {};
|
options = options ?? {};
|
||||||
options.placeholder = options.placeholder ?? type;
|
options.placeholder = options.placeholder ?? type;
|
||||||
const tp: TextFieldDef = ValidatedTextField.AllTypes[type]
|
const tp: TextFieldDef = ValidatedTextField.AllTypes[type]
|
||||||
let isValid = tp.isValid;
|
const isValidTp = tp.isValid;
|
||||||
|
let isValid;
|
||||||
if (options.isValid) {
|
if (options.isValid) {
|
||||||
const optValid = options.isValid;
|
const optValid = options.isValid;
|
||||||
isValid = (str, country) => {
|
isValid = (str, country) => {
|
||||||
return ValidatedTextField.AllTypes[type](str, country) && optValid(str);
|
if(str === undefined){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return isValidTp(str, country ?? options.country) && optValid(str, country ?? options.country);
|
||||||
}
|
}
|
||||||
|
}else{
|
||||||
|
isValid = isValidTp;
|
||||||
}
|
}
|
||||||
options.isValid = isValid;
|
options.isValid = isValid;
|
||||||
|
|
||||||
let input: InputElement<string> = new TextField(options);
|
let input: InputElement<string> = new TextField(options);
|
||||||
|
if (tp.reformat) {
|
||||||
|
input.GetValue().addCallbackAndRun(str => {
|
||||||
|
if (!options.isValid(str, options.country)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const formatted = tp.reformat(str, options.country);
|
||||||
|
input.GetValue().setData(formatted);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (tp.inputHelper) {
|
if (tp.inputHelper) {
|
||||||
input = new CombinedInputElement(input, tp.inputHelper(input.GetValue()));
|
input = new CombinedInputElement(input, tp.inputHelper(input.GetValue()));
|
||||||
}
|
}
|
||||||
|
@ -252,11 +267,12 @@ export default class ValidatedTextField {
|
||||||
startValidated?: boolean,
|
startValidated?: boolean,
|
||||||
textArea?: boolean,
|
textArea?: boolean,
|
||||||
textAreaRows?: number,
|
textAreaRows?: number,
|
||||||
isValid?: ((string: string) => boolean)
|
isValid?: ((string: string) => boolean),
|
||||||
|
country?: string
|
||||||
}): InputElement<T> {
|
}): InputElement<T> {
|
||||||
let textField: InputElement<string>;
|
let textField: InputElement<string>;
|
||||||
if (options.type) {
|
if (options.type) {
|
||||||
textField = ValidatedTextField.InputForType(options.type);
|
textField = ValidatedTextField.InputForType(options.type, options);
|
||||||
} else {
|
} else {
|
||||||
textField = new TextField(options);
|
textField = new TextField(options);
|
||||||
}
|
}
|
||||||
|
|
|
@ -326,15 +326,13 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let formatter = ValidatedTextField.AllTypes[type].reformat ?? ((str) => str);
|
|
||||||
|
|
||||||
const pickString =
|
const pickString =
|
||||||
(string: any) => {
|
(string: any) => {
|
||||||
if (string === "" || string === undefined) {
|
if (string === "" || string === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tag = new Tag(freeform.key, formatter(string, this._source.data._country));
|
const tag = new Tag(freeform.key, string);
|
||||||
|
|
||||||
if (freeform.extraTags === undefined) {
|
if (freeform.extraTags === undefined) {
|
||||||
return tag;
|
return tag;
|
||||||
|
@ -361,11 +359,14 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("Creating a freeform input element for ", this._source.data._country);
|
||||||
|
|
||||||
return ValidatedTextField.Mapped(pickString, toString, {
|
return ValidatedTextField.Mapped(pickString, toString, {
|
||||||
placeholder: this._freeform.placeholder,
|
placeholder: this._freeform.placeholder,
|
||||||
type: type,
|
type: type,
|
||||||
isValid: (str) => (str.length <= 255),
|
isValid: (str) => (str.length <= 255),
|
||||||
textArea: isTextArea
|
textArea: isTextArea,
|
||||||
|
country: this._source.data._country
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue