Merge pull request #398 from ing-bank/fix/amountParsing
fix(input-amount): handle user pasting of amounts heuristically
This commit is contained in:
commit
47cadb1696
5 changed files with 73 additions and 30 deletions
|
|
@ -310,12 +310,16 @@ export const FormatMixin = dedupeMixin(
|
|||
* `@user-input-changed` (this will happen later, when `formatOn` condition is met)
|
||||
*/
|
||||
_reflectBackFormattedValueToUser() {
|
||||
if (!this.__isHandlingUserInput) {
|
||||
if (this._reflectBackOn()) {
|
||||
// Text 'undefined' should not end up in <input>
|
||||
this.value = typeof this.formattedValue !== 'undefined' ? this.formattedValue : '';
|
||||
}
|
||||
}
|
||||
|
||||
_reflectBackOn() {
|
||||
return !this.__isHandlingUserInput;
|
||||
}
|
||||
|
||||
// This can be called whenever the view value should be updated. Dependent on component type
|
||||
// ("input" for <input> or "change" for <select>(mainly for IE)) a different event should be
|
||||
// used as source for the "user-input-changed" event (which can be seen as an abstraction
|
||||
|
|
|
|||
|
|
@ -49,10 +49,28 @@ export class LionInputAmount extends FieldCustomMixin(LocalizeMixin(LionInput))
|
|||
super();
|
||||
this.parser = parseAmount;
|
||||
this.formatter = formatAmount;
|
||||
this.__isPasting = false;
|
||||
|
||||
this.addEventListener('paste', () => {
|
||||
this.__isPasting = true;
|
||||
this.__parserCallcountSincePaste = 0;
|
||||
});
|
||||
|
||||
this.defaultValidators.push(new IsNumber());
|
||||
}
|
||||
|
||||
__callParser(value = this.formattedValue) {
|
||||
// TODO: input and change events both trigger parsing therefore we need to handle the second parse
|
||||
this.__parserCallcountSincePaste += 1;
|
||||
this.__isPasting = this.__parserCallcountSincePaste === 2;
|
||||
this.formatOptions.mode = this.__isPasting === true ? 'pasted' : 'auto';
|
||||
return super.__callParser(value);
|
||||
}
|
||||
|
||||
_reflectBackOn() {
|
||||
return super._reflectBackOn() || this.__isPasting;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
// eslint-disable-next-line wc/guard-super-call
|
||||
super.connectedCallback();
|
||||
|
|
|
|||
|
|
@ -11,9 +11,10 @@ function isDecimalSeparator(value) {
|
|||
/**
|
||||
* Determines the best possible parsing mode.
|
||||
*
|
||||
* Parsemode depends mostely on the last 4 chars.
|
||||
* - 1234 => xxx1234 (heuristic)
|
||||
* - If there is only one separator (withLocale)
|
||||
* - 1,23 => xxx1.23 (heuristic)
|
||||
* - else parse mode depends mostly on the last 4 chars
|
||||
* - 1234 => xxx1234 (heuristic)
|
||||
* - [space]123 => xxx123 (heuristic)
|
||||
* - ,123 => unclear
|
||||
* - if 1.000,123 (we find a different separator) => 1000.123 (heuristic)
|
||||
|
|
@ -30,7 +31,13 @@ function isDecimalSeparator(value) {
|
|||
* @param {string} value Clean number (only [0-9 ,.]) to be parsed
|
||||
* @return {string} unparseable|withLocale|heuristic
|
||||
*/
|
||||
function getParseMode(value) {
|
||||
function getParseMode(value, { mode = 'auto' } = {}) {
|
||||
const separators = value.match(/[., ]/g);
|
||||
|
||||
if (mode === 'auto' && separators && separators.length === 1) {
|
||||
return 'withLocale';
|
||||
}
|
||||
|
||||
if (value.length > 4) {
|
||||
const charAtLastSeparatorPosition = value[value.length - 4];
|
||||
if (isDecimalSeparator(charAtLastSeparatorPosition)) {
|
||||
|
|
@ -120,7 +127,7 @@ export function parseAmount(value, options) {
|
|||
return undefined;
|
||||
}
|
||||
const cleanedInput = matchedInput.join('');
|
||||
const parseMode = getParseMode(cleanedInput);
|
||||
const parseMode = getParseMode(cleanedInput, options);
|
||||
switch (parseMode) {
|
||||
case 'unparseable':
|
||||
return parseFloat(cleanedInput.match(/[0-9]/g).join(''));
|
||||
|
|
|
|||
|
|
@ -34,8 +34,12 @@ storiesOf('Forms|Input Amount', module)
|
|||
.add(
|
||||
'Force locale to nl-NL',
|
||||
() => html`
|
||||
<lion-input-amount label="Price" currency="JOD">
|
||||
.formatOptions="${{ locale: 'nl-NL' }}" .modelValue=${123456.78}
|
||||
<lion-input-amount
|
||||
label="Price"
|
||||
currency="JOD"
|
||||
.formatOptions="${{ locale: 'nl-NL' }}"
|
||||
.modelValue=${123456.78}
|
||||
>
|
||||
</lion-input-amount>
|
||||
`,
|
||||
)
|
||||
|
|
@ -79,5 +83,11 @@ storiesOf('Forms|Input Amount', module)
|
|||
Make sure to set the modelValue last as otherwise formatOptions will not be taken into
|
||||
account
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You can copy paste <input value="4000,0" /> and it will become 4000 independent of your
|
||||
locale. <br />
|
||||
If you write 4000,0 manually then it will become 4000 or 40000 dependent on your locale.
|
||||
</p>
|
||||
`,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -43,35 +43,35 @@ describe('parseAmount()', () => {
|
|||
expect(parseAmount('1 234,56789')).to.equal(1234.56789);
|
||||
});
|
||||
|
||||
it('detects separators heuristically when there is only one e.g. 123456,78', () => {
|
||||
expect(parseAmount('1.')).to.equal(1);
|
||||
expect(parseAmount('1,')).to.equal(1);
|
||||
expect(parseAmount('1 ')).to.equal(1);
|
||||
it('detects separators heuristically when there is only one and "pasted" mode used e.g. 123456,78', () => {
|
||||
expect(parseAmount('1.', { mode: 'pasted' })).to.equal(1);
|
||||
expect(parseAmount('1,', { mode: 'pasted' })).to.equal(1);
|
||||
expect(parseAmount('1 ', { mode: 'pasted' })).to.equal(1);
|
||||
|
||||
expect(parseAmount('1.2')).to.equal(1.2);
|
||||
expect(parseAmount('1,2')).to.equal(1.2);
|
||||
expect(parseAmount('1 2')).to.equal(12);
|
||||
expect(parseAmount('1.2', { mode: 'pasted' })).to.equal(1.2);
|
||||
expect(parseAmount('1,2', { mode: 'pasted' })).to.equal(1.2);
|
||||
expect(parseAmount('1 2', { mode: 'pasted' })).to.equal(12);
|
||||
|
||||
expect(parseAmount('1.23')).to.equal(1.23);
|
||||
expect(parseAmount('1,23')).to.equal(1.23);
|
||||
expect(parseAmount('1 23')).to.equal(123);
|
||||
expect(parseAmount('1.23', { mode: 'pasted' })).to.equal(1.23);
|
||||
expect(parseAmount('1,23', { mode: 'pasted' })).to.equal(1.23);
|
||||
expect(parseAmount('1 23', { mode: 'pasted' })).to.equal(123);
|
||||
|
||||
expect(parseAmount('1 234')).to.equal(1234);
|
||||
expect(parseAmount('1 234', { mode: 'pasted' })).to.equal(1234);
|
||||
|
||||
expect(parseAmount('1.2345')).to.equal(1.2345);
|
||||
expect(parseAmount('1,2345')).to.equal(1.2345);
|
||||
expect(parseAmount('1 2345')).to.equal(12345);
|
||||
expect(parseAmount('1.2345', { mode: 'pasted' })).to.equal(1.2345);
|
||||
expect(parseAmount('1,2345', { mode: 'pasted' })).to.equal(1.2345);
|
||||
expect(parseAmount('1 2345', { mode: 'pasted' })).to.equal(12345);
|
||||
|
||||
expect(parseAmount('1.23456')).to.equal(1.23456);
|
||||
expect(parseAmount('1,23456')).to.equal(1.23456);
|
||||
expect(parseAmount('1 23456')).to.equal(123456);
|
||||
expect(parseAmount('1.23456', { mode: 'pasted' })).to.equal(1.23456);
|
||||
expect(parseAmount('1,23456', { mode: 'pasted' })).to.equal(1.23456);
|
||||
expect(parseAmount('1 23456', { mode: 'pasted' })).to.equal(123456);
|
||||
|
||||
expect(parseAmount('1.234567')).to.equal(1.234567);
|
||||
expect(parseAmount('1,234567')).to.equal(1.234567);
|
||||
expect(parseAmount('1 234567')).to.equal(1234567);
|
||||
expect(parseAmount('1.234567', { mode: 'pasted' })).to.equal(1.234567);
|
||||
expect(parseAmount('1,234567', { mode: 'pasted' })).to.equal(1.234567);
|
||||
expect(parseAmount('1 234567', { mode: 'pasted' })).to.equal(1234567);
|
||||
|
||||
expect(parseAmount('123456,78')).to.equal(123456.78);
|
||||
expect(parseAmount('123456.78')).to.equal(123456.78);
|
||||
expect(parseAmount('123456,78', { mode: 'pasted' })).to.equal(123456.78);
|
||||
expect(parseAmount('123456.78', { mode: 'pasted' })).to.equal(123456.78);
|
||||
});
|
||||
|
||||
it('detects separators heuristically when there are 2 same ones e.g. 1.234.56', () => {
|
||||
|
|
@ -89,12 +89,16 @@ describe('parseAmount()', () => {
|
|||
expect(parseAmount('1,234,56789')).to.equal(1234.56789);
|
||||
});
|
||||
|
||||
it('uses locale if amount can not be interpreted heuristically e.g. 1.234', () => {
|
||||
it('uses locale to parse amount if there is only one separator e.g. 1.234', () => {
|
||||
localize.locale = 'en-GB';
|
||||
expect(parseAmount('12.34')).to.equal(12.34);
|
||||
expect(parseAmount('12,34')).to.equal(1234);
|
||||
expect(parseAmount('1.234')).to.equal(1.234);
|
||||
expect(parseAmount('1,234')).to.equal(1234);
|
||||
|
||||
localize.locale = 'nl-NL';
|
||||
expect(parseAmount('12.34')).to.equal(1234);
|
||||
expect(parseAmount('12,34')).to.equal(12.34);
|
||||
expect(parseAmount('1.234')).to.equal(1234);
|
||||
expect(parseAmount('1,234')).to.equal(1.234);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue