UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

696 lines (695 loc) 21.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.NUMBER_MINUS = exports.NUMBER_CHARS = void 0; exports.cleanNumber = cleanNumber; exports.formatPhone = exports.formatORG = exports.formatNumber = exports.formatNIN = exports.formatDecimals = exports.formatBAN = exports.format = exports.countDecimals = void 0; exports.getCurrencySymbol = getCurrencySymbol; exports.getDecimalSeparator = getDecimalSeparator; exports.getFallbackCurrencyDisplay = getFallbackCurrencyDisplay; exports.getThousandsSeparator = getThousandsSeparator; exports.roundHalfEven = roundHalfEven; exports.runIOSSelectionFix = runIOSSelectionFix; var _parse = _interopRequireDefault(require("core-js-pure/stable/json/parse.js")); var _defaults = require("../../shared/defaults.js"); var _componentHelper = require("../../shared/component-helper.js"); var _helpers = require("../../shared/helpers.js"); var _index = _interopRequireDefault(require("../../shared/locales/index.js")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } const NUMBER_CHARS = exports.NUMBER_CHARS = '\\-0-9,.'; const NUMBER_MINUS = exports.NUMBER_MINUS = '-|−|‐|‒|–|—|―'; const ABSENT_VALUE_FORMAT = '–'; const format = (value, { locale = null, clean = false, compact = null, phone = null, org = null, ban = null, nin = null, percent = null, currency = null, currency_display = null, currency_position = null, omit_currency_sign = null, clean_copy_value = null, decimals = null, omit_rounding = null, rounding = null, signDisplay = null, options = null, returnAria = false, invalidAriaText = null } = {}) => { value = isAbsent(value) ? ABSENT_VALUE_FORMAT : value; let display = value; let aria = null; let type = 'number'; if (!locale) { locale = _defaults.LOCALE; } else if (locale === 'auto') { try { locale = window.navigator.language; } catch (e) { (0, _componentHelper.warn)(e); } } if ((0, _componentHelper.isTrue)(clean)) { value = cleanNumber(value); } const opts = (typeof options === 'string' && options[0] === '{' ? (0, _parse.default)(options) : options) || {}; if (signDisplay) { opts.signDisplay = signDisplay; } if (parseFloat(decimals) >= 0) { value = formatDecimals(value, decimals, rounding !== null && rounding !== void 0 ? rounding : omit_rounding, opts); } else if (typeof opts.maximumFractionDigits === 'undefined' && !(0, _componentHelper.isTrue)(percent) && !(0, _componentHelper.isTrue)(currency)) { opts.maximumFractionDigits = 20; } if ((0, _componentHelper.isTrue)(phone)) { type = 'phone'; const { number: _number, aria: _aria } = formatPhone(value, locale); if (clean === null) { value = cleanNumber(value); } display = _number; aria = _aria; } else if ((0, _componentHelper.isTrue)(ban)) { type = 'ban'; const { number: _number, aria: _aria } = formatBAN(value, locale); display = _number; aria = _aria; } else if ((0, _componentHelper.isTrue)(nin)) { type = 'nin'; const { number: _number, aria: _aria } = formatNIN(value, locale); display = _number; aria = _aria; } else if ((0, _componentHelper.isTrue)(org)) { type = 'org'; const { number: _number, aria: _aria } = formatORG(value, locale); display = _number; aria = _aria; } else if ((0, _componentHelper.isTrue)(percent)) { if (decimals === null) { if (typeof opts.maximumFractionDigits === 'undefined') { decimals = countDecimals(value); } value = formatDecimals(value, decimals, rounding !== null && rounding !== void 0 ? rounding : omit_rounding, opts); } if (!opts.style) { opts.style = 'percent'; } display = formatNumber(value / 100, locale, opts); } else if ((0, _componentHelper.isTrue)(currency) || typeof currency === 'string') { type = 'currency'; opts.currency = opts.currency || ((0, _componentHelper.isTrue)(currency) ? _defaults.CURRENCY : currency); handleCompactBeforeDisplay({ value, locale, compact, decimals, opts }); if (decimals === null) { decimals = 2; value = formatDecimals(value, decimals, rounding !== null && rounding !== void 0 ? rounding : omit_rounding, opts); } const cleanedNumber = decimals >= 0 ? value : clean ? cleanNumber(value) : value; if (currency_display === false || currency_display === '') { omit_currency_sign = true; } opts.style = 'currency'; opts.currencyDisplay = getFallbackCurrencyDisplay(locale, opts.currencyDisplay || currency_display); if (typeof opts.minimumFractionDigits === 'undefined' && String(value).indexOf('.') === -1 && cleanedNumber % 1 === 0) { opts.minimumFractionDigits = 0; } let formatter = undefined; if ((0, _componentHelper.isTrue)(omit_currency_sign)) { formatter = item => { switch (item.type) { case 'literal': item.value = item.value === ' ' ? '' : item.value; return item; case 'currency': item.value = ''; return item; default: return item; } }; } if (!currency_position && locale && /(no|nb|nn)$/i.test(locale)) { currency_position = 'after'; } let currencySuffix = null; if (currency_position) { formatter = currencyPositionFormatter(formatter, ({ value }) => { return currencySuffix = alignCurrencySymbol(value.trim(), currency_display); }, currency_position); } display = formatNumber(cleanedNumber, locale, opts, formatter); display = prepareMinus(display, locale); if (currency_position && currencySuffix) { if (currency_position === 'after') { display = `${display.trim()} ${currencySuffix}`; } else if (currency_position === 'before') { display = `${currencySuffix} ${display.trim()}`; } } handleCompactBeforeAria({ value, compact, opts }); aria = formatNumber(cleanedNumber, locale, { minimumFractionDigits: 0, maximumFractionDigits: 2, ...opts, currencyDisplay: 'name' }); aria = enhanceSR(cleanedNumber, aria, locale); } else { handleCompactBeforeDisplay({ value, locale, compact, decimals, opts }); display = formatNumber(value, locale, opts); display = prepareMinus(display, locale); handleCompactBeforeAria({ value, compact, opts }); aria = formatNumber(value, locale, opts); aria = enhanceSR(value, aria, locale); } if (aria === null) { aria = display; } if (returnAria) { let cleanedValue; if (clean_copy_value) { cleanedValue = formatNumber(opts.style === 'percent' ? value / 100 : value, locale, opts, item => { switch (item.type) { case 'group': case 'literal': case 'currency': case 'percentSign': item.value = ''; return item; default: return item; } }); } else { const thousandsSeparator = getThousandsSeparator(locale); cleanedValue = String(display).replace(new RegExp(`${thousandsSeparator}(?=\\d{3})`, 'g'), ''); } if (value === 'invalid') { var _locales$locale; aria = invalidAriaText || ((_locales$locale = _index.default[locale]) === null || _locales$locale === void 0 ? void 0 : _locales$locale.NumberFormat.not_available) || 'N/A'; } return { value, cleanedValue, number: display, aria, locale, type }; } return display; }; exports.format = format; const formatDecimals = (value, decimals, rounding, opts = {}) => { decimals = parseFloat(decimals); if (decimals >= 0) { opts.minimumFractionDigits = decimals; opts.maximumFractionDigits = decimals; } if (String(value).includes('.')) { const decimalPlaces = decimals || opts.maximumFractionDigits; if (rounding === 'omit' || rounding === true) { const factor = Math.pow(10, decimalPlaces); value = Math.trunc(value * factor) / factor; } else { switch (rounding) { case 'half-even': { value = roundHalfEven(value, decimalPlaces); break; } } } } return value; }; exports.formatDecimals = formatDecimals; const countDecimals = (value, decimalSeparator = '.') => { var _String$split$; if (typeof value === 'number' && Math.floor(value.valueOf()) === value.valueOf()) { return 0; } return ((_String$split$ = String(value).split(decimalSeparator)[1]) === null || _String$split$ === void 0 ? void 0 : _String$split$.length) || 0; }; exports.countDecimals = countDecimals; const currencyPositionFormatter = (existingFormatter, callback, position = null) => { let count = 0; let countCurrency = -1; return item => { if (typeof existingFormatter === 'function') { item = existingFormatter(item); } count++; switch (item.type) { case 'currency': { if (position === 'after' || position === 'before' && count > 2) { countCurrency = count; callback(item); item.value = ''; } return item; } case 'literal': { if (count === countCurrency + 1) { item.value = ''; } return item; } default: return item; } }; }; const prepareMinus = (display, locale) => { if (!(locale && /(no|nb|nn)$/i.test(locale))) { return display; } const first = display[0]; const second = display[1]; if (first === '-' && second === '-') { return display; } const reg = `^(${NUMBER_MINUS})`; if (new RegExp(reg).test(first)) { if (parseFloat(second) > 0) { display = display.replace(new RegExp(reg + '(.*)'), '-$2'); } else { display = display.replace(new RegExp(reg + '([^0-9]+)(.*)'), '$2-$3'); } } return display; }; function alignCurrencySymbol(output, currencyDisplay) { if (typeof output === 'string' && currencyDisplay === 'name') { output = output.replace(/(nor[^\s]+?)\s(\w+)/i, '$2'); } return output; } const enhanceSR = (value, aria, locale) => { if (_helpers.IS_MAC && Math.abs(parseFloat(value)) <= 99999) { aria = String(aria).replace(/\s([0-9])/g, '$1'); } aria = prepareMinus(aria, locale); return aria; }; const formatNumber = (number, locale, options = {}, formatter = null) => { try { if (options.currencyDisplay) { options.currencyDisplay = getFallbackCurrencyDisplay(locale, options.currencyDisplay); } delete options.decimals; if (formatter) { number = formatToParts({ number, locale, options }).map(formatter).reduce((acc, { value }) => acc + value, ''); } else if (typeof Number !== 'undefined' && typeof Number.toLocaleString === 'function') { number = parseFloat(number).toLocaleString(locale, options); } if (new RegExp(`^(${NUMBER_MINUS})(0|0[^\\d]|0\\s.*)$`).test(number)) { number = number.replace(new RegExp(`(${NUMBER_MINUS})0`), '0'); } } catch (e) { (0, _componentHelper.warn)(`Number could not be formatted: ${JSON.stringify([number, locale, options])}`, e); } return replaceNaNWithDash(alignCurrencySymbol(number, options.currencyDisplay)); }; exports.formatNumber = formatNumber; function replaceNaNWithDash(number) { const string = String(number); const replaced = string.replace(/NaN/, ABSENT_VALUE_FORMAT); if (!/NaN/.test(string)) { return replaced; } const escapedDash = (0, _componentHelper.escapeRegexChars)(ABSENT_VALUE_FORMAT); return replaced.replace(new RegExp(`([^\\s])${escapedDash}`, 'g'), `$1 ${ABSENT_VALUE_FORMAT}`); } function isAbsent(value) { return value === null || value === undefined || value === '' || value === ABSENT_VALUE_FORMAT; } const formatPhone = (number, locale = null) => { if (isAbsent(number)) { return { number: ABSENT_VALUE_FORMAT, aria: ABSENT_VALUE_FORMAT }; } let display = number; let aria = null; switch (locale) { default: { let code = ''; number = String(number).replace(/^(00|\+|)47([^\s])/, '+47 $2').replace(/^00/, '+'); if (number.substring(0, 1) === '+') { const codeAndNumber = number.match(/^\+([\d-]{1,8})\s{0,2}([\d\s-]{1,20})$/); if (codeAndNumber) { code = `+${codeAndNumber[1]} `; number = codeAndNumber[2]; } } number = number.replace(/[^+\d]/g, ''); const length = number.length; if (length === 8 && number.substring(0, 1) === '8') { display = code + number.split(/([\d]{3})([\d]{2})/).filter(s => s).join(' '); } else { if (length < 6) { display = code + number; } else { if (code.includes('-')) { code = code.replace(/(\+[\d]{1,2})-([\d]{1,6})/, '$1 ($2)'); } display = code + number.split(length === 6 ? /^(\+[\d]{2})|([\d]{3})/ : /^(\+[\d]{2})|([\d]{2})/).filter(s => s).join(' '); } } aria = code + number.split(/([\d]{2})/).filter(s => s).join(' '); } } if (aria === null) { aria = display; } return { number: display, aria }; }; exports.formatPhone = formatPhone; const formatBAN = (number, locale = null) => { if (isAbsent(number)) { return { number: ABSENT_VALUE_FORMAT, aria: ABSENT_VALUE_FORMAT }; } number = String(number).replace(/[^0-9]/g, ''); let display = number; let aria = null; switch (locale) { default: { display = number.split(/([0-9]{4})([0-9]{2})([0-9]{1,})/).filter(s => s).join(' '); aria = number.split(/([0-9]{2})/).filter(s => s).join(' '); } } if (aria === null) { aria = display; } return { number: display, aria }; }; exports.formatBAN = formatBAN; const formatORG = (number, locale = null) => { if (isAbsent(number)) { return { number: ABSENT_VALUE_FORMAT, aria: ABSENT_VALUE_FORMAT }; } number = String(number).replace(/[^0-9]/g, ''); let display = number; let aria = null; switch (locale) { default: { display = number.split(/([0-9]{3})/).filter(s => s).join(' '); aria = number.split(/([0-9]{1})/).filter(s => s).join(' '); } } if (aria === null) { aria = display; } return { number: display, aria }; }; exports.formatORG = formatORG; const formatNIN = (number, locale = null) => { if (isAbsent(number)) { return { number: ABSENT_VALUE_FORMAT, aria: ABSENT_VALUE_FORMAT }; } number = String(number).replace(/[^0-9]/g, ''); let display = number; let aria = null; switch (locale) { default: { display = number.split(/([0-9]{6})/).filter(s => s).join(' '); aria = display.split(/([0-9]{2})([0-9]{2})([0-9]{2}) ([0-9]{1})([0-9]{1})([0-9]{1})([0-9]{1})([0-9]{1})/).filter(s => s).join(_helpers.IS_WIN ? '. ' : ' '); } } if (aria === null) { aria = display; } return { number: display, aria }; }; exports.formatNIN = formatNIN; function cleanNumber(num, { decimalSeparator = null, thousandsSeparator = null, prefix = null, suffix = null } = {}) { if (typeof num === 'number' || typeof num === 'undefined' || num === null) { return num; } num = String(num).trim(); if (typeof prefix === 'string' && num.startsWith(prefix)) { num = num.substring(prefix.length, num.length); } if (typeof suffix === 'string' && num.endsWith(suffix)) { num = num.substring(0, num.length - suffix.length); } if (/^[^0-9-]/.test(num)) { num = num.replace(/^(^[^0-9-]+)/, ''); } let decimal = decimalSeparator; let thousands = thousandsSeparator; if (/(\s)([0-9]{3})/.test(num)) { thousands = thousands || '\\s'; decimal = decimal || ','; } else if (/(\.)([0-9]{3})/.test(num) && !/([,'][0-9]{3})(\.)([0-9]{3})/.test(num)) { thousands = thousands || '\\.'; decimal = decimal || ",|·|'"; } else if (/(,)([0-9]{3})/.test(num)) { thousands = thousands || ','; decimal = decimal || '\\.|·'; } else if (/(')([0-9]{3})/.test(num)) { thousands = thousands || "'"; decimal = decimal || '\\.|,'; } else { thousands = ','; decimal = '\\.'; } const thousandReg = thousandsSeparator ? new RegExp(`([0-9]|)(${(0, _componentHelper.escapeRegexChars)(thousandsSeparator)})([0-9]{3})`, 'g') : new RegExp(`([0-9]|)(${thousands})([0-9]{3})`, 'g'); if (thousandReg.test(num)) { num = num.replace(thousandReg, '$1$3'); } const decimalReg = decimalSeparator ? new RegExp(`(${(0, _componentHelper.escapeRegexChars)(decimalSeparator)})([0-9]{0,})`, 'g') : new RegExp(`(${decimal})([0-9]{1,2})`, 'g'); if (decimalReg.test(num)) { num = num.replace(decimalReg, '.$2'); } if (!decimalSeparator) { const decimalBackup = new RegExp(`(${decimal})([0-9]{3,})`, 'g'); if (decimalBackup.test(num)) { num = num.replace(decimalBackup, '.$2'); } } return num.replace(new RegExp(`([^${NUMBER_CHARS}])`, 'g'), ''); } function runIOSSelectionFix() { try { const selection = window.getSelection(); const range = document.createRange(); selection.removeAllRanges(); selection.addRange(range); } catch (e) {} } function getFallbackCurrencyDisplay(locale = null, currencyDisplay = null) { if (!currencyDisplay && (!locale || /(no|nb|nn)$/i.test(locale))) { currencyDisplay = _defaults.CURRENCY_DISPLAY; } return currencyDisplay || _defaults.CURRENCY_FALLBACK_DISPLAY; } function getDecimalSeparator(locale = null) { var _formatToParts$find; const separator = ((_formatToParts$find = formatToParts({ number: 1.1, locale }).find(({ type }) => type === 'decimal')) === null || _formatToParts$find === void 0 ? void 0 : _formatToParts$find.value) || ','; return separator; } function getThousandsSeparator(locale = null) { var _formatToParts$find2; return ((_formatToParts$find2 = formatToParts({ number: 1000, locale }).find(({ type }) => type === 'group')) === null || _formatToParts$find2 === void 0 ? void 0 : _formatToParts$find2.value) || ' '; } function getCurrencySymbol(locale = null, currency = null, display = null, number = 2) { var _formatToParts$find3; if (!currency) { currency = _defaults.CURRENCY; } const currencyDisplay = getFallbackCurrencyDisplay(locale, display); return alignCurrencySymbol(((_formatToParts$find3 = formatToParts({ number, locale, options: { style: 'currency', currency, currencyDisplay } }).find(({ type }) => type === 'currency')) === null || _formatToParts$find3 === void 0 ? void 0 : _formatToParts$find3.value) || currency, currencyDisplay); } function formatToParts({ number, locale = null, options = null }) { if (typeof Intl !== 'undefined' && typeof Intl.NumberFormat === 'function') { try { const inst = Intl.NumberFormat(locale || _defaults.LOCALE, options || {}); if (typeof inst.formatToParts === 'function') { return inst.formatToParts(number); } else { return [{ value: inst.format(number) }]; } } catch (e) { (0, _componentHelper.warn)(e); } } return [{ value: number }]; } function handleCompactBeforeDisplay({ value, locale, compact, decimals = 0, opts } = {}) { if (!canHandleCompact({ value, compact })) { return; } value = parseInt(Math.abs(value)); opts.notation = 'compact'; if ((0, _componentHelper.isTrue)(compact) && locale && /(no|nb|nn)$/i.test(locale)) { opts.compactDisplay = Math.abs(value) < 1000000 ? 'long' : 'short'; } else { opts.compactDisplay = !(0, _componentHelper.isTrue)(compact) ? compact : 'short'; } if (typeof opts.maximumSignificantDigits === 'undefined') { if (isNaN(parseFloat(decimals))) { decimals = 0; } else { decimals = parseFloat(decimals); } const ref = String(value).length % 3; if (ref === 2) { decimals += 1; } else if (ref === 0) { decimals += 2; } opts.maximumSignificantDigits = decimals + 1; } } function handleCompactBeforeAria({ value, compact, opts }) { if (!canHandleCompact({ value, compact })) { return; } opts.compactDisplay = 'long'; } function canHandleCompact({ value, compact }) { if (compact && Math.abs(value) >= 1000) { return true; } return false; } function roundHalfEven(num, decimalPlaces = 2) { const multiplier = Math.pow(10, decimalPlaces); const adjustedNum = num * multiplier; const floored = Math.floor(adjustedNum); const diff = adjustedNum - floored; if (diff > 0.5) { return Math.ceil(adjustedNum) / multiplier; } else if (diff < 0.5) { return Math.floor(adjustedNum) / multiplier; } return floored % 2 === 0 ? floored / multiplier : Math.ceil(adjustedNum) / multiplier; } //# sourceMappingURL=NumberUtils.js.map