Skip to content

Commit

Permalink
fix(number-field): update IME change detection (#4672)
Browse files Browse the repository at this point in the history
  • Loading branch information
rubencarvalho authored Aug 19, 2024
1 parent a47f99a commit de05aee
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 13 deletions.
49 changes: 36 additions & 13 deletions packages/number-field/src/NumberField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,17 @@ export const remapMultiByteCharacters: Record<string, string> = {
'%': '%',
'+': '+',
: '-',
: '1',
: '2',
: '3',
: '4',
: '5',
: '6',
: '7',
: '8',
: '9',
: '0',
};

const chevronIcon: Record<string, (dir: 'Down' | 'Up') => TemplateResult> = {
s: (dir) => html`
<sp-icon-chevron50
Expand Down Expand Up @@ -175,20 +184,25 @@ export class NumberField extends TextfieldBase {
private _trackingValue = '';
private lastCommitedValue?: number;

private setValue(value: number = this.value): void {
this.value = value;
private setValue(newValue: number = this.value): void {
// Capture previous value for accurate IME change detection
const previousValue = this.lastCommitedValue;

this.value = newValue;

if (
typeof this.lastCommitedValue === 'undefined' ||
this.lastCommitedValue === this.value
typeof previousValue === 'undefined' ||
previousValue === this.value
) {
// Do not announce when the value is unchanged.
return;
}

this.lastCommitedValue = this.value;

this.dispatchEvent(
new Event('change', { bubbles: true, composed: true })
);
this.lastCommitedValue = this.value;
}

/**
Expand All @@ -214,7 +228,13 @@ export class NumberField extends TextfieldBase {
private valueBeforeFocus: string = '';
private isIntentDecimal: boolean = false;

private convertValueToNumber(value: string): number {
private convertValueToNumber(inputValue: string): number {
// Normalize full-width characters to their ASCII equivalents
let normalizedValue = inputValue
.split('')
.map((char) => remapMultiByteCharacters[char] || char)
.join('');

const separators = this.valueBeforeFocus
.split('')
.filter((char) => this.decimalsChars.has(char));
Expand All @@ -223,7 +243,7 @@ export class NumberField extends TextfieldBase {
if (
isIPhone() &&
this.inputElement.inputMode === 'decimal' &&
value !== this.valueBeforeFocus
normalizedValue !== this.valueBeforeFocus
) {
const parts = this.numberFormatter.formatToParts(1000.1);

Expand All @@ -234,12 +254,15 @@ export class NumberField extends TextfieldBase {
for (const separator of uniqueSeparators) {
const isDecimalSeparator = separator === replacementDecimal;
if (!isDecimalSeparator && !this.isIntentDecimal) {
value = value.replace(new RegExp(separator, 'g'), '');
normalizedValue = normalizedValue.replace(
new RegExp(separator, 'g'),
''
);
}
}

let hasReplacedDecimal = false;
const valueChars = value.split('');
const valueChars = normalizedValue.split('');
for (let index = valueChars.length - 1; index >= 0; index--) {
const char = valueChars[index];
if (this.decimalsChars.has(char)) {
Expand All @@ -249,11 +272,10 @@ export class NumberField extends TextfieldBase {
} else valueChars[index] = '';
}
}
value = valueChars.join('');
normalizedValue = valueChars.join('');
}
return this.numberParser.parse(value);
return this.numberParser.parse(normalizedValue);
}

private get _step(): number {
if (typeof this.step !== 'undefined') {
return this.step;
Expand Down Expand Up @@ -492,6 +514,7 @@ export class NumberField extends TextfieldBase {
.split('')
.map((char) => remapMultiByteCharacters[char] || char)
.join('');

if (this.numberParser.isValidPartialNumber(value)) {
// Use starting value as this.value is the `input` value.
this.lastCommitedValue = this.lastCommitedValue ?? this.value;
Expand Down
12 changes: 12 additions & 0 deletions packages/number-field/test/number-field.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,18 @@ describe('NumberField', () => {
el.value = 52;
expect(changeSpy.callCount).to.equal(0);
});
it('handles IME input correctly and dispatches change event', async () => {
el.focus();
el.dispatchEvent(new CompositionEvent('compositionstart'));
// input multibyte characters
await sendKeys({ type: '123' });
await elementUpdated(el);
el.dispatchEvent(new CompositionEvent('compositionend'));
await elementUpdated(el);
await sendKeys({ press: 'Enter' });
expect(el.value).to.equal(50123);
expect(changeSpy.callCount).to.equal(1);
});
it('via scroll', async () => {
el.focus();
await elementUpdated(el);
Expand Down

0 comments on commit de05aee

Please sign in to comment.