react-input-mask-format 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/number/index.ts","../src/number/number-format.tsx","../src/number/format-number.ts","../src/number/parse-number.ts","../src/utils/input.ts","../src/number/validate.ts","../src/number/use-number-format.ts","../src/set-native-value.ts"],"sourcesContent":["export { NumberFormat } from \"./number-format\";\nexport type { NumberFormatProps } from \"./number-format\";\nexport { useNumberFormat } from \"./use-number-format\";\nexport type { UseNumberFormatOptions } from \"./use-number-format\";\nexport { formatNumber } from \"./format-number\";\nexport { parseNumber } from \"./parse-number\";\nexport type { NumberFormatOptions, NumberFormatValues } from \"./types\";\n","import React, { forwardRef, useLayoutEffect, useReducer, useRef } from \"react\";\nimport type { NumberFormatOptions, NumberFormatValues } from \"./types\";\nimport { formatNumber } from \"./format-number\";\nimport { parseNumber, digitsBeforeCaret, resolveCaret } from \"./parse-number\";\nimport { isInputFocused, setInputSelection } from \"../utils/input\";\nimport { warnOnSeparatorCollision } from \"./validate\";\n\nexport interface NumberFormatProps\n extends NumberFormatOptions,\n Omit<React.InputHTMLAttributes<HTMLInputElement>, \"value\" | \"onChange\" | \"prefix\"> {\n value?: string | number;\n onValueChange?: (values: NumberFormatValues) => void;\n isAllowed?: (values: NumberFormatValues) => boolean;\n}\n\nexport const NumberFormat = forwardRef<HTMLInputElement, NumberFormatProps>(function NumberFormat(\n props,\n forwardedRef\n) {\n const {\n value = \"\",\n onValueChange,\n isAllowed,\n thousandSeparator,\n decimalSeparator,\n decimalScale,\n fixedDecimalScale,\n prefix,\n suffix,\n allowNegative,\n allowLeadingZeros,\n ...domProps\n } = props;\n\n const options: NumberFormatOptions = {\n thousandSeparator,\n decimalSeparator,\n decimalScale,\n fixedDecimalScale,\n prefix,\n suffix,\n allowNegative,\n allowLeadingZeros\n };\n\n warnOnSeparatorCollision(options);\n\n const innerRef = useRef<HTMLInputElement | null>(null);\n const caretRef = useRef<number | null>(null);\n const lastFormattedRef = useRef<string>(\"\");\n const lastValuesRef = useRef<NumberFormatValues | null>(null);\n\n // A controlled parent may call onValueChange with a floatValue that is\n // Object.is-equal to its current state (e.g. typing \"12.\" after \"12\" still\n // parses to 12). React then bails out of re-rendering that parent's\n // subtree entirely — our component never re-renders — and its own\n // controlled-input restoration afterwards resets the DOM's raw value back\n // to the last-committed prop, destroying the in-progress edit before our\n // `formatted` derivation above ever gets a chance to run. Forcing our OWN\n // render here (independent of whatever the parent decides) guarantees a\n // real commit happens with the preserved `formatted` string, so the DOM\n // node's committed value is updated and nothing gets restored away.\n const [, forceUpdate] = useReducer((n: number) => n + 1, 0);\n\n // Controlled display: normally derive the shown string from the `value` prop.\n // BUT when the prop is numerically equal to the values we last emitted via\n // onValueChange (the consumer echoing back e.g. floatValue), keep the exact\n // in-progress formatted string — otherwise a trailing decimal separator or\n // trailing zeros the user is mid-typing get destroyed by the lossy number\n // round-trip (e.g. \"12.\" -> 12 -> \"12\", eating the \".\").\n const propFloatValue =\n typeof value === \"number\"\n ? value\n : parseNumber(String(value ?? \"\"), options).floatValue;\n const formatted =\n lastValuesRef.current !== null && propFloatValue === lastValuesRef.current.floatValue\n ? lastValuesRef.current.formattedValue\n : formatNumber(value ?? \"\", options);\n lastFormattedRef.current = formatted;\n\n function handleChange(event: React.ChangeEvent<HTMLInputElement>): void {\n const el = event.target;\n const caret = el.selectionStart ?? el.value.length;\n const digits = digitsBeforeCaret(el.value, caret);\n const values = parseNumber(el.value, options);\n\n if (isAllowed && !isAllowed(values)) {\n // Revert: restore the previous formatted value AND put the caret back where\n // it was BEFORE the rejected keystroke. Derive that from how many chars the\n // rejected edit added, not from caretRef (which the layout effect has already\n // nulled by the time the next keystroke fires — that made it always fall back\n // to end-of-string and jump the caret on mid-string edits).\n const typedCaret = el.selectionStart ?? el.value.length;\n const lengthDelta = el.value.length - lastFormattedRef.current.length;\n const revertCaret = Math.max(0, typedCaret - lengthDelta);\n el.value = lastFormattedRef.current;\n if (isInputFocused(el)) setInputSelection(el, revertCaret, revertCaret);\n return;\n }\n\n caretRef.current = resolveCaret(values, digits, options);\n lastValuesRef.current = values;\n forceUpdate();\n onValueChange?.(values);\n }\n\n // After a controlled re-render, restore the caret we computed in onChange\n // (React resets it to the end for controlled inputs).\n useLayoutEffect(() => {\n const el = innerRef.current;\n if (el && caretRef.current !== null && isInputFocused(el)) {\n setInputSelection(el, caretRef.current, caretRef.current);\n caretRef.current = null;\n }\n });\n\n function setRef(el: HTMLInputElement | null): void {\n innerRef.current = el;\n if (typeof forwardedRef === \"function\") forwardedRef(el);\n else if (forwardedRef) (forwardedRef as React.MutableRefObject<HTMLInputElement | null>).current = el;\n }\n\n return <input {...domProps} ref={setRef} value={formatted} onChange={handleChange} />;\n});\n\nNumberFormat.displayName = \"NumberFormat\";\n","import type { NumberFormatOptions } from \"./types\";\n\nexport interface NumberParts {\n negative: boolean;\n int: string;\n frac: string;\n hasSeparator: boolean;\n}\n\nexport function resolveSeparators(options: NumberFormatOptions): { thousand: string; decimal: string } {\n const decimal = options.decimalSeparator ?? \".\";\n let thousand = \"\";\n if (options.thousandSeparator === true) thousand = \",\";\n else if (typeof options.thousandSeparator === \"string\") thousand = options.thousandSeparator;\n return { thousand, decimal };\n}\n\nfunction extract(raw: string, decimal: string, options: NumberFormatOptions): NumberParts {\n const allowNegative = options.allowNegative ?? true;\n const negative = allowNegative && raw.indexOf(\"-\") !== -1;\n\n const sepIndex = decimal ? raw.indexOf(decimal) : -1;\n const hasSeparator = sepIndex !== -1;\n const intRaw = hasSeparator ? raw.slice(0, sepIndex) : raw;\n const fracRaw = hasSeparator ? raw.slice(sepIndex + decimal.length) : \"\";\n\n let int = intRaw.replace(/\\D/g, \"\");\n let frac = fracRaw.replace(/\\D/g, \"\");\n\n if (!(options.allowLeadingZeros ?? false)) {\n int = int.replace(/^0+(?=\\d)/, \"\");\n }\n if (options.decimalScale !== undefined) {\n frac = frac.slice(0, options.decimalScale);\n }\n return { negative, int, frac, hasSeparator };\n}\n\n// Tokenize a user-facing formatted string (strips prefix/suffix, uses opts separators).\nexport function tokenize(rawInput: string, options: NumberFormatOptions): NumberParts {\n const { decimal } = resolveSeparators(options);\n let s = rawInput;\n if (options.prefix && s.startsWith(options.prefix)) s = s.slice(options.prefix.length);\n if (options.suffix && s.endsWith(options.suffix)) s = s.slice(0, s.length - options.suffix.length);\n return extract(s, decimal, options);\n}\n\n// Tokenize a canonical numeric string (from a JS number): always \".\" decimal, no affixes.\nfunction tokenizeCanonical(canonical: string, options: NumberFormatOptions): NumberParts {\n return extract(canonical, \".\", options);\n}\n\nfunction group(int: string, thousand: string): string {\n if (!thousand || int.length <= 3) return int;\n return int.replace(/\\B(?=(\\d{3})+(?!\\d))/g, thousand);\n}\n\nexport function buildFormatted(parts: NumberParts, options: NumberFormatOptions): string {\n const { thousand, decimal } = resolveSeparators(options);\n const prefix = options.prefix ?? \"\";\n const suffix = options.suffix ?? \"\";\n const fixed = options.fixedDecimalScale ?? false;\n const scale = options.decimalScale;\n\n const hasFrac = parts.frac.length > 0;\n const isEmpty = parts.int === \"\" && !hasFrac && !parts.hasSeparator;\n if (isEmpty) {\n return parts.negative ? `${prefix}-${suffix}` : \"\";\n }\n\n let intDisplay = parts.int;\n if (intDisplay === \"\" && (hasFrac || parts.hasSeparator)) intDisplay = \"0\";\n\n let body = group(intDisplay, thousand);\n\n if (fixed && scale !== undefined && scale > 0) {\n body += decimal + parts.frac.slice(0, scale).padEnd(scale, \"0\");\n } else if (hasFrac) {\n body += decimal + parts.frac;\n } else if (parts.hasSeparator) {\n body += decimal; // trailing separator while typing \"1.\"\n }\n\n return `${prefix}${parts.negative ? \"-\" : \"\"}${body}${suffix}`;\n}\n\nfunction numberToString(n: number): string {\n if (!Number.isFinite(n)) return \"\";\n const s = String(n);\n if (s.indexOf(\"e\") === -1 && s.indexOf(\"E\") === -1) return s;\n // Expand exponential notation (e.g. \"1e+21\", \"1e-7\") to a plain decimal string\n // so tokenize() doesn't read the exponent's digits/\"-\" as value characters.\n // Precision is bounded by JS's own float precision; this only guards extreme\n // magnitudes reachable via a numeric value prop (typing never yields \"e\").\n const sign = n < 0 ? \"-\" : \"\";\n const abs = Math.abs(n);\n // abs >= 1 with an exponent means a huge integer (>= 1e21, where every double\n // is integer-valued); BigInt expands it without an exponent. toFixed alone\n // fails here because it also switches to exponential notation at >= 1e21.\n const expanded =\n abs >= 1\n ? BigInt(Math.trunc(abs)).toString()\n : abs.toFixed(20).replace(/0+$/, \"\").replace(/\\.$/, \"\");\n return sign + expanded;\n}\n\nexport function formatNumber(input: string | number, options: NumberFormatOptions): string {\n const parts =\n typeof input === \"number\"\n ? tokenizeCanonical(numberToString(input), options)\n : tokenize(input, options);\n return buildFormatted(parts, options);\n}\n","import type { NumberFormatOptions, NumberFormatValues } from \"./types\";\nimport { tokenize, buildFormatted, resolveSeparators, type NumberParts } from \"./format-number\";\n\nfunction buildValue(parts: NumberParts): string {\n const sign = parts.negative ? \"-\" : \"\";\n let int = parts.int;\n if (int === \"\" && (parts.frac !== \"\" || parts.hasSeparator)) int = \"0\";\n let v = sign + int;\n if (parts.frac !== \"\") v += \".\" + parts.frac;\n else if (parts.hasSeparator) v += \".\";\n return v;\n}\n\nexport function parseNumber(formatted: string, options: NumberFormatOptions): NumberFormatValues {\n const parts = tokenize(formatted, options);\n const value = buildValue(parts);\n const formattedValue = buildFormatted(parts, options);\n\n // No digit characters entered at all (e.g. \"\", \"-\", \".\", \"-.\") → no numeric value.\n const hasNoDigits = parts.int === \"\" && parts.frac === \"\";\n const parsed = parseFloat(value);\n const floatValue = hasNoDigits || Number.isNaN(parsed) ? undefined : parsed;\n\n return { value, formattedValue, floatValue };\n}\n\nexport function digitsBeforeCaret(value: string, caret: number): number {\n let count = 0;\n for (let i = 0; i < caret && i < value.length; i++) {\n if (value[i] >= \"0\" && value[i] <= \"9\") count++;\n }\n return count;\n}\n\nexport function caretAfterReformat(nextFormatted: string, digitsBefore: number): number {\n if (digitsBefore <= 0) {\n // place caret before the first digit (after any prefix/sign)\n const firstDigit = nextFormatted.search(/\\d/);\n return firstDigit === -1 ? nextFormatted.length : firstDigit;\n }\n let seen = 0;\n let i = 0;\n for (; i < nextFormatted.length; i++) {\n if (nextFormatted[i] >= \"0\" && nextFormatted[i] <= \"9\") {\n seen++;\n if (seen === digitsBefore) return i + 1;\n }\n }\n return nextFormatted.length;\n}\n\nexport function resolveCaret(\n values: NumberFormatValues,\n digitsBefore: number,\n options: NumberFormatOptions\n): number {\n const caret = caretAfterReformat(values.formattedValue, digitsBefore);\n // caretAfterReformat is digit-anchored and can't see a just-typed trailing\n // decimal separator with no fraction digit yet (e.g. \"1,234.\" right after\n // the \".\" is typed) — it lands the caret BEFORE that separator. Step past it\n // so the next keystroke goes into the fraction rather than ahead of the \".\".\n if (values.value.endsWith(\".\")) {\n const { decimal } = resolveSeparators(options);\n if (decimal && values.formattedValue.slice(caret, caret + decimal.length) === decimal) {\n return caret + decimal.length;\n }\n }\n return caret;\n}\n","import type { Selection } from \"../types\";\n\nexport function setInputSelection(\n input: HTMLInputElement,\n start: number,\n end?: number\n): void {\n if (end === undefined) {\n end = start;\n }\n input.setSelectionRange(start, end);\n}\n\nexport function getInputSelection(input: HTMLInputElement): Required<Selection> {\n const start = input.selectionStart;\n const end = input.selectionEnd;\n\n return {\n start,\n end,\n length: (end ?? 0) - (start ?? 0)\n };\n}\n\nexport function isInputFocused(input: HTMLInputElement): boolean {\n const inputDocument = input.ownerDocument;\n return inputDocument.hasFocus() && inputDocument.activeElement === input;\n}\n","import type { NumberFormatOptions } from \"./types\";\nimport { resolveSeparators } from \"./format-number\";\n\nconst warned = new Set<string>();\n\n// Dev-only guard: thousandSeparator and decimalSeparator must differ, or the\n// value cannot be parsed correctly. Mirrors src/validate-props.ts's style.\nexport function warnOnSeparatorCollision(options: NumberFormatOptions): void {\n if (process.env.NODE_ENV === \"production\") return;\n const { thousand, decimal } = resolveSeparators(options);\n if (thousand && thousand === decimal) {\n if (warned.has(thousand)) return;\n warned.add(thousand);\n console.error(\n `react-input-mask-format: thousandSeparator and decimalSeparator must differ (both are \"${thousand}\"). The value will not parse correctly.`\n );\n }\n}\n","import { useRef, useCallback } from \"react\";\nimport type React from \"react\";\nimport type { NumberFormatOptions, NumberFormatValues } from \"./types\";\nimport { formatNumber } from \"./format-number\";\nimport { parseNumber, digitsBeforeCaret, resolveCaret } from \"./parse-number\";\nimport { setNativeValue } from \"../set-native-value\";\nimport { isInputFocused, setInputSelection } from \"../utils/input\";\nimport { warnOnSeparatorCollision } from \"./validate\";\n\nexport interface UseNumberFormatOptions extends NumberFormatOptions {\n onValueChange?: (values: NumberFormatValues) => void;\n}\n\nconst IS_JSDOM =\n typeof navigator !== \"undefined\" && navigator.userAgent.includes(\"jsdom\");\n\nexport function useNumberFormat(\n options: UseNumberFormatOptions\n): React.RefCallback<HTMLInputElement> {\n warnOnSeparatorCollision(options);\n\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const inputRef = useRef<HTMLInputElement | null>(null);\n const handlerRef = useRef<((event: Event) => void) | null>(null);\n\n return useCallback((el: HTMLInputElement | null) => {\n if (el) {\n const handler = (): void => {\n const opts = optionsRef.current;\n const caret = el.selectionStart ?? el.value.length;\n const digits = digitsBeforeCaret(el.value, caret);\n const values = parseNumber(el.value, opts);\n setNativeValue(el, values.formattedValue);\n if (isInputFocused(el)) {\n const next = resolveCaret(values, digits, opts);\n setInputSelection(el, next, next);\n }\n // JSDOM-ONLY: resync @testing-library/user-event v14's shadow-value cache.\n // setNativeValue above bypasses React's value tracker; under jsdom that\n // leaves user-event's per-input shadow value stale, corrupting masking from\n // ~the 4th keystroke. A plain (idempotent) reassignment resyncs it.\n // MUST NOT run in a real browser: there, microtasks drain BETWEEN event\n // listeners, so this write would fight React; real browsers read the live\n // DOM and need no resync. onValueChange (below) is called directly, not via\n // React's tracker, so it is unaffected in either environment.\n if (IS_JSDOM) {\n queueMicrotask(() => {\n if (el.value !== values.formattedValue) return;\n el.value = values.formattedValue;\n if (isInputFocused(el)) {\n const c = resolveCaret(values, digits, opts);\n setInputSelection(el, c, c);\n }\n });\n }\n opts.onValueChange?.(values);\n };\n el.addEventListener(\"input\", handler);\n handlerRef.current = handler;\n inputRef.current = el;\n // format any initial (defaultValue) content\n if (el.value) {\n setNativeValue(el, formatNumber(el.value, optionsRef.current));\n }\n } else {\n if (inputRef.current && handlerRef.current) {\n inputRef.current.removeEventListener(\"input\", handlerRef.current);\n }\n inputRef.current = null;\n handlerRef.current = null;\n }\n }, []);\n}\n","// Write to input.value through the native prototype setter so React's\n// internal value tracker stays stale. The user's keystroke `input` event then\n// bubbles into React's root listener, which sees masked value != stale tracker\n// and fires the consumer's onChange with the masked value. Using the tracked\n// (React-wrapped) setter instead would suppress that onChange.\nexport function setNativeValue(input: HTMLInputElement, value: string): void {\n const descriptor = Object.getOwnPropertyDescriptor(\n HTMLInputElement.prototype,\n \"value\"\n );\n const setter = descriptor && descriptor.set;\n if (setter) {\n setter.call(input, value);\n } else {\n input.value = value;\n }\n}\n"],"mappings":"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","names":["NumberFormat","React","import_react"]}
@@ -0,0 +1,46 @@
1
+ import React__default from 'react';
2
+
3
+ interface NumberFormatOptions {
4
+ /** Group integer digits. `true` → ",". A string sets a custom separator. */
5
+ thousandSeparator?: string | boolean;
6
+ /** Decimal separator. Default ".". */
7
+ decimalSeparator?: string;
8
+ /** Max digits after the decimal separator (truncated, not rounded). */
9
+ decimalScale?: number;
10
+ /** Pad fraction to `decimalScale` with zeros. */
11
+ fixedDecimalScale?: boolean;
12
+ /** Text before the number, e.g. "$ ". */
13
+ prefix?: string;
14
+ /** Text after the number, e.g. " %". */
15
+ suffix?: string;
16
+ /** Allow a leading "-". Default true. */
17
+ allowNegative?: boolean;
18
+ /** Keep leading zeros in the integer part. Default false. */
19
+ allowLeadingZeros?: boolean;
20
+ }
21
+ interface NumberFormatValues {
22
+ /** Unformatted numeric string with a "." decimal, e.g. "1234.56". */
23
+ value: string;
24
+ /** What the input shows, e.g. "$ 1,234.56". */
25
+ formattedValue: string;
26
+ /** parseFloat(value); undefined when empty / not a number. */
27
+ floatValue: number | undefined;
28
+ }
29
+
30
+ interface NumberFormatProps extends NumberFormatOptions, Omit<React__default.InputHTMLAttributes<HTMLInputElement>, "value" | "onChange" | "prefix"> {
31
+ value?: string | number;
32
+ onValueChange?: (values: NumberFormatValues) => void;
33
+ isAllowed?: (values: NumberFormatValues) => boolean;
34
+ }
35
+ declare const NumberFormat: React__default.ForwardRefExoticComponent<NumberFormatProps & React__default.RefAttributes<HTMLInputElement>>;
36
+
37
+ interface UseNumberFormatOptions extends NumberFormatOptions {
38
+ onValueChange?: (values: NumberFormatValues) => void;
39
+ }
40
+ declare function useNumberFormat(options: UseNumberFormatOptions): React__default.RefCallback<HTMLInputElement>;
41
+
42
+ declare function formatNumber(input: string | number, options: NumberFormatOptions): string;
43
+
44
+ declare function parseNumber(formatted: string, options: NumberFormatOptions): NumberFormatValues;
45
+
46
+ export { NumberFormat, type NumberFormatOptions, type NumberFormatProps, type NumberFormatValues, type UseNumberFormatOptions, formatNumber, parseNumber, useNumberFormat };
@@ -0,0 +1,46 @@
1
+ import React__default from 'react';
2
+
3
+ interface NumberFormatOptions {
4
+ /** Group integer digits. `true` → ",". A string sets a custom separator. */
5
+ thousandSeparator?: string | boolean;
6
+ /** Decimal separator. Default ".". */
7
+ decimalSeparator?: string;
8
+ /** Max digits after the decimal separator (truncated, not rounded). */
9
+ decimalScale?: number;
10
+ /** Pad fraction to `decimalScale` with zeros. */
11
+ fixedDecimalScale?: boolean;
12
+ /** Text before the number, e.g. "$ ". */
13
+ prefix?: string;
14
+ /** Text after the number, e.g. " %". */
15
+ suffix?: string;
16
+ /** Allow a leading "-". Default true. */
17
+ allowNegative?: boolean;
18
+ /** Keep leading zeros in the integer part. Default false. */
19
+ allowLeadingZeros?: boolean;
20
+ }
21
+ interface NumberFormatValues {
22
+ /** Unformatted numeric string with a "." decimal, e.g. "1234.56". */
23
+ value: string;
24
+ /** What the input shows, e.g. "$ 1,234.56". */
25
+ formattedValue: string;
26
+ /** parseFloat(value); undefined when empty / not a number. */
27
+ floatValue: number | undefined;
28
+ }
29
+
30
+ interface NumberFormatProps extends NumberFormatOptions, Omit<React__default.InputHTMLAttributes<HTMLInputElement>, "value" | "onChange" | "prefix"> {
31
+ value?: string | number;
32
+ onValueChange?: (values: NumberFormatValues) => void;
33
+ isAllowed?: (values: NumberFormatValues) => boolean;
34
+ }
35
+ declare const NumberFormat: React__default.ForwardRefExoticComponent<NumberFormatProps & React__default.RefAttributes<HTMLInputElement>>;
36
+
37
+ interface UseNumberFormatOptions extends NumberFormatOptions {
38
+ onValueChange?: (values: NumberFormatValues) => void;
39
+ }
40
+ declare function useNumberFormat(options: UseNumberFormatOptions): React__default.RefCallback<HTMLInputElement>;
41
+
42
+ declare function formatNumber(input: string | number, options: NumberFormatOptions): string;
43
+
44
+ declare function parseNumber(formatted: string, options: NumberFormatOptions): NumberFormatValues;
45
+
46
+ export { NumberFormat, type NumberFormatOptions, type NumberFormatProps, type NumberFormatValues, type UseNumberFormatOptions, formatNumber, parseNumber, useNumberFormat };
package/dist/number.js ADDED
@@ -0,0 +1,280 @@
1
+ import {
2
+ isInputFocused,
3
+ setInputSelection,
4
+ setNativeValue
5
+ } from "./chunk-CEZXZ7IJ.js";
6
+
7
+ // src/number/number-format.tsx
8
+ import React, { forwardRef, useLayoutEffect, useReducer, useRef } from "react";
9
+
10
+ // src/number/format-number.ts
11
+ function resolveSeparators(options) {
12
+ var _a;
13
+ const decimal = (_a = options.decimalSeparator) != null ? _a : ".";
14
+ let thousand = "";
15
+ if (options.thousandSeparator === true) thousand = ",";
16
+ else if (typeof options.thousandSeparator === "string") thousand = options.thousandSeparator;
17
+ return { thousand, decimal };
18
+ }
19
+ function extract(raw, decimal, options) {
20
+ var _a, _b;
21
+ const allowNegative = (_a = options.allowNegative) != null ? _a : true;
22
+ const negative = allowNegative && raw.indexOf("-") !== -1;
23
+ const sepIndex = decimal ? raw.indexOf(decimal) : -1;
24
+ const hasSeparator = sepIndex !== -1;
25
+ const intRaw = hasSeparator ? raw.slice(0, sepIndex) : raw;
26
+ const fracRaw = hasSeparator ? raw.slice(sepIndex + decimal.length) : "";
27
+ let int = intRaw.replace(/\D/g, "");
28
+ let frac = fracRaw.replace(/\D/g, "");
29
+ if (!((_b = options.allowLeadingZeros) != null ? _b : false)) {
30
+ int = int.replace(/^0+(?=\d)/, "");
31
+ }
32
+ if (options.decimalScale !== void 0) {
33
+ frac = frac.slice(0, options.decimalScale);
34
+ }
35
+ return { negative, int, frac, hasSeparator };
36
+ }
37
+ function tokenize(rawInput, options) {
38
+ const { decimal } = resolveSeparators(options);
39
+ let s = rawInput;
40
+ if (options.prefix && s.startsWith(options.prefix)) s = s.slice(options.prefix.length);
41
+ if (options.suffix && s.endsWith(options.suffix)) s = s.slice(0, s.length - options.suffix.length);
42
+ return extract(s, decimal, options);
43
+ }
44
+ function tokenizeCanonical(canonical, options) {
45
+ return extract(canonical, ".", options);
46
+ }
47
+ function group(int, thousand) {
48
+ if (!thousand || int.length <= 3) return int;
49
+ return int.replace(/\B(?=(\d{3})+(?!\d))/g, thousand);
50
+ }
51
+ function buildFormatted(parts, options) {
52
+ var _a, _b, _c;
53
+ const { thousand, decimal } = resolveSeparators(options);
54
+ const prefix = (_a = options.prefix) != null ? _a : "";
55
+ const suffix = (_b = options.suffix) != null ? _b : "";
56
+ const fixed = (_c = options.fixedDecimalScale) != null ? _c : false;
57
+ const scale = options.decimalScale;
58
+ const hasFrac = parts.frac.length > 0;
59
+ const isEmpty = parts.int === "" && !hasFrac && !parts.hasSeparator;
60
+ if (isEmpty) {
61
+ return parts.negative ? `${prefix}-${suffix}` : "";
62
+ }
63
+ let intDisplay = parts.int;
64
+ if (intDisplay === "" && (hasFrac || parts.hasSeparator)) intDisplay = "0";
65
+ let body = group(intDisplay, thousand);
66
+ if (fixed && scale !== void 0 && scale > 0) {
67
+ body += decimal + parts.frac.slice(0, scale).padEnd(scale, "0");
68
+ } else if (hasFrac) {
69
+ body += decimal + parts.frac;
70
+ } else if (parts.hasSeparator) {
71
+ body += decimal;
72
+ }
73
+ return `${prefix}${parts.negative ? "-" : ""}${body}${suffix}`;
74
+ }
75
+ function numberToString(n) {
76
+ if (!Number.isFinite(n)) return "";
77
+ const s = String(n);
78
+ if (s.indexOf("e") === -1 && s.indexOf("E") === -1) return s;
79
+ const sign = n < 0 ? "-" : "";
80
+ const abs = Math.abs(n);
81
+ const expanded = abs >= 1 ? BigInt(Math.trunc(abs)).toString() : abs.toFixed(20).replace(/0+$/, "").replace(/\.$/, "");
82
+ return sign + expanded;
83
+ }
84
+ function formatNumber(input, options) {
85
+ const parts = typeof input === "number" ? tokenizeCanonical(numberToString(input), options) : tokenize(input, options);
86
+ return buildFormatted(parts, options);
87
+ }
88
+
89
+ // src/number/parse-number.ts
90
+ function buildValue(parts) {
91
+ const sign = parts.negative ? "-" : "";
92
+ let int = parts.int;
93
+ if (int === "" && (parts.frac !== "" || parts.hasSeparator)) int = "0";
94
+ let v = sign + int;
95
+ if (parts.frac !== "") v += "." + parts.frac;
96
+ else if (parts.hasSeparator) v += ".";
97
+ return v;
98
+ }
99
+ function parseNumber(formatted, options) {
100
+ const parts = tokenize(formatted, options);
101
+ const value = buildValue(parts);
102
+ const formattedValue = buildFormatted(parts, options);
103
+ const hasNoDigits = parts.int === "" && parts.frac === "";
104
+ const parsed = parseFloat(value);
105
+ const floatValue = hasNoDigits || Number.isNaN(parsed) ? void 0 : parsed;
106
+ return { value, formattedValue, floatValue };
107
+ }
108
+ function digitsBeforeCaret(value, caret) {
109
+ let count = 0;
110
+ for (let i = 0; i < caret && i < value.length; i++) {
111
+ if (value[i] >= "0" && value[i] <= "9") count++;
112
+ }
113
+ return count;
114
+ }
115
+ function caretAfterReformat(nextFormatted, digitsBefore) {
116
+ if (digitsBefore <= 0) {
117
+ const firstDigit = nextFormatted.search(/\d/);
118
+ return firstDigit === -1 ? nextFormatted.length : firstDigit;
119
+ }
120
+ let seen = 0;
121
+ let i = 0;
122
+ for (; i < nextFormatted.length; i++) {
123
+ if (nextFormatted[i] >= "0" && nextFormatted[i] <= "9") {
124
+ seen++;
125
+ if (seen === digitsBefore) return i + 1;
126
+ }
127
+ }
128
+ return nextFormatted.length;
129
+ }
130
+ function resolveCaret(values, digitsBefore, options) {
131
+ const caret = caretAfterReformat(values.formattedValue, digitsBefore);
132
+ if (values.value.endsWith(".")) {
133
+ const { decimal } = resolveSeparators(options);
134
+ if (decimal && values.formattedValue.slice(caret, caret + decimal.length) === decimal) {
135
+ return caret + decimal.length;
136
+ }
137
+ }
138
+ return caret;
139
+ }
140
+
141
+ // src/number/validate.ts
142
+ var warned = /* @__PURE__ */ new Set();
143
+ function warnOnSeparatorCollision(options) {
144
+ if (process.env.NODE_ENV === "production") return;
145
+ const { thousand, decimal } = resolveSeparators(options);
146
+ if (thousand && thousand === decimal) {
147
+ if (warned.has(thousand)) return;
148
+ warned.add(thousand);
149
+ console.error(
150
+ `react-input-mask-format: thousandSeparator and decimalSeparator must differ (both are "${thousand}"). The value will not parse correctly.`
151
+ );
152
+ }
153
+ }
154
+
155
+ // src/number/number-format.tsx
156
+ var NumberFormat = forwardRef(function NumberFormat2(props, forwardedRef) {
157
+ const {
158
+ value = "",
159
+ onValueChange,
160
+ isAllowed,
161
+ thousandSeparator,
162
+ decimalSeparator,
163
+ decimalScale,
164
+ fixedDecimalScale,
165
+ prefix,
166
+ suffix,
167
+ allowNegative,
168
+ allowLeadingZeros,
169
+ ...domProps
170
+ } = props;
171
+ const options = {
172
+ thousandSeparator,
173
+ decimalSeparator,
174
+ decimalScale,
175
+ fixedDecimalScale,
176
+ prefix,
177
+ suffix,
178
+ allowNegative,
179
+ allowLeadingZeros
180
+ };
181
+ warnOnSeparatorCollision(options);
182
+ const innerRef = useRef(null);
183
+ const caretRef = useRef(null);
184
+ const lastFormattedRef = useRef("");
185
+ const lastValuesRef = useRef(null);
186
+ const [, forceUpdate] = useReducer((n) => n + 1, 0);
187
+ const propFloatValue = typeof value === "number" ? value : parseNumber(String(value != null ? value : ""), options).floatValue;
188
+ const formatted = lastValuesRef.current !== null && propFloatValue === lastValuesRef.current.floatValue ? lastValuesRef.current.formattedValue : formatNumber(value != null ? value : "", options);
189
+ lastFormattedRef.current = formatted;
190
+ function handleChange(event) {
191
+ var _a, _b;
192
+ const el = event.target;
193
+ const caret = (_a = el.selectionStart) != null ? _a : el.value.length;
194
+ const digits = digitsBeforeCaret(el.value, caret);
195
+ const values = parseNumber(el.value, options);
196
+ if (isAllowed && !isAllowed(values)) {
197
+ const typedCaret = (_b = el.selectionStart) != null ? _b : el.value.length;
198
+ const lengthDelta = el.value.length - lastFormattedRef.current.length;
199
+ const revertCaret = Math.max(0, typedCaret - lengthDelta);
200
+ el.value = lastFormattedRef.current;
201
+ if (isInputFocused(el)) setInputSelection(el, revertCaret, revertCaret);
202
+ return;
203
+ }
204
+ caretRef.current = resolveCaret(values, digits, options);
205
+ lastValuesRef.current = values;
206
+ forceUpdate();
207
+ onValueChange == null ? void 0 : onValueChange(values);
208
+ }
209
+ useLayoutEffect(() => {
210
+ const el = innerRef.current;
211
+ if (el && caretRef.current !== null && isInputFocused(el)) {
212
+ setInputSelection(el, caretRef.current, caretRef.current);
213
+ caretRef.current = null;
214
+ }
215
+ });
216
+ function setRef(el) {
217
+ innerRef.current = el;
218
+ if (typeof forwardedRef === "function") forwardedRef(el);
219
+ else if (forwardedRef) forwardedRef.current = el;
220
+ }
221
+ return /* @__PURE__ */ React.createElement("input", { ...domProps, ref: setRef, value: formatted, onChange: handleChange });
222
+ });
223
+ NumberFormat.displayName = "NumberFormat";
224
+
225
+ // src/number/use-number-format.ts
226
+ import { useRef as useRef2, useCallback } from "react";
227
+ var IS_JSDOM = typeof navigator !== "undefined" && navigator.userAgent.includes("jsdom");
228
+ function useNumberFormat(options) {
229
+ warnOnSeparatorCollision(options);
230
+ const optionsRef = useRef2(options);
231
+ optionsRef.current = options;
232
+ const inputRef = useRef2(null);
233
+ const handlerRef = useRef2(null);
234
+ return useCallback((el) => {
235
+ if (el) {
236
+ const handler = () => {
237
+ var _a, _b;
238
+ const opts = optionsRef.current;
239
+ const caret = (_a = el.selectionStart) != null ? _a : el.value.length;
240
+ const digits = digitsBeforeCaret(el.value, caret);
241
+ const values = parseNumber(el.value, opts);
242
+ setNativeValue(el, values.formattedValue);
243
+ if (isInputFocused(el)) {
244
+ const next = resolveCaret(values, digits, opts);
245
+ setInputSelection(el, next, next);
246
+ }
247
+ if (IS_JSDOM) {
248
+ queueMicrotask(() => {
249
+ if (el.value !== values.formattedValue) return;
250
+ el.value = values.formattedValue;
251
+ if (isInputFocused(el)) {
252
+ const c = resolveCaret(values, digits, opts);
253
+ setInputSelection(el, c, c);
254
+ }
255
+ });
256
+ }
257
+ (_b = opts.onValueChange) == null ? void 0 : _b.call(opts, values);
258
+ };
259
+ el.addEventListener("input", handler);
260
+ handlerRef.current = handler;
261
+ inputRef.current = el;
262
+ if (el.value) {
263
+ setNativeValue(el, formatNumber(el.value, optionsRef.current));
264
+ }
265
+ } else {
266
+ if (inputRef.current && handlerRef.current) {
267
+ inputRef.current.removeEventListener("input", handlerRef.current);
268
+ }
269
+ inputRef.current = null;
270
+ handlerRef.current = null;
271
+ }
272
+ }, []);
273
+ }
274
+ export {
275
+ NumberFormat,
276
+ formatNumber,
277
+ parseNumber,
278
+ useNumberFormat
279
+ };
280
+ //# sourceMappingURL=number.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/number/number-format.tsx","../src/number/format-number.ts","../src/number/parse-number.ts","../src/number/validate.ts","../src/number/use-number-format.ts"],"sourcesContent":["import React, { forwardRef, useLayoutEffect, useReducer, useRef } from \"react\";\nimport type { NumberFormatOptions, NumberFormatValues } from \"./types\";\nimport { formatNumber } from \"./format-number\";\nimport { parseNumber, digitsBeforeCaret, resolveCaret } from \"./parse-number\";\nimport { isInputFocused, setInputSelection } from \"../utils/input\";\nimport { warnOnSeparatorCollision } from \"./validate\";\n\nexport interface NumberFormatProps\n extends NumberFormatOptions,\n Omit<React.InputHTMLAttributes<HTMLInputElement>, \"value\" | \"onChange\" | \"prefix\"> {\n value?: string | number;\n onValueChange?: (values: NumberFormatValues) => void;\n isAllowed?: (values: NumberFormatValues) => boolean;\n}\n\nexport const NumberFormat = forwardRef<HTMLInputElement, NumberFormatProps>(function NumberFormat(\n props,\n forwardedRef\n) {\n const {\n value = \"\",\n onValueChange,\n isAllowed,\n thousandSeparator,\n decimalSeparator,\n decimalScale,\n fixedDecimalScale,\n prefix,\n suffix,\n allowNegative,\n allowLeadingZeros,\n ...domProps\n } = props;\n\n const options: NumberFormatOptions = {\n thousandSeparator,\n decimalSeparator,\n decimalScale,\n fixedDecimalScale,\n prefix,\n suffix,\n allowNegative,\n allowLeadingZeros\n };\n\n warnOnSeparatorCollision(options);\n\n const innerRef = useRef<HTMLInputElement | null>(null);\n const caretRef = useRef<number | null>(null);\n const lastFormattedRef = useRef<string>(\"\");\n const lastValuesRef = useRef<NumberFormatValues | null>(null);\n\n // A controlled parent may call onValueChange with a floatValue that is\n // Object.is-equal to its current state (e.g. typing \"12.\" after \"12\" still\n // parses to 12). React then bails out of re-rendering that parent's\n // subtree entirely — our component never re-renders — and its own\n // controlled-input restoration afterwards resets the DOM's raw value back\n // to the last-committed prop, destroying the in-progress edit before our\n // `formatted` derivation above ever gets a chance to run. Forcing our OWN\n // render here (independent of whatever the parent decides) guarantees a\n // real commit happens with the preserved `formatted` string, so the DOM\n // node's committed value is updated and nothing gets restored away.\n const [, forceUpdate] = useReducer((n: number) => n + 1, 0);\n\n // Controlled display: normally derive the shown string from the `value` prop.\n // BUT when the prop is numerically equal to the values we last emitted via\n // onValueChange (the consumer echoing back e.g. floatValue), keep the exact\n // in-progress formatted string — otherwise a trailing decimal separator or\n // trailing zeros the user is mid-typing get destroyed by the lossy number\n // round-trip (e.g. \"12.\" -> 12 -> \"12\", eating the \".\").\n const propFloatValue =\n typeof value === \"number\"\n ? value\n : parseNumber(String(value ?? \"\"), options).floatValue;\n const formatted =\n lastValuesRef.current !== null && propFloatValue === lastValuesRef.current.floatValue\n ? lastValuesRef.current.formattedValue\n : formatNumber(value ?? \"\", options);\n lastFormattedRef.current = formatted;\n\n function handleChange(event: React.ChangeEvent<HTMLInputElement>): void {\n const el = event.target;\n const caret = el.selectionStart ?? el.value.length;\n const digits = digitsBeforeCaret(el.value, caret);\n const values = parseNumber(el.value, options);\n\n if (isAllowed && !isAllowed(values)) {\n // Revert: restore the previous formatted value AND put the caret back where\n // it was BEFORE the rejected keystroke. Derive that from how many chars the\n // rejected edit added, not from caretRef (which the layout effect has already\n // nulled by the time the next keystroke fires — that made it always fall back\n // to end-of-string and jump the caret on mid-string edits).\n const typedCaret = el.selectionStart ?? el.value.length;\n const lengthDelta = el.value.length - lastFormattedRef.current.length;\n const revertCaret = Math.max(0, typedCaret - lengthDelta);\n el.value = lastFormattedRef.current;\n if (isInputFocused(el)) setInputSelection(el, revertCaret, revertCaret);\n return;\n }\n\n caretRef.current = resolveCaret(values, digits, options);\n lastValuesRef.current = values;\n forceUpdate();\n onValueChange?.(values);\n }\n\n // After a controlled re-render, restore the caret we computed in onChange\n // (React resets it to the end for controlled inputs).\n useLayoutEffect(() => {\n const el = innerRef.current;\n if (el && caretRef.current !== null && isInputFocused(el)) {\n setInputSelection(el, caretRef.current, caretRef.current);\n caretRef.current = null;\n }\n });\n\n function setRef(el: HTMLInputElement | null): void {\n innerRef.current = el;\n if (typeof forwardedRef === \"function\") forwardedRef(el);\n else if (forwardedRef) (forwardedRef as React.MutableRefObject<HTMLInputElement | null>).current = el;\n }\n\n return <input {...domProps} ref={setRef} value={formatted} onChange={handleChange} />;\n});\n\nNumberFormat.displayName = \"NumberFormat\";\n","import type { NumberFormatOptions } from \"./types\";\n\nexport interface NumberParts {\n negative: boolean;\n int: string;\n frac: string;\n hasSeparator: boolean;\n}\n\nexport function resolveSeparators(options: NumberFormatOptions): { thousand: string; decimal: string } {\n const decimal = options.decimalSeparator ?? \".\";\n let thousand = \"\";\n if (options.thousandSeparator === true) thousand = \",\";\n else if (typeof options.thousandSeparator === \"string\") thousand = options.thousandSeparator;\n return { thousand, decimal };\n}\n\nfunction extract(raw: string, decimal: string, options: NumberFormatOptions): NumberParts {\n const allowNegative = options.allowNegative ?? true;\n const negative = allowNegative && raw.indexOf(\"-\") !== -1;\n\n const sepIndex = decimal ? raw.indexOf(decimal) : -1;\n const hasSeparator = sepIndex !== -1;\n const intRaw = hasSeparator ? raw.slice(0, sepIndex) : raw;\n const fracRaw = hasSeparator ? raw.slice(sepIndex + decimal.length) : \"\";\n\n let int = intRaw.replace(/\\D/g, \"\");\n let frac = fracRaw.replace(/\\D/g, \"\");\n\n if (!(options.allowLeadingZeros ?? false)) {\n int = int.replace(/^0+(?=\\d)/, \"\");\n }\n if (options.decimalScale !== undefined) {\n frac = frac.slice(0, options.decimalScale);\n }\n return { negative, int, frac, hasSeparator };\n}\n\n// Tokenize a user-facing formatted string (strips prefix/suffix, uses opts separators).\nexport function tokenize(rawInput: string, options: NumberFormatOptions): NumberParts {\n const { decimal } = resolveSeparators(options);\n let s = rawInput;\n if (options.prefix && s.startsWith(options.prefix)) s = s.slice(options.prefix.length);\n if (options.suffix && s.endsWith(options.suffix)) s = s.slice(0, s.length - options.suffix.length);\n return extract(s, decimal, options);\n}\n\n// Tokenize a canonical numeric string (from a JS number): always \".\" decimal, no affixes.\nfunction tokenizeCanonical(canonical: string, options: NumberFormatOptions): NumberParts {\n return extract(canonical, \".\", options);\n}\n\nfunction group(int: string, thousand: string): string {\n if (!thousand || int.length <= 3) return int;\n return int.replace(/\\B(?=(\\d{3})+(?!\\d))/g, thousand);\n}\n\nexport function buildFormatted(parts: NumberParts, options: NumberFormatOptions): string {\n const { thousand, decimal } = resolveSeparators(options);\n const prefix = options.prefix ?? \"\";\n const suffix = options.suffix ?? \"\";\n const fixed = options.fixedDecimalScale ?? false;\n const scale = options.decimalScale;\n\n const hasFrac = parts.frac.length > 0;\n const isEmpty = parts.int === \"\" && !hasFrac && !parts.hasSeparator;\n if (isEmpty) {\n return parts.negative ? `${prefix}-${suffix}` : \"\";\n }\n\n let intDisplay = parts.int;\n if (intDisplay === \"\" && (hasFrac || parts.hasSeparator)) intDisplay = \"0\";\n\n let body = group(intDisplay, thousand);\n\n if (fixed && scale !== undefined && scale > 0) {\n body += decimal + parts.frac.slice(0, scale).padEnd(scale, \"0\");\n } else if (hasFrac) {\n body += decimal + parts.frac;\n } else if (parts.hasSeparator) {\n body += decimal; // trailing separator while typing \"1.\"\n }\n\n return `${prefix}${parts.negative ? \"-\" : \"\"}${body}${suffix}`;\n}\n\nfunction numberToString(n: number): string {\n if (!Number.isFinite(n)) return \"\";\n const s = String(n);\n if (s.indexOf(\"e\") === -1 && s.indexOf(\"E\") === -1) return s;\n // Expand exponential notation (e.g. \"1e+21\", \"1e-7\") to a plain decimal string\n // so tokenize() doesn't read the exponent's digits/\"-\" as value characters.\n // Precision is bounded by JS's own float precision; this only guards extreme\n // magnitudes reachable via a numeric value prop (typing never yields \"e\").\n const sign = n < 0 ? \"-\" : \"\";\n const abs = Math.abs(n);\n // abs >= 1 with an exponent means a huge integer (>= 1e21, where every double\n // is integer-valued); BigInt expands it without an exponent. toFixed alone\n // fails here because it also switches to exponential notation at >= 1e21.\n const expanded =\n abs >= 1\n ? BigInt(Math.trunc(abs)).toString()\n : abs.toFixed(20).replace(/0+$/, \"\").replace(/\\.$/, \"\");\n return sign + expanded;\n}\n\nexport function formatNumber(input: string | number, options: NumberFormatOptions): string {\n const parts =\n typeof input === \"number\"\n ? tokenizeCanonical(numberToString(input), options)\n : tokenize(input, options);\n return buildFormatted(parts, options);\n}\n","import type { NumberFormatOptions, NumberFormatValues } from \"./types\";\nimport { tokenize, buildFormatted, resolveSeparators, type NumberParts } from \"./format-number\";\n\nfunction buildValue(parts: NumberParts): string {\n const sign = parts.negative ? \"-\" : \"\";\n let int = parts.int;\n if (int === \"\" && (parts.frac !== \"\" || parts.hasSeparator)) int = \"0\";\n let v = sign + int;\n if (parts.frac !== \"\") v += \".\" + parts.frac;\n else if (parts.hasSeparator) v += \".\";\n return v;\n}\n\nexport function parseNumber(formatted: string, options: NumberFormatOptions): NumberFormatValues {\n const parts = tokenize(formatted, options);\n const value = buildValue(parts);\n const formattedValue = buildFormatted(parts, options);\n\n // No digit characters entered at all (e.g. \"\", \"-\", \".\", \"-.\") → no numeric value.\n const hasNoDigits = parts.int === \"\" && parts.frac === \"\";\n const parsed = parseFloat(value);\n const floatValue = hasNoDigits || Number.isNaN(parsed) ? undefined : parsed;\n\n return { value, formattedValue, floatValue };\n}\n\nexport function digitsBeforeCaret(value: string, caret: number): number {\n let count = 0;\n for (let i = 0; i < caret && i < value.length; i++) {\n if (value[i] >= \"0\" && value[i] <= \"9\") count++;\n }\n return count;\n}\n\nexport function caretAfterReformat(nextFormatted: string, digitsBefore: number): number {\n if (digitsBefore <= 0) {\n // place caret before the first digit (after any prefix/sign)\n const firstDigit = nextFormatted.search(/\\d/);\n return firstDigit === -1 ? nextFormatted.length : firstDigit;\n }\n let seen = 0;\n let i = 0;\n for (; i < nextFormatted.length; i++) {\n if (nextFormatted[i] >= \"0\" && nextFormatted[i] <= \"9\") {\n seen++;\n if (seen === digitsBefore) return i + 1;\n }\n }\n return nextFormatted.length;\n}\n\nexport function resolveCaret(\n values: NumberFormatValues,\n digitsBefore: number,\n options: NumberFormatOptions\n): number {\n const caret = caretAfterReformat(values.formattedValue, digitsBefore);\n // caretAfterReformat is digit-anchored and can't see a just-typed trailing\n // decimal separator with no fraction digit yet (e.g. \"1,234.\" right after\n // the \".\" is typed) — it lands the caret BEFORE that separator. Step past it\n // so the next keystroke goes into the fraction rather than ahead of the \".\".\n if (values.value.endsWith(\".\")) {\n const { decimal } = resolveSeparators(options);\n if (decimal && values.formattedValue.slice(caret, caret + decimal.length) === decimal) {\n return caret + decimal.length;\n }\n }\n return caret;\n}\n","import type { NumberFormatOptions } from \"./types\";\nimport { resolveSeparators } from \"./format-number\";\n\nconst warned = new Set<string>();\n\n// Dev-only guard: thousandSeparator and decimalSeparator must differ, or the\n// value cannot be parsed correctly. Mirrors src/validate-props.ts's style.\nexport function warnOnSeparatorCollision(options: NumberFormatOptions): void {\n if (process.env.NODE_ENV === \"production\") return;\n const { thousand, decimal } = resolveSeparators(options);\n if (thousand && thousand === decimal) {\n if (warned.has(thousand)) return;\n warned.add(thousand);\n console.error(\n `react-input-mask-format: thousandSeparator and decimalSeparator must differ (both are \"${thousand}\"). The value will not parse correctly.`\n );\n }\n}\n","import { useRef, useCallback } from \"react\";\nimport type React from \"react\";\nimport type { NumberFormatOptions, NumberFormatValues } from \"./types\";\nimport { formatNumber } from \"./format-number\";\nimport { parseNumber, digitsBeforeCaret, resolveCaret } from \"./parse-number\";\nimport { setNativeValue } from \"../set-native-value\";\nimport { isInputFocused, setInputSelection } from \"../utils/input\";\nimport { warnOnSeparatorCollision } from \"./validate\";\n\nexport interface UseNumberFormatOptions extends NumberFormatOptions {\n onValueChange?: (values: NumberFormatValues) => void;\n}\n\nconst IS_JSDOM =\n typeof navigator !== \"undefined\" && navigator.userAgent.includes(\"jsdom\");\n\nexport function useNumberFormat(\n options: UseNumberFormatOptions\n): React.RefCallback<HTMLInputElement> {\n warnOnSeparatorCollision(options);\n\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const inputRef = useRef<HTMLInputElement | null>(null);\n const handlerRef = useRef<((event: Event) => void) | null>(null);\n\n return useCallback((el: HTMLInputElement | null) => {\n if (el) {\n const handler = (): void => {\n const opts = optionsRef.current;\n const caret = el.selectionStart ?? el.value.length;\n const digits = digitsBeforeCaret(el.value, caret);\n const values = parseNumber(el.value, opts);\n setNativeValue(el, values.formattedValue);\n if (isInputFocused(el)) {\n const next = resolveCaret(values, digits, opts);\n setInputSelection(el, next, next);\n }\n // JSDOM-ONLY: resync @testing-library/user-event v14's shadow-value cache.\n // setNativeValue above bypasses React's value tracker; under jsdom that\n // leaves user-event's per-input shadow value stale, corrupting masking from\n // ~the 4th keystroke. A plain (idempotent) reassignment resyncs it.\n // MUST NOT run in a real browser: there, microtasks drain BETWEEN event\n // listeners, so this write would fight React; real browsers read the live\n // DOM and need no resync. onValueChange (below) is called directly, not via\n // React's tracker, so it is unaffected in either environment.\n if (IS_JSDOM) {\n queueMicrotask(() => {\n if (el.value !== values.formattedValue) return;\n el.value = values.formattedValue;\n if (isInputFocused(el)) {\n const c = resolveCaret(values, digits, opts);\n setInputSelection(el, c, c);\n }\n });\n }\n opts.onValueChange?.(values);\n };\n el.addEventListener(\"input\", handler);\n handlerRef.current = handler;\n inputRef.current = el;\n // format any initial (defaultValue) content\n if (el.value) {\n setNativeValue(el, formatNumber(el.value, optionsRef.current));\n }\n } else {\n if (inputRef.current && handlerRef.current) {\n inputRef.current.removeEventListener(\"input\", handlerRef.current);\n }\n inputRef.current = null;\n handlerRef.current = null;\n }\n }, []);\n}\n"],"mappings":"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","names":["NumberFormat","useRef","useRef"]}
@@ -1,4 +1,4 @@
1
- import { M as Mask, T as Transform, F as FormatChars } from './types-Bfu5Jmh0.cjs';
1
+ import { M as Mask, T as Transform, F as FormatChars } from './types-BTNqtchO.cjs';
2
2
  import 'react';
