fix(ui): [input-amount] make sure that previous locale is not used for parsing on user-edit with <= 2 decimals
This commit is contained in:
parent
b9833795b1
commit
29b729ed84
5 changed files with 77 additions and 16 deletions
5
.changeset/odd-feet-teach.md
Normal file
5
.changeset/odd-feet-teach.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@lion/ui': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
[input-amount] make sure that previous locale is not used for parsing on user-edit with <= 2 decimals
|
||||||
|
|
@ -131,7 +131,7 @@ describe('<lion-input-amount>', () => {
|
||||||
expect(_inputNode.value).to.equal('100.12');
|
expect(_inputNode.value).to.equal('100.12');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adjusts formats with locale when formatOptions.mode is "user-edited"', async () => {
|
it('formats with locale when formatOptions.mode is "user-edited" and value has three decimal places', async () => {
|
||||||
const el = /** @type {LionInputAmount} */ (
|
const el = /** @type {LionInputAmount} */ (
|
||||||
await fixture(
|
await fixture(
|
||||||
html`<lion-input-amount
|
html`<lion-input-amount
|
||||||
|
|
@ -154,6 +154,14 @@ describe('<lion-input-amount>', () => {
|
||||||
expect(el.modelValue).to.equal(123456);
|
expect(el.modelValue).to.equal(123456);
|
||||||
expect(el.formattedValue).to.equal('123.456,00');
|
expect(el.formattedValue).to.equal('123.456,00');
|
||||||
|
|
||||||
|
// When editing an already existing value, we interpet the separators as they are.
|
||||||
|
// However, only when the decimal places are 3 or more.
|
||||||
|
mimicUserInput(el, '123.456.00');
|
||||||
|
expect(parserSpy.lastCall.args[1]?.mode).to.equal('user-edited');
|
||||||
|
expect(formatterSpy.lastCall.args[1]?.mode).to.equal('user-edited');
|
||||||
|
expect(el.modelValue).to.equal(123456);
|
||||||
|
expect(el.formattedValue).to.equal('123.456,00');
|
||||||
|
|
||||||
// Formatting should only affect values that should be formatted / parsed as a consequence of user input.
|
// Formatting should only affect values that should be formatted / parsed as a consequence of user input.
|
||||||
// When a user finished editing, the default should be restored.
|
// When a user finished editing, the default should be restored.
|
||||||
// (think of a programmatically set modelValue, that should behave idempotent, regardless of when it is set)
|
// (think of a programmatically set modelValue, that should behave idempotent, regardless of when it is set)
|
||||||
|
|
@ -162,6 +170,45 @@ describe('<lion-input-amount>', () => {
|
||||||
expect(formatterSpy.lastCall.args[1]?.mode).to.equal('auto');
|
expect(formatterSpy.lastCall.args[1]?.mode).to.equal('auto');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('formats with heuristic when formatOptions.mode is "user-edited" and value has two decimal places', async () => {
|
||||||
|
const el = /** @type {LionInputAmount} */ (
|
||||||
|
await fixture(
|
||||||
|
html`<lion-input-amount
|
||||||
|
.modelValue=${64}
|
||||||
|
currency="EUR"
|
||||||
|
.formatOptions="${{ locale: 'en-GB' }}"
|
||||||
|
></lion-input-amount>`,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const parserSpy = sinon.spy(el, 'parser');
|
||||||
|
const formatterSpy = sinon.spy(el, 'formatter');
|
||||||
|
|
||||||
|
expect(el.formattedValue).to.equal('64.00');
|
||||||
|
// @ts-expect-error [allow-protected] in test
|
||||||
|
expect(el._inputNode.value).to.equal('64.00');
|
||||||
|
|
||||||
|
// When editing an already existing value, we interpret the separators based on decimal places when there's 1 or
|
||||||
|
// less separator in total (otherwise we would accidentally multiply or divide by 1000)
|
||||||
|
mimicUserInput(el, '64,00');
|
||||||
|
expect(parserSpy.lastCall.args[1]?.mode).to.equal('user-edited');
|
||||||
|
expect(formatterSpy.lastCall.args[1]?.mode).to.equal('user-edited');
|
||||||
|
expect(el.modelValue).to.equal(64);
|
||||||
|
expect(el.formattedValue).to.equal('64.00');
|
||||||
|
|
||||||
|
mimicUserInput(el, '64,0');
|
||||||
|
expect(parserSpy.lastCall.args[1]?.mode).to.equal('user-edited');
|
||||||
|
expect(formatterSpy.lastCall.args[1]?.mode).to.equal('user-edited');
|
||||||
|
expect(el.modelValue).to.equal(64);
|
||||||
|
expect(el.formattedValue).to.equal('64.00');
|
||||||
|
|
||||||
|
// Formatting should only affect values that should be formatted / parsed as a consequence of user input.
|
||||||
|
// When a user finished editing, the default should be restored.
|
||||||
|
// (think of a programmatically set modelValue, that should behave idempotent, regardless of when it is set)
|
||||||
|
el.modelValue = 1234;
|
||||||
|
expect(el.formattedValue).to.equal('1,234.00');
|
||||||
|
expect(formatterSpy.lastCall.args[1]?.mode).to.equal('auto');
|
||||||
|
});
|
||||||
|
|
||||||
it('sets inputmode attribute to decimal', async () => {
|
it('sets inputmode attribute to decimal', async () => {
|
||||||
const el = /** @type {LionInputAmount} */ (
|
const el = /** @type {LionInputAmount} */ (
|
||||||
await fixture(`<lion-input-amount></lion-input-amount>`)
|
await fixture(`<lion-input-amount></lion-input-amount>`)
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,6 @@ describe('parseAmount()', async () => {
|
||||||
expect(parseAmount('123.456,78', { mode: 'auto' })).to.equal(123456.78);
|
expect(parseAmount('123.456,78', { mode: 'auto' })).to.equal(123456.78);
|
||||||
expect(
|
expect(
|
||||||
parseAmount('123.456,78', { mode: 'user-edited', viewValueStates: ['formatted'] }),
|
parseAmount('123.456,78', { mode: 'user-edited', viewValueStates: ['formatted'] }),
|
||||||
).to.equal(123.45678);
|
).to.equal(123456.78);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -20,25 +20,34 @@ import { getDecimalSeparator } from './getDecimalSeparator.js';
|
||||||
* @return {string} unparseable|withLocale|heuristic
|
* @return {string} unparseable|withLocale|heuristic
|
||||||
*/
|
*/
|
||||||
function getParseMode(value, { mode = 'auto', viewValueStates } = {}) {
|
function getParseMode(value, { mode = 'auto', viewValueStates } = {}) {
|
||||||
const separators = value.match(/[., ]/g);
|
const separatorsRe = /[., ]/g;
|
||||||
|
const separators = value.match(separatorsRe) || [];
|
||||||
// When a user edits an existing value, we already formatted it with a certain locale.
|
// @ts-expect-error [wait-for-platform-types]
|
||||||
// For best UX, we stick with this locale
|
const decimalPlaces = (value.split(separatorsRe) || []).at(-1).length;
|
||||||
const shouldAlignWithExistingSeparators =
|
const isCurViewFormattedAndUserIsEditing =
|
||||||
viewValueStates?.includes('formatted') && mode === 'user-edited';
|
viewValueStates?.includes('formatted') && mode === 'user-edited';
|
||||||
|
|
||||||
if (!separators || shouldAlignWithExistingSeparators) {
|
// When a user edits an existing value, we already formatted it with a certain locale.
|
||||||
|
// For best UX, we stick with this locale. However, we only do this when the user has
|
||||||
|
// entered at least 3 decimal places.
|
||||||
|
const shouldUseCurLocale = isCurViewFormattedAndUserIsEditing && decimalPlaces > 2;
|
||||||
|
|
||||||
|
const shouldUseLocale =
|
||||||
|
shouldUseCurLocale ||
|
||||||
|
!separators.length ||
|
||||||
|
(mode === 'auto' && separators.length === 1 && decimalPlaces >= 3);
|
||||||
|
if (shouldUseLocale) {
|
||||||
return 'withLocale';
|
return 'withLocale';
|
||||||
}
|
}
|
||||||
if (mode === 'auto' && separators.length === 1) {
|
|
||||||
const decimalLength = value.split(`${separators}`)[1].length;
|
const shouldUseHeuristic =
|
||||||
if (decimalLength >= 3) {
|
(isCurViewFormattedAndUserIsEditing && decimalPlaces <= 2) ||
|
||||||
return 'withLocale';
|
separators.length === 1 ||
|
||||||
}
|
separators[0] !== separators[separators.length - 1];
|
||||||
}
|
if (shouldUseHeuristic) {
|
||||||
if (separators.length === 1 || separators[0] !== separators[separators.length - 1]) {
|
|
||||||
return 'heuristic';
|
return 'heuristic';
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'unparseable';
|
return 'unparseable';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ describe('parseNumber()', () => {
|
||||||
expect(parseNumber('123.456,78', { mode: 'auto' })).to.equal(123456.78);
|
expect(parseNumber('123.456,78', { mode: 'auto' })).to.equal(123456.78);
|
||||||
expect(
|
expect(
|
||||||
parseNumber('123.456,78', { mode: 'user-edited', viewValueStates: ['formatted'] }),
|
parseNumber('123.456,78', { mode: 'user-edited', viewValueStates: ['formatted'] }),
|
||||||
).to.equal(123.45678);
|
).to.equal(123456.78);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('detects separators unparseable when there are 2 same ones e.g. 1.234.56', () => {
|
it('detects separators unparseable when there are 2 same ones e.g. 1.234.56', () => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue