You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
222 lines
5.2 KiB
222 lines
5.2 KiB
import { IntlConfig } from '../CurrencyInputProps';
|
|
import { escapeRegExp } from './escapeRegExp';
|
|
import { getSuffix } from './getSuffix';
|
|
|
|
export type FormatValueOptions = {
|
|
/**
|
|
* Value to format
|
|
*/
|
|
value: string | undefined;
|
|
|
|
/**
|
|
* Decimal separator
|
|
*
|
|
* Default = '.'
|
|
*/
|
|
decimalSeparator?: string;
|
|
|
|
/**
|
|
* Group separator
|
|
*
|
|
* Default = ','
|
|
*/
|
|
groupSeparator?: string;
|
|
|
|
/**
|
|
* Turn off separators
|
|
*
|
|
* This will override Group separators
|
|
*
|
|
* Default = false
|
|
*/
|
|
disableGroupSeparators?: boolean;
|
|
|
|
/**
|
|
* Intl locale currency config
|
|
*/
|
|
intlConfig?: IntlConfig;
|
|
|
|
/**
|
|
* Specify decimal scale for padding/trimming
|
|
*
|
|
* Eg. 1.5 -> 1.50 or 1.234 -> 1.23
|
|
*/
|
|
decimalScale?: number;
|
|
|
|
/**
|
|
* Prefix
|
|
*/
|
|
prefix?: string;
|
|
|
|
/**
|
|
* Suffix
|
|
*/
|
|
suffix?: string;
|
|
};
|
|
|
|
/**
|
|
* Format value with decimal separator, group separator and prefix
|
|
*/
|
|
export const formatValue = (options: FormatValueOptions): string => {
|
|
const {
|
|
value: _value,
|
|
decimalSeparator,
|
|
intlConfig,
|
|
decimalScale,
|
|
prefix = '',
|
|
suffix = '',
|
|
} = options;
|
|
|
|
if (_value === '' || _value === undefined) {
|
|
return '';
|
|
}
|
|
|
|
if (_value === '-') {
|
|
return '-';
|
|
}
|
|
|
|
const isNegative = new RegExp(`^\\d?-${prefix ? `${escapeRegExp(prefix)}?` : ''}\\d`).test(
|
|
_value
|
|
);
|
|
|
|
const value =
|
|
decimalSeparator !== '.'
|
|
? replaceDecimalSeparator(_value, decimalSeparator, isNegative)
|
|
: _value;
|
|
|
|
const defaultNumberFormatOptions = {
|
|
minimumFractionDigits: decimalScale || 0,
|
|
maximumFractionDigits: 20,
|
|
};
|
|
|
|
const numberFormatter = intlConfig
|
|
? new Intl.NumberFormat(
|
|
intlConfig.locale,
|
|
intlConfig.currency
|
|
? {
|
|
...defaultNumberFormatOptions,
|
|
style: 'currency',
|
|
currency: intlConfig.currency,
|
|
}
|
|
: defaultNumberFormatOptions
|
|
)
|
|
: new Intl.NumberFormat(undefined, defaultNumberFormatOptions);
|
|
|
|
const parts = numberFormatter.formatToParts(Number(value));
|
|
|
|
let formatted = replaceParts(parts, options);
|
|
|
|
// Does intl formatting add a suffix?
|
|
const intlSuffix = getSuffix(formatted, { ...options });
|
|
|
|
// Include decimal separator if user input ends with decimal separator
|
|
const includeDecimalSeparator = _value.slice(-1) === decimalSeparator ? decimalSeparator : '';
|
|
|
|
const [, decimals] = value.match(RegExp('\\d+\\.(\\d+)')) || [];
|
|
|
|
// Keep original decimal padding if no decimalScale
|
|
if (decimalScale === undefined && decimals && decimalSeparator) {
|
|
if (formatted.includes(decimalSeparator)) {
|
|
formatted = formatted.replace(
|
|
RegExp(`(\\d+)(${escapeRegExp(decimalSeparator)})(\\d+)`, 'g'),
|
|
`$1$2${decimals}`
|
|
);
|
|
} else {
|
|
if (intlSuffix && !suffix) {
|
|
formatted = formatted.replace(intlSuffix, `${decimalSeparator}${decimals}${intlSuffix}`);
|
|
} else {
|
|
formatted = `${formatted}${decimalSeparator}${decimals}`;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (suffix && includeDecimalSeparator) {
|
|
return `${formatted}${includeDecimalSeparator}${suffix}`;
|
|
}
|
|
|
|
if (intlSuffix && includeDecimalSeparator) {
|
|
return formatted.replace(intlSuffix, `${includeDecimalSeparator}${intlSuffix}`);
|
|
}
|
|
|
|
if (intlSuffix && suffix) {
|
|
return formatted.replace(intlSuffix, `${includeDecimalSeparator}${suffix}`);
|
|
}
|
|
|
|
return [formatted, includeDecimalSeparator, suffix].join('');
|
|
};
|
|
|
|
/**
|
|
* Before converting to Number, decimal separator has to be .
|
|
*/
|
|
const replaceDecimalSeparator = (
|
|
value: string,
|
|
decimalSeparator: FormatValueOptions['decimalSeparator'],
|
|
isNegative: boolean
|
|
): string => {
|
|
let newValue = value;
|
|
if (decimalSeparator && decimalSeparator !== '.') {
|
|
newValue = newValue.replace(RegExp(escapeRegExp(decimalSeparator), 'g'), '.');
|
|
if (isNegative && decimalSeparator === '-') {
|
|
newValue = `-${newValue.slice(1)}`;
|
|
}
|
|
}
|
|
return newValue;
|
|
};
|
|
|
|
const replaceParts = (
|
|
parts: Intl.NumberFormatPart[],
|
|
{
|
|
prefix,
|
|
groupSeparator,
|
|
decimalSeparator,
|
|
decimalScale,
|
|
disableGroupSeparators = false,
|
|
}: Pick<
|
|
FormatValueOptions,
|
|
'prefix' | 'groupSeparator' | 'decimalSeparator' | 'decimalScale' | 'disableGroupSeparators'
|
|
>
|
|
): string => {
|
|
return parts
|
|
.reduce(
|
|
(prev, { type, value }, i) => {
|
|
if (i === 0 && prefix) {
|
|
if (type === 'minusSign') {
|
|
return [value, prefix];
|
|
}
|
|
|
|
if (type === 'currency') {
|
|
return [...prev, prefix];
|
|
}
|
|
|
|
return [prefix, value];
|
|
}
|
|
|
|
if (type === 'currency') {
|
|
return prefix ? prev : [...prev, value];
|
|
}
|
|
|
|
if (type === 'group') {
|
|
return !disableGroupSeparators
|
|
? [...prev, groupSeparator !== undefined ? groupSeparator : value]
|
|
: prev;
|
|
}
|
|
|
|
if (type === 'decimal') {
|
|
if (decimalScale !== undefined && decimalScale === 0) {
|
|
return prev;
|
|
}
|
|
|
|
return [...prev, decimalSeparator !== undefined ? decimalSeparator : value];
|
|
}
|
|
|
|
if (type === 'fraction') {
|
|
return [...prev, decimalScale !== undefined ? value.slice(0, decimalScale) : value];
|
|
}
|
|
|
|
return [...prev, value];
|
|
},
|
|
['']
|
|
)
|
|
.join('');
|
|
};
|