253 lines
15 KiB
JavaScript
253 lines
15 KiB
JavaScript
import {NumberFormatter as $488c6ddbf4ef74c2$export$cc77c4ff7e8673c5} from "./NumberFormatter.mjs";
|
|
|
|
/*
|
|
* Copyright 2020 Adobe. All rights reserved.
|
|
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
* governing permissions and limitations under the License.
|
|
*/
|
|
const $6c7bd7858deea686$var$CURRENCY_SIGN_REGEX = new RegExp('^.*\\(.*\\).*$');
|
|
const $6c7bd7858deea686$var$NUMBERING_SYSTEMS = [
|
|
'latn',
|
|
'arab',
|
|
'hanidec'
|
|
];
|
|
class $6c7bd7858deea686$export$cd11ab140839f11d {
|
|
/**
|
|
* Parses the given string to a number. Returns NaN if a valid number could not be parsed.
|
|
*/ parse(value) {
|
|
return $6c7bd7858deea686$var$getNumberParserImpl(this.locale, this.options, value).parse(value);
|
|
}
|
|
/**
|
|
* Returns whether the given string could potentially be a valid number. This should be used to
|
|
* validate user input as the user types. If a `minValue` or `maxValue` is provided, the validity
|
|
* of the minus/plus sign characters can be checked.
|
|
*/ isValidPartialNumber(value, minValue, maxValue) {
|
|
return $6c7bd7858deea686$var$getNumberParserImpl(this.locale, this.options, value).isValidPartialNumber(value, minValue, maxValue);
|
|
}
|
|
/**
|
|
* Returns a numbering system for which the given string is valid in the current locale.
|
|
* If no numbering system could be detected, the default numbering system for the current
|
|
* locale is returned.
|
|
*/ getNumberingSystem(value) {
|
|
return $6c7bd7858deea686$var$getNumberParserImpl(this.locale, this.options, value).options.numberingSystem;
|
|
}
|
|
constructor(locale, options = {}){
|
|
this.locale = locale;
|
|
this.options = options;
|
|
}
|
|
}
|
|
const $6c7bd7858deea686$var$numberParserCache = new Map();
|
|
function $6c7bd7858deea686$var$getNumberParserImpl(locale, options, value) {
|
|
// First try the default numbering system for the provided locale
|
|
let defaultParser = $6c7bd7858deea686$var$getCachedNumberParser(locale, options);
|
|
// If that doesn't match, and the locale doesn't include a hard coded numbering system,
|
|
// try each of the other supported numbering systems until we find one that matches.
|
|
if (!locale.includes('-nu-') && !defaultParser.isValidPartialNumber(value)) {
|
|
for (let numberingSystem of $6c7bd7858deea686$var$NUMBERING_SYSTEMS)if (numberingSystem !== defaultParser.options.numberingSystem) {
|
|
let parser = $6c7bd7858deea686$var$getCachedNumberParser(locale + (locale.includes('-u-') ? '-nu-' : '-u-nu-') + numberingSystem, options);
|
|
if (parser.isValidPartialNumber(value)) return parser;
|
|
}
|
|
}
|
|
return defaultParser;
|
|
}
|
|
function $6c7bd7858deea686$var$getCachedNumberParser(locale, options) {
|
|
let cacheKey = locale + (options ? Object.entries(options).sort((a, b)=>a[0] < b[0] ? -1 : 1).join() : '');
|
|
let parser = $6c7bd7858deea686$var$numberParserCache.get(cacheKey);
|
|
if (!parser) {
|
|
parser = new $6c7bd7858deea686$var$NumberParserImpl(locale, options);
|
|
$6c7bd7858deea686$var$numberParserCache.set(cacheKey, parser);
|
|
}
|
|
return parser;
|
|
}
|
|
// The actual number parser implementation. Instances of this class are cached
|
|
// based on the locale, options, and detected numbering system.
|
|
class $6c7bd7858deea686$var$NumberParserImpl {
|
|
parse(value) {
|
|
// to parse the number, we need to remove anything that isn't actually part of the number, for example we want '-10.40' not '-10.40 USD'
|
|
let fullySanitizedValue = this.sanitize(value);
|
|
if (this.symbols.group) // Remove group characters, and replace decimal points and numerals with ASCII values.
|
|
fullySanitizedValue = $6c7bd7858deea686$var$replaceAll(fullySanitizedValue, this.symbols.group, '');
|
|
if (this.symbols.decimal) fullySanitizedValue = fullySanitizedValue.replace(this.symbols.decimal, '.');
|
|
if (this.symbols.minusSign) fullySanitizedValue = fullySanitizedValue.replace(this.symbols.minusSign, '-');
|
|
fullySanitizedValue = fullySanitizedValue.replace(this.symbols.numeral, this.symbols.index);
|
|
if (this.options.style === 'percent') {
|
|
// javascript is bad at dividing by 100 and maintaining the same significant figures, so perform it on the string before parsing
|
|
let isNegative = fullySanitizedValue.indexOf('-');
|
|
fullySanitizedValue = fullySanitizedValue.replace('-', '');
|
|
let index = fullySanitizedValue.indexOf('.');
|
|
if (index === -1) index = fullySanitizedValue.length;
|
|
fullySanitizedValue = fullySanitizedValue.replace('.', '');
|
|
if (index - 2 === 0) fullySanitizedValue = `0.${fullySanitizedValue}`;
|
|
else if (index - 2 === -1) fullySanitizedValue = `0.0${fullySanitizedValue}`;
|
|
else if (index - 2 === -2) fullySanitizedValue = '0.00';
|
|
else fullySanitizedValue = `${fullySanitizedValue.slice(0, index - 2)}.${fullySanitizedValue.slice(index - 2)}`;
|
|
if (isNegative > -1) fullySanitizedValue = `-${fullySanitizedValue}`;
|
|
}
|
|
let newValue = fullySanitizedValue ? +fullySanitizedValue : NaN;
|
|
if (isNaN(newValue)) return NaN;
|
|
if (this.options.style === 'percent') {
|
|
var _this_options_minimumFractionDigits, _this_options_maximumFractionDigits;
|
|
// extra step for rounding percents to what our formatter would output
|
|
let options = {
|
|
...this.options,
|
|
style: 'decimal',
|
|
minimumFractionDigits: Math.min(((_this_options_minimumFractionDigits = this.options.minimumFractionDigits) !== null && _this_options_minimumFractionDigits !== void 0 ? _this_options_minimumFractionDigits : 0) + 2, 20),
|
|
maximumFractionDigits: Math.min(((_this_options_maximumFractionDigits = this.options.maximumFractionDigits) !== null && _this_options_maximumFractionDigits !== void 0 ? _this_options_maximumFractionDigits : 0) + 2, 20)
|
|
};
|
|
return new $6c7bd7858deea686$export$cd11ab140839f11d(this.locale, options).parse(new (0, $488c6ddbf4ef74c2$export$cc77c4ff7e8673c5)(this.locale, options).format(newValue));
|
|
}
|
|
// accounting will always be stripped to a positive number, so if it's accounting and has a () around everything, then we need to make it negative again
|
|
if (this.options.currencySign === 'accounting' && $6c7bd7858deea686$var$CURRENCY_SIGN_REGEX.test(value)) newValue = -1 * newValue;
|
|
return newValue;
|
|
}
|
|
sanitize(value) {
|
|
// Remove literals and whitespace, which are allowed anywhere in the string
|
|
value = value.replace(this.symbols.literals, '');
|
|
// Replace the ASCII minus sign with the minus sign used in the current locale
|
|
// so that both are allowed in case the user's keyboard doesn't have the locale's minus sign.
|
|
if (this.symbols.minusSign) value = value.replace('-', this.symbols.minusSign);
|
|
// In arab numeral system, their decimal character is 1643, but most keyboards don't type that
|
|
// instead they use the , (44) character or apparently the (1548) character.
|
|
if (this.options.numberingSystem === 'arab') {
|
|
if (this.symbols.decimal) {
|
|
value = value.replace(',', this.symbols.decimal);
|
|
value = value.replace(String.fromCharCode(1548), this.symbols.decimal);
|
|
}
|
|
if (this.symbols.group) value = $6c7bd7858deea686$var$replaceAll(value, '.', this.symbols.group);
|
|
}
|
|
// fr-FR group character is char code 8239, but that's not a key on the french keyboard,
|
|
// so allow 'period' as a group char and replace it with a space
|
|
if (this.options.locale === 'fr-FR') value = $6c7bd7858deea686$var$replaceAll(value, '.', String.fromCharCode(8239));
|
|
return value;
|
|
}
|
|
isValidPartialNumber(value, minValue = -Infinity, maxValue = Infinity) {
|
|
value = this.sanitize(value);
|
|
// Remove minus or plus sign, which must be at the start of the string.
|
|
if (this.symbols.minusSign && value.startsWith(this.symbols.minusSign) && minValue < 0) value = value.slice(this.symbols.minusSign.length);
|
|
else if (this.symbols.plusSign && value.startsWith(this.symbols.plusSign) && maxValue > 0) value = value.slice(this.symbols.plusSign.length);
|
|
// Numbers cannot start with a group separator
|
|
if (this.symbols.group && value.startsWith(this.symbols.group)) return false;
|
|
// Numbers that can't have any decimal values fail if a decimal character is typed
|
|
if (this.symbols.decimal && value.indexOf(this.symbols.decimal) > -1 && this.options.maximumFractionDigits === 0) return false;
|
|
// Remove numerals, groups, and decimals
|
|
if (this.symbols.group) value = $6c7bd7858deea686$var$replaceAll(value, this.symbols.group, '');
|
|
value = value.replace(this.symbols.numeral, '');
|
|
if (this.symbols.decimal) value = value.replace(this.symbols.decimal, '');
|
|
// The number is valid if there are no remaining characters
|
|
return value.length === 0;
|
|
}
|
|
constructor(locale, options = {}){
|
|
this.locale = locale;
|
|
this.formatter = new Intl.NumberFormat(locale, options);
|
|
this.options = this.formatter.resolvedOptions();
|
|
this.symbols = $6c7bd7858deea686$var$getSymbols(locale, this.formatter, this.options, options);
|
|
var _this_options_minimumFractionDigits, _this_options_maximumFractionDigits;
|
|
if (this.options.style === 'percent' && (((_this_options_minimumFractionDigits = this.options.minimumFractionDigits) !== null && _this_options_minimumFractionDigits !== void 0 ? _this_options_minimumFractionDigits : 0) > 18 || ((_this_options_maximumFractionDigits = this.options.maximumFractionDigits) !== null && _this_options_maximumFractionDigits !== void 0 ? _this_options_maximumFractionDigits : 0) > 18)) console.warn('NumberParser cannot handle percentages with greater than 18 decimal places, please reduce the number in your options.');
|
|
}
|
|
}
|
|
const $6c7bd7858deea686$var$nonLiteralParts = new Set([
|
|
'decimal',
|
|
'fraction',
|
|
'integer',
|
|
'minusSign',
|
|
'plusSign',
|
|
'group'
|
|
]);
|
|
// This list is derived from https://www.unicode.org/cldr/charts/43/supplemental/language_plural_rules.html#comparison and includes
|
|
// all unique numbers which we need to check in order to determine all the plural forms for a given locale.
|
|
// See: https://github.com/adobe/react-spectrum/pull/5134/files#r1337037855 for used script
|
|
const $6c7bd7858deea686$var$pluralNumbers = [
|
|
0,
|
|
4,
|
|
2,
|
|
1,
|
|
11,
|
|
20,
|
|
3,
|
|
7,
|
|
100,
|
|
21,
|
|
0.1,
|
|
1.1
|
|
];
|
|
function $6c7bd7858deea686$var$getSymbols(locale, formatter, intlOptions, originalOptions) {
|
|
var _allParts_find, _posAllParts_find, _decimalParts_find, _allParts_find1;
|
|
// formatter needs access to all decimal places in order to generate the correct literal strings for the plural set
|
|
let symbolFormatter = new Intl.NumberFormat(locale, {
|
|
...intlOptions,
|
|
minimumSignificantDigits: 1,
|
|
maximumSignificantDigits: 21
|
|
});
|
|
// Note: some locale's don't add a group symbol until there is a ten thousands place
|
|
let allParts = symbolFormatter.formatToParts(-10000.111);
|
|
let posAllParts = symbolFormatter.formatToParts(10000.111);
|
|
let pluralParts = $6c7bd7858deea686$var$pluralNumbers.map((n)=>symbolFormatter.formatToParts(n));
|
|
var _allParts_find_value;
|
|
let minusSign = (_allParts_find_value = (_allParts_find = allParts.find((p)=>p.type === 'minusSign')) === null || _allParts_find === void 0 ? void 0 : _allParts_find.value) !== null && _allParts_find_value !== void 0 ? _allParts_find_value : '-';
|
|
let plusSign = (_posAllParts_find = posAllParts.find((p)=>p.type === 'plusSign')) === null || _posAllParts_find === void 0 ? void 0 : _posAllParts_find.value;
|
|
// Safari does not support the signDisplay option, but our number parser polyfills it.
|
|
// If no plus sign was returned, but the original options contained signDisplay, default to the '+' character.
|
|
// @ts-ignore
|
|
if (!plusSign && ((originalOptions === null || originalOptions === void 0 ? void 0 : originalOptions.signDisplay) === 'exceptZero' || (originalOptions === null || originalOptions === void 0 ? void 0 : originalOptions.signDisplay) === 'always')) plusSign = '+';
|
|
// If maximumSignificantDigits is 1 (the minimum) then we won't get decimal characters out of the above formatters
|
|
// Percent also defaults to 0 fractionDigits, so we need to make a new one that isn't percent to get an accurate decimal
|
|
let decimalParts = new Intl.NumberFormat(locale, {
|
|
...intlOptions,
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2
|
|
}).formatToParts(0.001);
|
|
let decimal = (_decimalParts_find = decimalParts.find((p)=>p.type === 'decimal')) === null || _decimalParts_find === void 0 ? void 0 : _decimalParts_find.value;
|
|
let group = (_allParts_find1 = allParts.find((p)=>p.type === 'group')) === null || _allParts_find1 === void 0 ? void 0 : _allParts_find1.value;
|
|
// this set is also for a regex, it's all literals that might be in the string we want to eventually parse that
|
|
// don't contribute to the numerical value
|
|
let allPartsLiterals = allParts.filter((p)=>!$6c7bd7858deea686$var$nonLiteralParts.has(p.type)).map((p)=>$6c7bd7858deea686$var$escapeRegex(p.value));
|
|
let pluralPartsLiterals = pluralParts.flatMap((p)=>p.filter((p)=>!$6c7bd7858deea686$var$nonLiteralParts.has(p.type)).map((p)=>$6c7bd7858deea686$var$escapeRegex(p.value)));
|
|
let sortedLiterals = [
|
|
...new Set([
|
|
...allPartsLiterals,
|
|
...pluralPartsLiterals
|
|
])
|
|
].sort((a, b)=>b.length - a.length);
|
|
let literals = sortedLiterals.length === 0 ? new RegExp('[\\p{White_Space}]', 'gu') : new RegExp(`${sortedLiterals.join('|')}|[\\p{White_Space}]`, 'gu');
|
|
// These are for replacing non-latn characters with the latn equivalent
|
|
let numerals = [
|
|
...new Intl.NumberFormat(intlOptions.locale, {
|
|
useGrouping: false
|
|
}).format(9876543210)
|
|
].reverse();
|
|
let indexes = new Map(numerals.map((d, i)=>[
|
|
d,
|
|
i
|
|
]));
|
|
let numeral = new RegExp(`[${numerals.join('')}]`, 'g');
|
|
let index = (d)=>String(indexes.get(d));
|
|
return {
|
|
minusSign: minusSign,
|
|
plusSign: plusSign,
|
|
decimal: decimal,
|
|
group: group,
|
|
literals: literals,
|
|
numeral: numeral,
|
|
index: index
|
|
};
|
|
}
|
|
function $6c7bd7858deea686$var$replaceAll(str, find, replace) {
|
|
// @ts-ignore
|
|
if (str.replaceAll) // @ts-ignore
|
|
return str.replaceAll(find, replace);
|
|
return str.split(find).join(replace);
|
|
}
|
|
function $6c7bd7858deea686$var$escapeRegex(string) {
|
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
}
|
|
|
|
|
|
export {$6c7bd7858deea686$export$cd11ab140839f11d as NumberParser};
|
|
//# sourceMappingURL=NumberParser.module.js.map
|