123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414 |
- /*!
- * All material copyright ESRI, All Rights Reserved, unless otherwise specified.
- * See https://github.com/Esri/calcite-components/blob/master/LICENSE.md for details.
- * v1.0.0-beta.97
- */
- 'use strict';
- const key = require('./key-6a462411.js');
- const observers = require('./observers-5706326b.js');
- const dom = require('./dom-2ec8c9ed.js');
- // adopted from https://stackoverflow.com/a/66939244
- class BigDecimal {
- constructor(input) {
- if (input instanceof BigDecimal) {
- return input;
- }
- const [integers, decimals] = String(input).split(".").concat("");
- this.value =
- BigInt(integers + decimals.padEnd(BigDecimal.DECIMALS, "0").slice(0, BigDecimal.DECIMALS)) +
- BigInt(BigDecimal.ROUNDED && decimals[BigDecimal.DECIMALS] >= "5");
- this.isNegative = input.charAt(0) === "-";
- }
- static _divRound(dividend, divisor) {
- return BigDecimal.fromBigInt(dividend / divisor + (BigDecimal.ROUNDED ? ((dividend * BigInt(2)) / divisor) % BigInt(2) : BigInt(0)));
- }
- static fromBigInt(bigint) {
- return Object.assign(Object.create(BigDecimal.prototype), { value: bigint });
- }
- toString() {
- const s = this.value
- .toString()
- .replace(new RegExp("-", "g"), "")
- .padStart(BigDecimal.DECIMALS + 1, "0");
- const i = s.slice(0, -BigDecimal.DECIMALS);
- const d = s.slice(-BigDecimal.DECIMALS).replace(/\.?0+$/, "");
- const value = i.concat(d.length ? "." + d : "");
- return `${this.isNegative ? "-" : ""}${value}`;
- }
- formatToParts(formatter) {
- const s = this.value
- .toString()
- .replace(new RegExp("-", "g"), "")
- .padStart(BigDecimal.DECIMALS + 1, "0");
- const i = s.slice(0, -BigDecimal.DECIMALS);
- const d = s.slice(-BigDecimal.DECIMALS).replace(/\.?0+$/, "");
- const parts = formatter.formatToParts(BigInt(i));
- this.isNegative && parts.unshift({ type: "minusSign", value: numberStringFormatter.minusSign });
- if (d.length) {
- parts.push({ type: "decimal", value: numberStringFormatter.decimal });
- d.split("").forEach((char) => parts.push({ type: "fraction", value: char }));
- }
- return parts;
- }
- format(formatter) {
- const s = this.value
- .toString()
- .replace(new RegExp("-", "g"), "")
- .padStart(BigDecimal.DECIMALS + 1, "0");
- const i = s.slice(0, -BigDecimal.DECIMALS);
- const d = s.slice(-BigDecimal.DECIMALS).replace(/\.?0+$/, "");
- const iFormatted = `${this.isNegative ? numberStringFormatter.minusSign : ""}${formatter.format(BigInt(i))}`;
- const dFormatted = d.length ? `${numberStringFormatter.decimal}${formatter.format(BigInt(d))}` : "";
- return `${iFormatted}${dFormatted}`;
- }
- add(num) {
- return BigDecimal.fromBigInt(this.value + new BigDecimal(num).value);
- }
- subtract(num) {
- return BigDecimal.fromBigInt(this.value - new BigDecimal(num).value);
- }
- multiply(num) {
- return BigDecimal._divRound(this.value * new BigDecimal(num).value, BigDecimal.SHIFT);
- }
- divide(num) {
- return BigDecimal._divRound(this.value * BigDecimal.SHIFT, new BigDecimal(num).value);
- }
- }
- // Configuration: constants
- BigDecimal.DECIMALS = 100; // number of decimals on all instances
- BigDecimal.ROUNDED = true; // numbers are truncated (false) or rounded (true)
- BigDecimal.SHIFT = BigInt("1" + "0".repeat(BigDecimal.DECIMALS)); // derived constant
- function isValidNumber(numberString) {
- return !(!numberString || isNaN(Number(numberString)));
- }
- function parseNumberString(numberString) {
- if (!numberString || !stringContainsNumbers(numberString)) {
- return "";
- }
- return sanitizeExponentialNumberString(numberString, (nonExpoNumString) => {
- let containsDecimal = false;
- const result = nonExpoNumString
- .split("")
- .filter((value, i) => {
- if (value.match(/\./g) && !containsDecimal) {
- containsDecimal = true;
- return true;
- }
- if (value.match(/\-/g) && i === 0) {
- return true;
- }
- return key.numberKeys.includes(value);
- })
- .reduce((string, part) => string + part);
- return isValidNumber(result) ? new BigDecimal(result).toString() : "";
- });
- }
- // regex for number sanitization
- const allLeadingZerosOptionallyNegative = /^([-0])0+(?=\d)/;
- const decimalOnlyAtEndOfString = /(?!^\.)\.$/;
- const allHyphensExceptTheStart = /(?!^-)-/g;
- const isNegativeDecimalOnlyZeros = /^-\b0\b\.?0*$/;
- const sanitizeNumberString = (numberString) => sanitizeExponentialNumberString(numberString, (nonExpoNumString) => {
- const sanitizedValue = nonExpoNumString
- .replace(allHyphensExceptTheStart, "")
- .replace(decimalOnlyAtEndOfString, "")
- .replace(allLeadingZerosOptionallyNegative, "$1");
- return isValidNumber(sanitizedValue)
- ? isNegativeDecimalOnlyZeros.test(sanitizedValue)
- ? sanitizedValue
- : new BigDecimal(sanitizedValue).toString()
- : nonExpoNumString;
- });
- function sanitizeExponentialNumberString(numberString, func) {
- if (!numberString) {
- return numberString;
- }
- const firstE = numberString.toLowerCase().indexOf("e") + 1;
- if (!firstE) {
- return func(numberString);
- }
- return numberString
- .replace(/[eE]*$/g, "")
- .substring(0, firstE)
- .concat(numberString.slice(firstE).replace(/[eE]/g, ""))
- .split(/[eE]/)
- .map((section, i) => (i === 1 ? func(section.replace(/\./g, "")) : func(section)))
- .join("e")
- .replace(/^e/, "1e");
- }
- function stringContainsNumbers(string) {
- return key.numberKeys.some((number) => string.includes(number));
- }
- const defaultLocale = "en";
- const locales = [
- "ar",
- "bg",
- "bs",
- "ca",
- "cs",
- "da",
- "de",
- "de-CH",
- "el",
- defaultLocale,
- "en-AU",
- "en-CA",
- "en-GB",
- "es",
- "es-MX",
- "et",
- "fi",
- "fr",
- "fr-CH",
- "he",
- "hi",
- "hr",
- "hu",
- "id",
- "it",
- "it-CH",
- "ja",
- "ko",
- "lt",
- "lv",
- "mk",
- "nb",
- "nl",
- "pl",
- "pt",
- "pt-PT",
- "ro",
- "ru",
- "sk",
- "sl",
- "sr",
- "sv",
- "th",
- "tr",
- "uk",
- "vi",
- "zh-CN",
- "zh-HK",
- "zh-TW"
- ];
- const numberingSystems = [
- "arab",
- "arabext",
- "bali",
- "beng",
- "deva",
- "fullwide",
- "gujr",
- "guru",
- "hanidec",
- "khmr",
- "knda",
- "laoo",
- "latn",
- "limb",
- "mlym",
- "mong",
- "mymr",
- "orya",
- "tamldec",
- "telu",
- "thai",
- "tibt"
- ];
- const isNumberingSystemSupported = (numberingSystem) => numberingSystems.includes(numberingSystem);
- const browserNumberingSystem = new Intl.NumberFormat().resolvedOptions().numberingSystem;
- const defaultNumberingSystem = browserNumberingSystem === "arab" || !isNumberingSystemSupported(browserNumberingSystem)
- ? "latn"
- : browserNumberingSystem;
- const getSupportedNumberingSystem = (numberingSystem) => isNumberingSystemSupported(numberingSystem) ? numberingSystem : defaultNumberingSystem;
- function getSupportedLocale(locale) {
- if (locales.indexOf(locale) > -1) {
- return locale;
- }
- if (!locale) {
- return defaultLocale;
- }
- locale = locale.toLowerCase();
- // we support both 'nb' and 'no' (BCP 47) for Norwegian
- if (locale === "nb") {
- return "no";
- }
- if (locale.includes("-")) {
- locale = locale.replace(/(\w+)-(\w+)/, (_match, language, region) => `${language}-${region.toUpperCase()}`);
- if (!locales.includes(locale)) {
- locale = locale.split("-")[0];
- }
- }
- return locales.includes(locale) ? locale : defaultLocale;
- }
- const connectedComponents = new Set();
- /**
- * This utility sets up internals for messages support.
- *
- * It needs to be called in `connectedCallback` before any logic that depends on locale
- *
- * @param component
- */
- function connectLocalized(component) {
- updateEffectiveLocale(component);
- if (connectedComponents.size === 0) {
- mutationObserver.observe(document.documentElement, {
- attributes: true,
- attributeFilter: ["lang"],
- subtree: true
- });
- }
- connectedComponents.add(component);
- }
- /**
- * This is only exported for components that implemented the now deprecated `locale` prop.
- *
- * Do not use this utils for new components.
- *
- * @param component
- */
- function updateEffectiveLocale(component) {
- component.effectiveLocale = getLocale(component);
- }
- /**
- * This utility tears down internals for messages support.
- *
- * It needs to be called in `disconnectedCallback`
- *
- * @param component
- */
- function disconnectLocalized(component) {
- connectedComponents.delete(component);
- if (connectedComponents.size === 0) {
- mutationObserver.disconnect();
- }
- }
- const mutationObserver = observers.createObserver("mutation", (records) => {
- records.forEach((record) => {
- const el = record.target;
- connectedComponents.forEach((component) => {
- const hasOverridingLocale = !!(component.locale && !component.el.lang);
- const inUnrelatedSubtree = !dom.containsCrossShadowBoundary(el, component.el);
- if (hasOverridingLocale || inUnrelatedSubtree) {
- return;
- }
- const closestLangEl = dom.closestElementCrossShadowBoundary(component.el, "[lang]");
- if (!closestLangEl) {
- component.effectiveLocale = defaultLocale;
- return;
- }
- const closestLang = closestLangEl.lang;
- component.effectiveLocale =
- // user set lang="" means unknown language, so we use default
- closestLangEl.hasAttribute("lang") && closestLang === "" ? defaultLocale : closestLang;
- });
- });
- });
- /**
- * This util helps resolve a component's locale.
- * It will also fall back on the deprecated `locale` if a component implemented this previously.
- *
- * @param component
- */
- function getLocale(component) {
- var _a;
- return (component.el.lang ||
- component.locale ||
- ((_a = dom.closestElementCrossShadowBoundary(component.el, "[lang]")) === null || _a === void 0 ? void 0 : _a.lang) ||
- document.documentElement.lang ||
- defaultLocale);
- }
- /**
- * This util formats and parses numbers for localization
- */
- class NumberStringFormat {
- constructor() {
- this.delocalize = (numberString) =>
- // For performance, (de)localization is skipped if the formatter isn't initialized.
- // In order to localize/delocalize, e.g. when lang/numberingSystem props are not default values,
- // `numberFormatOptions` must be set in a component to create and cache the formatter.
- this._numberFormatOptions
- ? sanitizeExponentialNumberString(numberString, (nonExpoNumString) => nonExpoNumString
- .trim()
- .replace(new RegExp(`[${this._minusSign}]`, "g"), "-")
- .replace(new RegExp(`[${this._group}]`, "g"), "")
- .replace(new RegExp(`[${this._decimal}]`, "g"), ".")
- .replace(new RegExp(`[${this._digits.join("")}]`, "g"), this._getDigitIndex))
- : numberString;
- this.localize = (numberString) => this._numberFormatOptions
- ? sanitizeExponentialNumberString(numberString, (nonExpoNumString) => isValidNumber(nonExpoNumString.trim())
- ? new BigDecimal(nonExpoNumString.trim())
- .format(this._numberFormatter)
- .replace(new RegExp(`[${this._actualGroup}]`, "g"), this._group)
- : nonExpoNumString)
- : numberString;
- }
- get group() {
- return this._group;
- }
- get decimal() {
- return this._decimal;
- }
- get minusSign() {
- return this._minusSign;
- }
- get digits() {
- return this._digits;
- }
- get numberFormatter() {
- return this._numberFormatter;
- }
- get numberFormatOptions() {
- return this._numberFormatOptions;
- }
- /**
- * numberFormatOptions needs to be set before localize/delocalize is called to ensure the options are up to date
- */
- set numberFormatOptions(options) {
- options.locale = getSupportedLocale(options === null || options === void 0 ? void 0 : options.locale);
- options.numberingSystem = getSupportedNumberingSystem(options === null || options === void 0 ? void 0 : options.numberingSystem);
- if (
- // No need to create the formatter if `locale` and `numberingSystem`
- // are the default values and `numberFormatOptions` has not been set
- (!this._numberFormatOptions &&
- options.locale === defaultLocale &&
- options.numberingSystem === defaultNumberingSystem &&
- // don't skip initialization if any options besides locale/numberingSystem are set
- Object.keys(options).length === 2) ||
- // cache formatter by only recreating when options change
- JSON.stringify(this._numberFormatOptions) === JSON.stringify(options)) {
- return;
- }
- this._numberFormatOptions = options;
- this._numberFormatter = new Intl.NumberFormat(this._numberFormatOptions.locale, this._numberFormatOptions);
- this._digits = [
- ...new Intl.NumberFormat(this._numberFormatOptions.locale, {
- useGrouping: false,
- numberingSystem: this._numberFormatOptions.numberingSystem
- }).format(9876543210)
- ].reverse();
- const index = new Map(this._digits.map((d, i) => [d, i]));
- const parts = new Intl.NumberFormat(this._numberFormatOptions.locale).formatToParts(-12345678.9);
- this._actualGroup = parts.find((d) => d.type === "group").value;
- // change whitespace group characters that don't render correctly
- this._group = this._actualGroup.trim().length === 0 ? " " : this._actualGroup;
- this._decimal = parts.find((d) => d.type === "decimal").value;
- this._minusSign = parts.find((d) => d.type === "minusSign").value;
- this._getDigitIndex = (d) => index.get(d);
- }
- }
- const numberStringFormatter = new NumberStringFormat();
- exports.connectLocalized = connectLocalized;
- exports.defaultNumberingSystem = defaultNumberingSystem;
- exports.disconnectLocalized = disconnectLocalized;
- exports.getSupportedLocale = getSupportedLocale;
- exports.getSupportedNumberingSystem = getSupportedNumberingSystem;
- exports.isValidNumber = isValidNumber;
- exports.numberStringFormatter = numberStringFormatter;
- exports.parseNumberString = parseNumberString;
- exports.sanitizeNumberString = sanitizeNumberString;
- exports.updateEffectiveLocale = updateEffectiveLocale;
|