feat: format number add thousandSeparator option, fix type decimalSep (#1774)
This commit is contained in:
parent
11c5ffe094
commit
2d58320e51
12 changed files with 240 additions and 21 deletions
6
.changeset/neat-pots-prove.md
Normal file
6
.changeset/neat-pots-prove.md
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
'@lion/input-amount': minor
|
||||||
|
'@lion/localize': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Allow specifying thousandSeparator for format number. BREAKING: change decimalSeparator type to only be ',' or '.'.
|
||||||
|
|
@ -108,15 +108,15 @@ export const noDecimals = () => html`
|
||||||
|
|
||||||
For copy pasting numbers into the input-amount, there is slightly different parsing behavior.
|
For copy pasting numbers into the input-amount, there is slightly different parsing behavior.
|
||||||
|
|
||||||
Normally, when it receives an input with only 1 separator character, we check the locale to determine whether this character is a thousand separator, or a decimal separator.
|
Normally, when it receives an input with only 1 separator character, we check the locale to determine whether this character is a group (thousand) separator, or a decimal separator.
|
||||||
When a user pastes the input from a different source, we find this approach (locale-based) quite unreliable, because it may have been copied from a 'mathematical context' (like an Excel sheet) or a context with a different locale.
|
When a user pastes the input from a different source, we find this approach (locale-based) quite unreliable, because it may have been copied from a 'mathematical context' (like an Excel sheet) or a context with a different locale.
|
||||||
Therefore, we use the heuristics based method to parse the input when it is pasted by the user.
|
Therefore, we use the heuristics based method to parse the input when it is pasted by the user.
|
||||||
|
|
||||||
### What this means
|
### What this means
|
||||||
|
|
||||||
If the user in an English locale types `400,0` it will become `4,000.00`
|
If the user in an English locale types `400,0` it will become `4,000.00`
|
||||||
because the locale determines that the comma is a thousand separator, not a decimal separator.
|
because the locale determines that the comma is a group separator, not a decimal separator.
|
||||||
|
|
||||||
If the user in an English locale pastes `400,0` instead, it will become `400.00` because we cannot rely on locale.
|
If the user in an English locale pastes `400,0` instead, it will become `400.00` because we cannot rely on locale.
|
||||||
Therefore, instead, we determine that the comma cannot be a thousand separator because it is not followed by 3 digits after.
|
Therefore, instead, we determine that the comma cannot be a group separator because it is not followed by 3 digits after.
|
||||||
It is more likely to be a decimal separator.
|
It is more likely to be a decimal separator.
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ export const unparseable = () => html`
|
||||||
|
|
||||||
A formatter should return a `formattedValue`. It accepts the current modelValue and an options object.
|
A formatter should return a `formattedValue`. It accepts the current modelValue and an options object.
|
||||||
|
|
||||||
Below is a very naive and limited parser that ignores non-digits. The formatter then uses `Intl.NumberFormat` to format it with thousand separators.
|
Below is a very naive and limited parser that ignores non-digits. The formatter then uses `Intl.NumberFormat` to format it with group (thousand) separators.
|
||||||
|
|
||||||
Formatted value is reflected back to the user `on-blur` of the field, but only if the field has no errors (validation).
|
Formatted value is reflected back to the user `on-blur` of the field, but only if the field has no errors (validation).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,18 @@ describe('<lion-input-amount>', () => {
|
||||||
expect(el.formattedValue).to.equal('99.00');
|
expect(el.formattedValue).to.equal('99.00');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('supports overriding groupSeparator in formatOptions', async () => {
|
||||||
|
const el = /** @type {LionInputAmount} */ (
|
||||||
|
await fixture(
|
||||||
|
html`<lion-input-amount
|
||||||
|
.formatOptions="${{ locale: 'nl-NL', groupSeparator: ',', decimalSeparator: '.' }}"
|
||||||
|
.modelValue="${9999}"
|
||||||
|
></lion-input-amount>`,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
expect(el.formattedValue).to.equal('9,999.00');
|
||||||
|
});
|
||||||
|
|
||||||
it('ignores global locale change if property is provided', async () => {
|
it('ignores global locale change if property is provided', async () => {
|
||||||
const el = /** @type {LionInputAmount} */ (
|
const el = /** @type {LionInputAmount} */ (
|
||||||
await fixture(html`
|
await fixture(html`
|
||||||
|
|
|
||||||
|
|
@ -13,5 +13,6 @@ export { getCurrencyName } from './src/number/getCurrencyName.js';
|
||||||
export { getDecimalSeparator } from './src/number/getDecimalSeparator.js';
|
export { getDecimalSeparator } from './src/number/getDecimalSeparator.js';
|
||||||
export { getFractionDigits } from './src/number/getFractionDigits.js';
|
export { getFractionDigits } from './src/number/getFractionDigits.js';
|
||||||
export { getGroupSeparator } from './src/number/getGroupSeparator.js';
|
export { getGroupSeparator } from './src/number/getGroupSeparator.js';
|
||||||
|
export { getSeparatorsFromNumber } from './src/number/getSeparatorsFromNumber.js';
|
||||||
export { normalizeCurrencyLabel } from './src/number/normalizeCurrencyLabel.js';
|
export { normalizeCurrencyLabel } from './src/number/normalizeCurrencyLabel.js';
|
||||||
export { parseNumber } from './src/number/parseNumber.js';
|
export { parseNumber } from './src/number/parseNumber.js';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { emptyStringWhenNumberNan } from './utils/emptyStringWhenNumberNan.js';
|
import { emptyStringWhenNumberNan } from './utils/emptyStringWhenNumberNan.js';
|
||||||
|
import { getSeparatorsFromNumber } from './getSeparatorsFromNumber.js';
|
||||||
import { getDecimalSeparator } from './getDecimalSeparator.js';
|
import { getDecimalSeparator } from './getDecimalSeparator.js';
|
||||||
import { getGroupSeparator } from './getGroupSeparator.js';
|
import { getGroupSeparator } from './getGroupSeparator.js';
|
||||||
import { getLocale } from '../utils/getLocale.js';
|
import { getLocale } from '../utils/getLocale.js';
|
||||||
|
|
@ -48,14 +49,29 @@ export function formatNumberToParts(number, options = {}) {
|
||||||
let formattedParts = [];
|
let formattedParts = [];
|
||||||
|
|
||||||
const formattedNumber = Intl.NumberFormat(computedLocale, options).format(parsedNumber);
|
const formattedNumber = Intl.NumberFormat(computedLocale, options).format(parsedNumber);
|
||||||
const regexCurrency = /[.,\s0-9]/;
|
const { decimalSeparator, groupSeparator } = getSeparatorsFromNumber(
|
||||||
|
parsedNumber,
|
||||||
|
formattedNumber,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-irregular-whitespace
|
||||||
|
const regexCurrency = /[.,\s0-9 _ ]/;
|
||||||
const regexMinusSign = /[-]/; // U+002D, Hyphen-Minus, -
|
const regexMinusSign = /[-]/; // U+002D, Hyphen-Minus, -
|
||||||
const regexNum = /[0-9]/;
|
const regexNum = /[0-9]/;
|
||||||
const regexSeparator = /[.,]/;
|
|
||||||
const regexSpace = /[\s]/;
|
const regexSpace = /[\s]/;
|
||||||
let currency = '';
|
let currency = '';
|
||||||
let numberPart = '';
|
let numberPart = '';
|
||||||
let fraction = false;
|
let fraction = false;
|
||||||
|
let isGroup = false;
|
||||||
|
const group = getGroupSeparator(computedLocale, options);
|
||||||
|
const decimal = getDecimalSeparator(computedLocale, options);
|
||||||
|
if (decimalSeparator && groupSeparator && group === decimal) {
|
||||||
|
throw new Error(`Decimal and group (thousand) separator are the same character: '${group}'.
|
||||||
|
This can happen due to both props being specified as the same, or one of the props being the same as the other one from default locale.
|
||||||
|
Please specify .groupSeparator / .decimalSeparator on the formatOptions object to be different.`);
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < formattedNumber.length; i += 1) {
|
for (let i = 0; i < formattedNumber.length; i += 1) {
|
||||||
// detect minusSign
|
// detect minusSign
|
||||||
if (regexMinusSign.test(formattedNumber[i])) {
|
if (regexMinusSign.test(formattedNumber[i])) {
|
||||||
|
|
@ -76,24 +92,35 @@ export function formatNumberToParts(number, options = {}) {
|
||||||
currency = '';
|
currency = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// detect dot and comma separators
|
// group sep must be lead by / followed by a number
|
||||||
if (regexSeparator.test(formattedNumber[i])) {
|
if (
|
||||||
|
formattedNumber[i] === groupSeparator &&
|
||||||
|
formattedNumber[i - 1].match(regexNum) &&
|
||||||
|
formattedNumber[i + 1].match(regexNum)
|
||||||
|
) {
|
||||||
// Write number grouping
|
// Write number grouping
|
||||||
if (numberPart) {
|
if (numberPart) {
|
||||||
formattedParts.push({ type: 'integer', value: numberPart });
|
formattedParts.push({ type: 'integer', value: numberPart });
|
||||||
numberPart = '';
|
numberPart = '';
|
||||||
}
|
}
|
||||||
const decimal = getDecimalSeparator(computedLocale, options);
|
|
||||||
if (formattedNumber[i] === decimal || options.decimalSeparator === decimal) {
|
formattedParts.push({ type: 'group', value: group });
|
||||||
formattedParts.push({ type: 'decimal', value: decimal });
|
isGroup = true;
|
||||||
fraction = true;
|
|
||||||
} else {
|
|
||||||
formattedParts.push({ type: 'group', value: formattedNumber[i] });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (formattedNumber[i] === decimalSeparator) {
|
||||||
|
// Write number grouping
|
||||||
|
if (numberPart) {
|
||||||
|
formattedParts.push({ type: 'integer', value: numberPart });
|
||||||
|
numberPart = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
formattedParts.push({ type: 'decimal', value: decimal });
|
||||||
|
fraction = true;
|
||||||
|
}
|
||||||
|
|
||||||
// detect literals (empty spaces) or space group separator
|
// detect literals (empty spaces) or space group separator
|
||||||
if (regexSpace.test(formattedNumber[i])) {
|
if (regexSpace.test(formattedNumber[i])) {
|
||||||
const group = getGroupSeparator(computedLocale);
|
|
||||||
const hasNumberPart = !!numberPart;
|
const hasNumberPart = !!numberPart;
|
||||||
// Write number grouping
|
// Write number grouping
|
||||||
if (numberPart && !fraction) {
|
if (numberPart && !fraction) {
|
||||||
|
|
@ -106,10 +133,12 @@ export function formatNumberToParts(number, options = {}) {
|
||||||
// If space equals the group separator it gets type group
|
// If space equals the group separator it gets type group
|
||||||
if (normalSpaces(formattedNumber[i]) === group && hasNumberPart && !fraction) {
|
if (normalSpaces(formattedNumber[i]) === group && hasNumberPart && !fraction) {
|
||||||
formattedParts.push({ type: 'group', value: formattedNumber[i] });
|
formattedParts.push({ type: 'group', value: formattedNumber[i] });
|
||||||
} else {
|
// if we already pushed it as a group separator, don't add it as a literal on top..
|
||||||
|
} else if (!isGroup) {
|
||||||
formattedParts.push({ type: 'literal', value: formattedNumber[i] });
|
formattedParts.push({ type: 'literal', value: formattedNumber[i] });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
isGroup = false;
|
||||||
// Numbers after the decimal sign are fractions, write the last
|
// Numbers after the decimal sign are fractions, write the last
|
||||||
// fractions at the end of the number
|
// fractions at the end of the number
|
||||||
if (fraction === true && i === formattedNumber.length - 1) {
|
if (fraction === true && i === formattedNumber.length - 1) {
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,13 @@ import { normalSpaces } from './utils/normalSpaces.js';
|
||||||
* Gets the group separator
|
* Gets the group separator
|
||||||
*
|
*
|
||||||
* @param {string} [locale] To override the browser locale
|
* @param {string} [locale] To override the browser locale
|
||||||
|
* @param {import('../../types/LocalizeMixinTypes').FormatNumberOptions} [options]
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function getGroupSeparator(locale) {
|
export function getGroupSeparator(locale, options) {
|
||||||
|
if (options && options.groupSeparator) {
|
||||||
|
return options.groupSeparator;
|
||||||
|
}
|
||||||
const computedLocale = getLocale(locale);
|
const computedLocale = getLocale(locale);
|
||||||
const formattedNumber = Intl.NumberFormat(computedLocale, {
|
const formattedNumber = Intl.NumberFormat(computedLocale, {
|
||||||
style: 'decimal',
|
style: 'decimal',
|
||||||
|
|
|
||||||
58
packages/localize/src/number/getSeparatorsFromNumber.js
Normal file
58
packages/localize/src/number/getSeparatorsFromNumber.js
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {number} parsedNumber
|
||||||
|
* @param {string} formattedNumber
|
||||||
|
* @param {import('../../types/LocalizeMixinTypes').FormatNumberOptions} [options]
|
||||||
|
* @returns {{groupSeparator: string|null, decimalSeparator: string|null}}
|
||||||
|
*/
|
||||||
|
export function getSeparatorsFromNumber(parsedNumber, formattedNumber, options) {
|
||||||
|
// separator can only happen if there is at least 1 digit before and after the separator
|
||||||
|
// eslint-disable-next-line no-irregular-whitespace
|
||||||
|
const regexSeparator = /[0-9](?<sep>[\s,. _ '])[0-9]/g;
|
||||||
|
|
||||||
|
/** @type {string[]} */
|
||||||
|
const separators = [];
|
||||||
|
let match;
|
||||||
|
// eslint-disable-next-line no-cond-assign
|
||||||
|
while ((match = regexSeparator.exec(formattedNumber)) !== null) {
|
||||||
|
if (match.groups && match.groups.sep) {
|
||||||
|
separators.push(match.groups?.sep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let groupSeparator = null;
|
||||||
|
let decimalSeparator = null;
|
||||||
|
if (separators) {
|
||||||
|
if (separators.length === 1) {
|
||||||
|
const parts = formattedNumber.split(separators[0]);
|
||||||
|
// Not sure if decimal or group, because only 1 separator.
|
||||||
|
// if the separator is followed by at least 3 or more digits
|
||||||
|
// and if the original number value is more or equal than 1000 or less or equal than -1000
|
||||||
|
// or the minimum integer digits is forced to more than 3,
|
||||||
|
// it has to be the group separator
|
||||||
|
if (
|
||||||
|
parts[1].replace(/[^0-9]/g, '').length >= 3 &&
|
||||||
|
(parsedNumber >= 1000 ||
|
||||||
|
parsedNumber <= -1 * 1000 ||
|
||||||
|
(options?.minimumIntegerDigits && options.minimumIntegerDigits > 3))
|
||||||
|
) {
|
||||||
|
[groupSeparator] = separators;
|
||||||
|
} else {
|
||||||
|
[decimalSeparator] = separators;
|
||||||
|
}
|
||||||
|
} else if (separators.every(val => val === separators[0])) {
|
||||||
|
// multiple separators, check if they are all the same or not
|
||||||
|
// if the same, it means they are group separators
|
||||||
|
// if not, it means that the last one must be the decimal separator
|
||||||
|
[groupSeparator] = separators;
|
||||||
|
} else {
|
||||||
|
[groupSeparator] = separators;
|
||||||
|
decimalSeparator = separators[separators.length - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
groupSeparator,
|
||||||
|
decimalSeparator,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -94,7 +94,7 @@ function parseHeuristic(value) {
|
||||||
// 1. put placeholder at decimal separator
|
// 1. put placeholder at decimal separator
|
||||||
const numberString = value
|
const numberString = value
|
||||||
.replace(/(,|\.)([^,|.]*)$/g, '_decSep_$2')
|
.replace(/(,|\.)([^,|.]*)$/g, '_decSep_$2')
|
||||||
.replace(/(,|\.| )/g, '') // 2. remove all thousand separators
|
.replace(/(,|\.| )/g, '') // 2. remove all group separators
|
||||||
.replace(/_decSep_/, '.'); // 3. restore decimal separator
|
.replace(/_decSep_/, '.'); // 3. restore decimal separator
|
||||||
return parseFloat(numberString);
|
return parseFloat(numberString);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,64 @@ describe('formatNumber', () => {
|
||||||
maximumFractionDigits: 2,
|
maximumFractionDigits: 2,
|
||||||
}),
|
}),
|
||||||
).to.equal('112.345.678,00');
|
).to.equal('112.345.678,00');
|
||||||
|
expect(
|
||||||
|
formatNumber(112345678, {
|
||||||
|
style: 'decimal',
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
groupSeparator: ' ',
|
||||||
|
decimalSeparator: '.',
|
||||||
|
}),
|
||||||
|
).to.equal('112 345 678.00');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws when decimal and group separator are the same value, only when problematic', () => {
|
||||||
|
localize.locale = 'nl-NL';
|
||||||
|
const fn = () =>
|
||||||
|
formatNumber(112345678, {
|
||||||
|
style: 'decimal',
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
decimalSeparator: '.', // same as group separator for nl-NL
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(fn).to.throw(`Decimal and group (thousand) separator are the same character: '.'.
|
||||||
|
This can happen due to both props being specified as the same, or one of the props being the same as the other one from default locale.
|
||||||
|
Please specify .groupSeparator / .decimalSeparator on the formatOptions object to be different.`);
|
||||||
|
|
||||||
|
const fn2 = () =>
|
||||||
|
formatNumber(112345678, {
|
||||||
|
style: 'decimal',
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
groupSeparator: ',',
|
||||||
|
decimalSeparator: ',',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(fn2).to.throw(`Decimal and group (thousand) separator are the same character: ','.
|
||||||
|
This can happen due to both props being specified as the same, or one of the props being the same as the other one from default locale.
|
||||||
|
Please specify .groupSeparator / .decimalSeparator on the formatOptions object to be different.`);
|
||||||
|
|
||||||
|
// this one doesn't end up with decimals, so not a problem
|
||||||
|
const fn3 = () =>
|
||||||
|
formatNumber(112345678, {
|
||||||
|
groupSeparator: ',',
|
||||||
|
decimalSeparator: ',',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(fn3).to.not.throw();
|
||||||
|
|
||||||
|
// this one doesn't end up with group separators (<1000), so not a problem
|
||||||
|
const fn4 = () =>
|
||||||
|
formatNumber(112.345678, {
|
||||||
|
style: 'decimal',
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
groupSeparator: ',',
|
||||||
|
decimalSeparator: ',',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(fn4).to.not.throw();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('formats 2-digit decimals correctly', () => {
|
it('formats 2-digit decimals correctly', () => {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { expect } from '@open-wc/testing';
|
||||||
|
|
||||||
|
import { getSeparatorsFromNumber } from '../../src/number/getSeparatorsFromNumber.js';
|
||||||
|
|
||||||
|
describe('getSeparatorsFromNumber', () => {
|
||||||
|
it('returns group separator for locale', () => {
|
||||||
|
expect(getSeparatorsFromNumber(99, '99.00')).to.eql({
|
||||||
|
groupSeparator: null,
|
||||||
|
decimalSeparator: '.',
|
||||||
|
});
|
||||||
|
expect(getSeparatorsFromNumber(1000, '1,000')).to.eql({
|
||||||
|
groupSeparator: ',',
|
||||||
|
decimalSeparator: null,
|
||||||
|
});
|
||||||
|
expect(getSeparatorsFromNumber(12345678901, '12,345,678.901')).to.eql({
|
||||||
|
groupSeparator: ',',
|
||||||
|
decimalSeparator: '.',
|
||||||
|
});
|
||||||
|
expect(getSeparatorsFromNumber(12345678901, '12_345_678_901')).to.eql({
|
||||||
|
groupSeparator: '_',
|
||||||
|
decimalSeparator: null,
|
||||||
|
});
|
||||||
|
expect(getSeparatorsFromNumber(123, '123,00 €')).to.eql({
|
||||||
|
groupSeparator: null,
|
||||||
|
decimalSeparator: ',',
|
||||||
|
});
|
||||||
|
expect(getSeparatorsFromNumber(123, '€123,00')).to.eql({
|
||||||
|
groupSeparator: null,
|
||||||
|
decimalSeparator: ',',
|
||||||
|
});
|
||||||
|
expect(getSeparatorsFromNumber(1234, '123.400 dollar')).to.eql({
|
||||||
|
groupSeparator: '.',
|
||||||
|
decimalSeparator: null,
|
||||||
|
});
|
||||||
|
expect(getSeparatorsFromNumber(1234.5, '1 234,50 €')).to.eql({
|
||||||
|
groupSeparator: ' ',
|
||||||
|
decimalSeparator: ',',
|
||||||
|
});
|
||||||
|
expect(getSeparatorsFromNumber(-1234, '-1,234')).to.eql({
|
||||||
|
groupSeparator: ',',
|
||||||
|
decimalSeparator: null,
|
||||||
|
});
|
||||||
|
expect(getSeparatorsFromNumber(123, '0,123', { minimumIntegerDigits: 4 })).to.eql({
|
||||||
|
groupSeparator: ',',
|
||||||
|
decimalSeparator: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -19,7 +19,6 @@ export declare interface FormatDateOptions extends Intl.DateTimeFormatOptions {
|
||||||
|
|
||||||
roundMode?: string;
|
roundMode?: string;
|
||||||
returnIfNaN?: string;
|
returnIfNaN?: string;
|
||||||
decimalSeparator?: string;
|
|
||||||
mode?: 'pasted' | 'auto';
|
mode?: 'pasted' | 'auto';
|
||||||
|
|
||||||
postProcessors?: Map<string, DatePostProcessor>;
|
postProcessors?: Map<string, DatePostProcessor>;
|
||||||
|
|
@ -35,7 +34,11 @@ export declare interface FormatNumberOptions extends Intl.NumberFormatOptions {
|
||||||
numberingSystem?: string;
|
numberingSystem?: string;
|
||||||
roundMode?: string;
|
roundMode?: string;
|
||||||
returnIfNaN?: string;
|
returnIfNaN?: string;
|
||||||
decimalSeparator?: string;
|
// https://en.wikipedia.org/wiki/Decimal_separator#Current_standards
|
||||||
|
decimalSeparator?: ',' | '.';
|
||||||
|
// https://en.wikipedia.org/wiki/Decimal_separator#Digit_grouping
|
||||||
|
// note the half space in there as well
|
||||||
|
groupSeparator?: ',' | '.' | ' ' | '_' | ' ' | "'";
|
||||||
mode?: 'pasted' | 'auto';
|
mode?: 'pasted' | 'auto';
|
||||||
|
|
||||||
postProcessors?: Map<string, NumberPostProcessor>;
|
postProcessors?: Map<string, NumberPostProcessor>;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue