import { formatAmount } from '@transferwise/formatting'; import type { KeyboardEvent } from 'react'; export const getDecimalSeparator = (currency: string, locale: string): string | null => { return formatAmount(1.1, currency, locale).replace(/\p{Number}/gu, ''); }; export const getGroupSeparator = (currency: string, locale: string): string | null => { return formatAmount(10000000, currency, locale).replace(/\p{Number}/gu, '')[0]; }; export const getDecimalCount = (currency: string, locale: string): number => { const decimalSeparator = getDecimalSeparator(currency, locale); if (!decimalSeparator) { return 0; } const parts = formatAmount(1.1, currency, locale).split(decimalSeparator); return parts.length === 2 ? parts[1].length : 0; }; export const getEnteredDecimalsCount = (value: string, decimalSeparator: string) => { return value.split(decimalSeparator)[1]?.length ?? 0; }; export const getUnformattedNumber = ({ value: formattedValue, currency, locale, }: { value: string; currency: string; locale: string; }): number | null => { const groupSeparator = getGroupSeparator(currency, locale); const decimalSeparator = getDecimalSeparator(currency, locale); if (!formattedValue) { return null; } // parseFloat can't handle thousands separators const withoutGroupSeparator = groupSeparator ? formattedValue.replace(/ /gu, '').replace(new RegExp(`\\${groupSeparator}`, 'g'), '') : formattedValue; // parseFloat can only handle . as decimal separator const withNormalisedDecimalSeparator = decimalSeparator ? withoutGroupSeparator.replace(decimalSeparator, '.') : withoutGroupSeparator; const parsedValue = Number.parseFloat(withNormalisedDecimalSeparator); return parsedValue; }; export const getFormattedString = ({ value: unformattedValue, currency, locale, alwaysShowDecimals = false, }: { value: number; currency: string; locale: string; alwaysShowDecimals?: boolean; }): string => { const decimalSeparator = getDecimalSeparator(currency, locale); // formatAmount rounds extra decimals, so 1.999 will become 2. Instead we will manually strip extra decimals so that it becomes 1.99 after formatting const decimalCount = getDecimalCount(currency, locale); const unformattedString = unformattedValue.toString(); const [integerPart, decimalPart] = decimalSeparator ? unformattedString.split(decimalSeparator) : [unformattedString, undefined]; const formattedDecimalPart = decimalPart ? decimalPart.slice(0, decimalCount) : ''; const sanitisedUnformattedValue = Number.parseFloat(`${integerPart}.${formattedDecimalPart}`); return formatAmount(sanitisedUnformattedValue, currency, locale, { alwaysShowDecimals, }); }; export const isInputPossiblyOverflowing = ({ ref, value, }: { ref: React.RefObject; value: string; }) => { const textLength = value.length; const inputWidth = ref.current?.clientWidth; if (!inputWidth || !textLength) { return; } const maxCharactersWithoutOverflow = Math.floor(inputWidth / 19); return textLength > maxCharactersWithoutOverflow; }; const allowedInputKeys = new Set([ 'Backspace', 'Delete', ',', '.', 'ArrowLeft', 'ArrowRight', 'Enter', 'Tab', ]); export const isAllowedInputKey = (e: KeyboardEvent) => { const { metaKey, key, ctrlKey } = e; const isNumberKey = !Number.isNaN(Number.parseInt(key, 10)); return isNumberKey || metaKey || ctrlKey || allowedInputKeys.has(key); };