@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
794 lines (793 loc) • 25.2 kB
JavaScript
"use client";
import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
import "core-js/modules/es.string.replace.js";
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
import React from 'react';
import Context from '../../shared/Context';
import { LOCALE, CURRENCY, CURRENCY_DISPLAY } from '../../shared/defaults';
import { warn, isTrue, slugify, escapeRegexChars } from '../../shared/component-helper';
import { getOffsetTop, getOffsetLeft, getSelectedElement, copyToClipboard, IS_MAC, IS_WIN } from '../../shared/helpers';
import locales from '../../shared/locales';
const NUMBER_CHARS = '\\-0-9,.';
export const format = function (value) {
let {
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,
options = null,
returnAria = false,
invalidAriaText = null
} = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
let display = value;
let aria = null;
let type = 'number';
if (!locale) {
locale = LOCALE;
} else if (locale === 'auto') {
try {
locale = window.navigator.language;
} catch (e) {
warn(e);
}
}
if (isTrue(clean)) {
value = cleanNumber(value);
}
const opts = (typeof options === 'string' && options[0] === '{' ? JSON.parse(options) : options) || {};
if (parseFloat(decimals) >= 0) {
value = formatDecimals(value, decimals, rounding !== null && rounding !== void 0 ? rounding : omit_rounding, opts);
} else if (typeof opts.maximumFractionDigits === 'undefined' && !isTrue(percent) && !isTrue(currency)) {
opts.maximumFractionDigits = 20;
}
if (isTrue(phone)) {
type = 'phone';
const {
number: _number,
aria: _aria
} = formatPhone(value, locale);
if (clean === null) {
value = cleanNumber(value);
}
display = _number;
aria = _aria;
} else if (isTrue(ban)) {
type = 'ban';
const {
number: _number,
aria: _aria
} = formatBAN(value, locale);
display = _number;
aria = _aria;
} else if (isTrue(nin)) {
type = 'nin';
const {
number: _number,
aria: _aria
} = formatNIN(value, locale);
display = _number;
aria = _aria;
} else if (isTrue(org)) {
type = 'org';
const {
number: _number,
aria: _aria
} = formatORG(value, locale);
display = _number;
aria = _aria;
} else if (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 (isTrue(currency) || typeof currency === 'string') {
type = 'currency';
opts.currency = opts.currency || (isTrue(currency) ? 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);
}
let 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 (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, _ref => {
let {
value
} = _ref;
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, _objectSpread(_objectSpread({
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 = locales[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;
};
export const formatDecimals = function (value, decimals, rounding) {
let opts = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
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;
};
export const countDecimals = function (value) {
var _String$split$;
let decimalSeparator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '.';
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;
};
const currencyPositionFormatter = function (existingFormatter, callback) {
let position = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 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 = '^(-|−|‐|‒|–|—|―)';
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 (IS_MAC && Math.abs(parseFloat(value)) <= 99999) {
aria = String(aria).replace(/\s([0-9])/g, '$1');
}
aria = prepareMinus(aria, locale);
return aria;
};
export const formatNumber = function (number, locale) {
let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
let formatter = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
try {
if (options.currencyDisplay) {
options.currencyDisplay = getFallbackCurrencyDisplay(locale, options.currencyDisplay);
}
delete options.decimals;
if (locale && /(en|gb)$/i.test(locale)) {
formatter = getGroupFormatter(locale, null, formatter);
}
if (formatter) {
number = formatToParts({
number,
locale,
options
}).map(formatter).reduce((acc, _ref2) => {
let {
value
} = _ref2;
return acc + value;
}, '');
} else if (typeof Number !== 'undefined' && typeof Number.toLocaleString === 'function') {
number = parseFloat(number).toLocaleString(locale, options);
}
if (String(number).startsWith('−0')) {
number = number.replace('−0', '0');
}
} catch (e) {
warn(`Number could not be formatted: ${JSON.stringify([number, locale, options])}`, e);
}
return replaceNaNWithDash(alignCurrencySymbol(number, options.currencyDisplay));
};
function replaceNaNWithDash(number) {
return String(number).replace(/NaN/, '–');
}
export const formatPhone = function (number) {
let locale = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
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
};
};
export const formatBAN = function (number) {
let locale = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
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
};
};
export const formatORG = function (number) {
let locale = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
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
};
};
export const formatNIN = function (number) {
let locale = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
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(IS_WIN ? '. ' : ' ');
}
}
if (aria === null) {
aria = display;
}
return {
number: display,
aria
};
};
export function cleanNumber(num) {
let {
decimalSeparator = null,
thousandsSeparator = null,
prefix = null,
suffix = null
} = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
if (typeof num === 'number') {
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]|)(${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(`(${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'), '');
}
export function runIOSSelectionFix() {
try {
const selection = window.getSelection();
const range = document.createRange();
selection.removeAllRanges();
selection.addRange(range);
} catch (e) {}
}
export async function copyWithEffect(value, label) {
let positionElement = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
let success = null;
if (value) {
try {
const fx = showSelectionNotice({
value,
label
});
success = await copyToClipboard(value);
if (success === true) {
fx.run(positionElement);
}
} catch (e) {
warn(e);
success = e;
}
}
return success;
}
export function showSelectionNotice(_ref3) {
let {
value,
label,
timeout = 3e3
} = _ref3;
const id = 'id-' + slugify(value);
if (typeof document !== 'undefined' && document.getElementById(id)) {
return {
run: () => {}
};
}
let elem, content, root;
try {
root = document.querySelector('.dnb-tooltip__portal, body');
elem = document.createElement('span');
elem.setAttribute('id', id);
elem.setAttribute('class', 'dnb-tooltip');
elem.setAttribute('role', 'tooltip');
const arrow = document.createElement('span');
arrow.setAttribute('class', 'dnb-tooltip__arrow dnb-tooltip__arrow__position--top');
content = document.createElement('span');
content.setAttribute('class', 'dnb-tooltip__content');
content.setAttribute('aria-live', 'assertive');
elem.appendChild(arrow);
elem.appendChild(content);
} catch (e) {
warn(e);
}
return new class SelectionFx {
remove() {
try {
root.removeChild(elem);
elem = null;
content = null;
} catch (e) {}
}
hide() {
try {
elem.classList.add('dnb-tooltip--hide');
} catch (e) {}
}
run() {
let pE = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getSelectedElement();
try {
root.appendChild(elem);
const top = getOffsetTop(pE);
const left = getOffsetLeft(pE);
content.innerHTML = String(label) + (pE instanceof HTMLElement ? `<span class="dnb-sr-only">: ${(pE && pE.querySelector('.dnb-number-format__selection') || pE).innerHTML}</span>` : '');
const width = (pE && pE.querySelector('.dnb-number-format__visible') || pE || {}).offsetWidth || 0;
elem.style.top = `${top - elem.offsetHeight}px`;
elem.style.left = `${left + width / 2 - elem.offsetWidth / 2}px`;
elem.classList.add('dnb-tooltip--active');
setTimeout(this.hide, timeout);
setTimeout(this.remove, timeout + 600);
} catch (e) {
warn(e);
}
}
}();
}
export function useCopyWithNotice() {
const {
translation: {
NumberFormat: {
clipboard_copy
}
}
} = React.useContext(Context);
const copy = (value, positionElement) => {
copyWithEffect(value, clipboard_copy, positionElement);
};
return {
copy
};
}
export function getFallbackCurrencyDisplay() {
let locale = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
let currencyDisplay = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
if (!currencyDisplay && (!locale || /(no|nb|nn)$/i.test(locale))) {
currencyDisplay = 'narrowSymbol';
}
return currencyDisplay || CURRENCY_DISPLAY;
}
export function getDecimalSeparator() {
var _formatToParts$find;
let locale = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
const separator = ((_formatToParts$find = formatToParts({
number: 1.1,
locale
}).find(_ref4 => {
let {
type
} = _ref4;
return type === 'decimal';
})) === null || _formatToParts$find === void 0 ? void 0 : _formatToParts$find.value) || ',';
return separator;
}
export function getThousandsSeparator() {
var _formatToParts$map$fi;
let locale = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
const formatter = getGroupFormatter(locale);
return ((_formatToParts$map$fi = formatToParts({
number: 1000,
locale
}).map(formatter).find(_ref5 => {
let {
type
} = _ref5;
return type === 'group';
})) === null || _formatToParts$map$fi === void 0 ? void 0 : _formatToParts$map$fi.value) || ' ';
}
export function getCurrencySymbol() {
var _formatToParts$find2;
let locale = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
let currency = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
let display = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
let number = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 2;
if (!currency) {
currency = CURRENCY;
}
const currencyDisplay = getFallbackCurrencyDisplay(locale, display);
return alignCurrencySymbol(((_formatToParts$find2 = formatToParts({
number,
locale,
options: {
style: 'currency',
currency,
currencyDisplay
}
}).find(_ref6 => {
let {
type
} = _ref6;
return type === 'currency';
})) === null || _formatToParts$find2 === void 0 ? void 0 : _formatToParts$find2.value) || currency, currencyDisplay);
}
function getGroupFormatter() {
let locale = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
let separatorSymbol = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
let existingFormatter = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
if (locale && /(en|gb)$/i.test(locale)) {
separatorSymbol = ' ';
}
return item => {
if (typeof existingFormatter === 'function') {
item = existingFormatter(item);
}
if (item.type === 'group') {
item.value = separatorSymbol || item.value;
}
return item;
};
}
function formatToParts(_ref7) {
let {
number,
locale = null,
options = null
} = _ref7;
if (typeof Intl !== 'undefined' && typeof Intl.NumberFormat === 'function') {
try {
const inst = Intl.NumberFormat(locale || LOCALE, options || {});
if (typeof inst.formatToParts === 'function') {
return inst.formatToParts(number);
} else {
return [{
value: inst.format(number)
}];
}
} catch (e) {
warn(e);
}
}
return [{
value: number
}];
}
function handleCompactBeforeDisplay() {
let {
value,
locale,
compact,
decimals = 0,
opts
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
if (!canHandleCompact({
value,
compact
})) {
return;
}
value = parseInt(Math.abs(value));
opts.notation = 'compact';
if (isTrue(compact) && locale && /(no|nb|nn)$/i.test(locale)) {
opts.compactDisplay = Math.abs(value) < 1000000 ? 'long' : 'short';
} else {
opts.compactDisplay = !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(_ref8) {
let {
value,
compact,
opts
} = _ref8;
if (!canHandleCompact({
value,
compact
})) {
return;
}
opts.compactDisplay = 'long';
}
function canHandleCompact(_ref9) {
let {
value,
compact
} = _ref9;
if (compact && Math.abs(value) >= 1000) {
return true;
}
return false;
}
export function roundHalfEven(num) {
let decimalPlaces = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 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