3
3
 
4
4
  interface MaskPreset {
package/dist/presets.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { M as Mask, T as Transform, F as FormatChars } from './types-Bfu5Jmh0.js';
1
+ import { M as Mask, T as Transform, F as FormatChars } from './types-BTNqtchO.js';
2
2
  import 'react';
3
3
 
4
4
  interface MaskPreset {
@@ -57,5 +57,21 @@ interface InputMaskProps extends Omit<React.InputHTMLAttributes<HTMLInputElement
57
57
  /** @deprecated Use beforeMaskedStateChange. */
58
58
  beforeMaskedValueChange?: BeforeMaskedValueChangeFn;
59
59
  }
60
+ interface UseMaskOptions {
61
+ /** Mask format — same grammar as InputMask's `mask`. */
62
+ mask?: Mask | null;
63
+ /** Character(s) to cover unfilled parts. Default "_", null — empty. */
64
+ maskPlaceholder?: string | null;
65
+ /** Show mask even when input is empty and unfocused. */
66
+ alwaysShowMask?: boolean;
67
+ /** Map of custom mask tokens to character classes. Prefer RegExp values. */
68
+ formatChars?: Record<string, RegExp | string>;
69
+ /** Normalize each entered character. Must be pure. */
70
+ transform?: Transform;
71
+ /** Modify masked value and cursor position before they are applied. */
72
+ beforeMaskedStateChange?: BeforeMaskedStateChangeFn;
73
+ /** @deprecated Use beforeMaskedStateChange. */
74
+ beforeMaskedValueChange?: BeforeMaskedValueChangeFn;
75
+ }
60
76
 
61
- export type { BeforeMaskedStateChangeFn as B, FormatChars as F, InputMaskProps as I, Mask as M, Selection as S, Transform as T, BeforeMaskedValueChangeFn as a, InputState as b };
77
+ export type { BeforeMaskedStateChangeFn as B, FormatChars as F, InputMaskProps as I, Mask as M, Selection as S, Transform as T, UseMaskOptions as U, BeforeMaskedValueChangeFn as a, InputState as b };
@@ -57,5 +57,21 @@ interface InputMaskProps extends Omit<React.InputHTMLAttributes<HTMLInputElement
57
57
  /** @deprecated Use beforeMaskedStateChange. */
58
58
  beforeMaskedValueChange?: BeforeMaskedValueChangeFn;
59
59
  }
