@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
696 lines (695 loc) • 21.3 kB
JavaScript
;
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