From 6319da80a21511735d9e9518125dbc2a24364f88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=AEzg=C4=83=20Ionu=C8=9B=20Alexandru?= <67201262+mizgaionutalexandru@users.noreply.github.com> Date: Wed, 19 Jun 2024 06:40:25 +0300 Subject: [PATCH] fix(number-field): multiple separators use-cases in decimal inputs in iOS devices (#4571) * fix(number-field): fixed multiple separators usecase in decimal inputs * test(number-field): decimal interpretation iOS test --- packages/number-field/src/NumberField.ts | 49 ++++++++++++++++--- .../number-field/test/number-field.test.ts | 34 +++++++++++++ 2 files changed, 75 insertions(+), 8 deletions(-) diff --git a/packages/number-field/src/NumberField.ts b/packages/number-field/src/NumberField.ts index fcf2e8d6b3..9a2dcc5a90 100644 --- a/packages/number-field/src/NumberField.ts +++ b/packages/number-field/src/NumberField.ts @@ -210,18 +210,46 @@ export class NumberField extends TextfieldBase { ); } + private decimalsChars = new Set(['.', ',']); + private valueBeforeFocus: string = ''; + private isIntentDecimal: boolean = false; + private convertValueToNumber(value: string): number { - if (isIPhone() && this.inputElement.inputMode === 'decimal') { + const separators = this.valueBeforeFocus + .split('') + .filter((char) => this.decimalsChars.has(char)); + const uniqueSeparators = new Set(separators); + + if ( + isIPhone() && + this.inputElement.inputMode === 'decimal' && + value !== this.valueBeforeFocus + ) { const parts = this.numberFormatter.formatToParts(1000.1); - const sourceDecimal = value - .split('') - .find((char) => char === ',' || char === '.'); + const replacementDecimal = parts.find( (part) => part.type === 'decimal' - )?.value; - if (sourceDecimal && replacementDecimal) { - value = value.replace(sourceDecimal, replacementDecimal); + )!.value; + + for (const separator of uniqueSeparators) { + const isDecimalSeparator = separator === replacementDecimal; + if (!isDecimalSeparator && !this.isIntentDecimal) { + value = value.replace(new RegExp(separator, 'g'), ''); + } + } + + let hasReplacedDecimal = false; + const valueChars = value.split(''); + for (let index = valueChars.length - 1; index >= 0; index--) { + const char = valueChars[index]; + if (this.decimalsChars.has(char)) { + if (!hasReplacedDecimal) { + valueChars[index] = replacementDecimal; + hasReplacedDecimal = true; + } else valueChars[index] = ''; + } } + value = valueChars.join(''); } return this.numberParser.parse(value); } @@ -379,12 +407,14 @@ export class NumberField extends TextfieldBase { this._trackingValue = this.inputValue; this.keyboardFocused = !this.readonly && true; this.addEventListener('wheel', this.onScroll, { passive: false }); + this.valueBeforeFocus = this.inputElement.value; } protected override onBlur(_event: FocusEvent): void { super.onBlur(_event); this.keyboardFocused = !this.readonly && false; this.removeEventListener('wheel', this.onScroll); + this.isIntentDecimal = false; } private handleFocusin(): void { @@ -441,7 +471,7 @@ export class NumberField extends TextfieldBase { }); } - protected override handleInput(event: Event): void { + protected override handleInput(event: InputEvent): void { if (this.isComposing) { event.stopPropagation(); return; @@ -454,6 +484,9 @@ export class NumberField extends TextfieldBase { '' ); } + if (event.data && this.decimalsChars.has(event.data)) + this.isIntentDecimal = true; + const { value: originalValue, selectionStart } = this.inputElement; const value = originalValue .split('') diff --git a/packages/number-field/test/number-field.test.ts b/packages/number-field/test/number-field.test.ts index 42f625d332..8d68100259 100644 --- a/packages/number-field/test/number-field.test.ts +++ b/packages/number-field/test/number-field.test.ts @@ -117,6 +117,40 @@ describe('NumberField', () => { expect(el.focusElement.value).to.equal('13 377 331'); }); }); + xit('correctly interprets decimal point on iPhone', async () => { + // setUserAgent is not currently supported by Playwright + await setUserAgent( + 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1' + ); + const el = await getElFrom(decimals({ value: 1234 })); + expect(el.formattedValue).to.equal('1,234'); + + el.focus(); + await sendKeys({ press: 'Backspace' }); + el.blur(); + expect(el.formattedValue).to.equal('123'); + + el.focus(); + await sendKeys({ type: '45' }); + el.blur(); + expect(el.formattedValue).to.equal('12,345'); + + el.focus(); + await sendKeys({ type: ',6' }); + el.blur(); + expect(el.formattedValue).to.equal('12,345.6'); + + el.focus(); + await sendKeys({ type: ',7' }); + el.blur(); + expect(el.formattedValue).to.equal('123,456.7'); + + el.focus(); + await sendKeys({ press: 'Backspace' }); + await sendKeys({ press: 'Backspace' }); + el.blur(); + expect(el.formattedValue).to.equal('123,456'); + }); describe('Step', () => { it('can be 0', async () => { const el = await getElFrom(