60
+ interface UseMaskOptions {
61
+ /** Mask format — same grammar as InputMask's `mask`. */
62
+ mask?: Mask | null;
63
+ /** Character(s) to cover unfilled parts. Default "_", null — empty. */
64
+ maskPlaceholder?: string | null;
65
+ /** Show mask even when input is empty and unfocused. */
66
+ alwaysShowMask?: boolean;
67
+ /** Map of custom mask tokens to character classes. Prefer RegExp values. */
68
+ formatChars?: Record<string, RegExp | string>;
69
+ /** Normalize each entered character. Must be pure. */
70
+ transform?: Transform;
71
+ /** Modify masked value and cursor position before they are applied. */
72
+ beforeMaskedStateChange?: BeforeMaskedStateChangeFn;
73
+ /** @deprecated Use beforeMaskedStateChange. */
74
+ beforeMaskedValueChange?: BeforeMaskedValueChangeFn;
75
+ }
60
76
 
61
- export type { BeforeMaskedStateChangeFn as B, FormatChars as F, InputMaskProps as I, Mask as M, Selection as S, Transform as T, BeforeMaskedValueChangeFn as a, InputState as b };
77
+ export type { BeforeMaskedStateChangeFn as B, FormatChars as F, InputMaskProps as I, Mask as M, Selection as S, Transform as T, UseMaskOptions as U, BeforeMaskedValueChangeFn as a, InputState as b };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "react-input-mask-format",
3
3
  "description": "Masked input component for React",
4
- "version": "2.1.0",
4
+ "version": "2.2.0",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/Temirtator/react-input-mask-format",
7
7
  "license": "MIT",
@@ -58,6 +58,10 @@
58
58
  "import": { "types": "./dist/validators.d.ts", "default": "./dist/validators.js" },
59
59
  "require": { "types": "./dist/validators.d.cts", "default": "./dist/validators.cjs" }
60
60
  },
61
+ "./number": {
62
+ "import": { "types": "./dist/number.d.ts", "default": "./dist/number.js" },
63
+ "require": { "types": "./dist/number.d.cts", "default": "./dist/number.cjs" }
64
+ },
61
65
  "./package.json": "./package.json"
62
66
  },
63
67
  "sideEffects": false,
@@ -67,7 +71,8 @@
67
71
  "size-limit": [
68
72
  { "path": "dist/index.js", "limit": "6 KB" },
69
73
  { "path": "dist/presets.js", "limit": "2 KB" },
70
- { "path": "dist/validators.js", "limit": "2 KB" }
74
+ { "path": "dist/validators.js", "limit": "2 KB" },
75
+ { "path": "dist/number.js", "limit": "2.5 KB" }
71
76
  ],
72
77
  "scripts": {
73
78
  "build": "tsup",