UNPKG

headless-currency-input

Version:

Headless Currency Input is a component to format currency input in an elegant way.

1 lines 9.52 kB
{"version":3,"sources":["../src/utils.ts","../src/currency-input.tsx"],"names":["setCaretPosition","el","caretPos","RenderCurrencyInput","locale","currency","withCurrencySymbol","props","forwadedRef","innerRef","useRef","currencyFormat","resolveCurrencyFormat","prefix","minimumFractionDigits","maximumFractionDigits","divideBy","format","inputValue","_a","_b","value","amount","onValueChange","values","sourceInfo","val","floatVal","onFocus","event","positionLastDigit","onInput","jsx","NumberFormatBase","mergeRefs","CurrencyInput","forwardRef"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * @see https://github.com/i18n-components/i18n-components/blob/main/packages/input-number/src/helpers.ts#L2\n */\nexport function setCaretPosition(el: HTMLInputElement, caretPos = 0): boolean {\n\t// biome-ignore lint/correctness/noSelfAssign: comes from the reference\n\tel.value = el.value;\n\t// ^ this is used to not only get \"focus\", but\n\t// to make sure we don't have it everything -selected-\n\t// (it causes an issue in chrome, and having it doesn't hurt any other browser)\n\n\tif (el !== null) {\n\t\tif (el.selectionStart || el.selectionStart === 0) {\n\t\t\tel.focus();\n\t\t\tel.setSelectionRange(caretPos, caretPos);\n\t\t\treturn true;\n\t\t}\n\n\t\t// fail city, fortunately this never happens (as far as I've tested) :)\n\t\tel.focus();\n\t\treturn false;\n\t}\n\n\treturn false;\n}\n","\"use client\";\n\nimport { mergeRefs } from \"@react-aria/utils\";\nimport { resolveCurrencyFormat } from \"@sumup/intl\";\nimport { type FocusEvent, type FormEvent, type ForwardedRef, forwardRef, useRef } from \"react\";\nimport {\n\ttype InputAttributes,\n\tNumberFormatBase,\n\ttype NumberFormatBaseProps,\n\ttype NumberFormatValues,\n\ttype SourceInfo,\n} from \"react-number-format\";\n\nimport { setCaretPosition } from \"./utils\";\n\ntype CurrencyInputProps<BaseType = InputAttributes> = Omit<\n\tNumberFormatBaseProps<BaseType>,\n\t\"format\" | \"prefix\" | \"customInput\"\n> & {\n\tlocale?: string;\n\tcurrency?: string;\n\twithCurrencySymbol?: boolean;\n\tcustomInput?: React.ComponentType<BaseType>;\n};\n\nfunction RenderCurrencyInput<BaseType = InputAttributes>(\n\t{ locale = \"en\", currency = \"USD\", withCurrencySymbol = true, ...props }: CurrencyInputProps<BaseType>,\n\tforwadedRef: ForwardedRef<HTMLInputElement>,\n) {\n\tconst innerRef = useRef<HTMLInputElement>(null);\n\tconst currencyFormat = resolveCurrencyFormat(locale, currency);\n\tconst prefix = currencyFormat?.currencyPosition === \"prefix\" ? `${currencyFormat.currencySymbol} ` : \"\";\n\tconst minimumFractionDigits = currencyFormat?.minimumFractionDigits ?? 0;\n\tconst maximumFractionDigits = currencyFormat?.maximumFractionDigits ?? 0;\n\tconst divideBy = 10 ** minimumFractionDigits;\n\n\tfunction format(inputValue: string) {\n\t\tlet value = 0;\n\t\tif (!Number(inputValue)) {\n\t\t\tvalue = 0;\n\t\t\t// when this happens, we want to position the caret at the end\n\t\t\t// of the input only if the user is on the input\n\t\t\tif (typeof document !== \"undefined\" && document.activeElement === innerRef.current) {\n\t\t\t\tinnerRef.current?.setSelectionRange(\n\t\t\t\t\tinputValue.length + (minimumFractionDigits ?? 0),\n\t\t\t\t\tinputValue.length + (minimumFractionDigits ?? 0),\n\t\t\t\t);\n\n\t\t\t\tif (inputValue.length && inputValue.length <= 3) {\n\t\t\t\t\t// to avoid the caret jumping around, we want to position it\n\t\t\t\t\t// at the end of the input when the user is typing the first\n\t\t\t\t\t// numbers\n\t\t\t\t\tinnerRef.current?.setSelectionRange(\n\t\t\t\t\t\tinputValue.length + (minimumFractionDigits ?? 0),\n\t\t\t\t\t\tinputValue.length + (minimumFractionDigits ?? 0),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tvalue = Number(inputValue);\n\t\t}\n\n\t\tconst amount = new Intl.NumberFormat(currencyFormat?.locale, {\n\t\t\tstyle: \"currency\",\n\t\t\tcurrency: currencyFormat?.currency,\n\t\t\tcurrencyDisplay: \"code\",\n\t\t\tminimumFractionDigits,\n\t\t\tmaximumFractionDigits,\n\t\t})\n\t\t\t// dynamically divide the value by the minimumFractionDigits\n\t\t\t.format(minimumFractionDigits ? value / divideBy : value)\n\t\t\t.replace(/[a-z]{3}/i, \"\")\n\t\t\t.trim();\n\n\t\tif (withCurrencySymbol) {\n\t\t\treturn `${prefix}${amount}`;\n\t\t}\n\n\t\treturn `${amount}`;\n\t}\n\n\tfunction onValueChange(values: NumberFormatValues, sourceInfo: SourceInfo) {\n\t\tconst val = minimumFractionDigits\n\t\t\t? (Number.parseFloat(values.value) / divideBy).toFixed(minimumFractionDigits)\n\t\t\t: values.value;\n\t\tconst floatVal = values.floatValue && minimumFractionDigits ? values.floatValue / divideBy : values.floatValue;\n\n\t\tconsole.info;\n\t\tprops?.onValueChange?.(\n\t\t\t{\n\t\t\t\tvalue: val,\n\t\t\t\tfloatValue: floatVal,\n\t\t\t\tformattedValue: values.formattedValue,\n\t\t\t},\n\t\t\tsourceInfo,\n\t\t);\n\t}\n\n\tfunction onFocus(event: FocusEvent<HTMLInputElement>) {\n\t\tif (innerRef.current) {\n\t\t\tconst positionLastDigit = innerRef.current.value.length - prefix.length;\n\t\t\tsetCaretPosition(innerRef.current, positionLastDigit + (minimumFractionDigits ?? 0));\n\t\t}\n\n\t\tprops?.onFocus?.(event);\n\t}\n\n\t// set caret position when the user is typing, but not when the user move the caret\n\t// with the arrow keys\n\tfunction onInput(event: FormEvent<HTMLInputElement>) {\n\t\tif (innerRef.current) {\n\t\t\tconst positionLastDigit = innerRef.current.value.length - prefix.length;\n\t\t\tsetCaretPosition(innerRef.current, positionLastDigit + (minimumFractionDigits ?? 0));\n\t\t}\n\n\t\t// @ts-expect-error - onInput is not part of the type\n\t\tprops?.onInput?.(event);\n\t}\n\n\treturn (\n\t\t// @ts-expect-error\n\t\t<NumberFormatBase\n\t\t\t{...props}\n\t\t\tformat={format}\n\t\t\tonFocus={onFocus}\n\t\t\tonInput={onInput}\n\t\t\tonValueChange={onValueChange}\n\t\t\tgetInputRef={mergeRefs(innerRef, forwadedRef)}\n\t\t\tprefix={undefined}\n\t\t\tvalueIsNumericString={false}\n\t\t/>\n\t);\n}\n\n// @ts-expect-error - forwardRef is not part of the type\nexport const CurrencyInput: <BaseType = InputAttributes>(\n\tprops: CurrencyInputProps<BaseType> & { ref?: ForwardedRef<HTMLInputElement> },\n) => ReturnType<typeof RenderCurrencyInput<BaseType>> = forwardRef(RenderCurrencyInput);\n"]}