numora-react 3.2.0 → 3.4.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.
package/dist/index.cjs CHANGED
@@ -62,7 +62,7 @@ function createSyntheticChangeEvent(input) {
62
62
  };
63
63
  }
64
64
  const NumoraInput = react.forwardRef((props, ref) => {
65
- const { maxDecimals = 2, onChange, onPaste, onBlur, onKeyDown, onFocus, onRawValueChange, formatOn = numora.FormatOn.Blur, thousandSeparator, thousandStyle = numora.ThousandStyle.Thousand, decimalSeparator, decimalMinLength, enableCompactNotation = false, enableNegative = false, enableLeadingZeros = false, rawValueMode = false, value: controlledValue, defaultValue, ...rest } = props;
65
+ const { maxDecimals = 2, onChange, onPaste, onBlur, onKeyDown, onFocus, onRawValueChange, locale, formatOn = numora.FormatOn.Blur, thousandSeparator, thousandStyle = numora.ThousandStyle.Thousand, decimalSeparator, decimalMinLength, enableCompactNotation = false, enableNegative = false, enableLeadingZeros = false, rawValueMode = false, value: controlledValue, defaultValue, ...rest } = props;
66
66
  numora.validateNumoraInputOptions({
67
67
  decimalMaxLength: maxDecimals,
68
68
  decimalMinLength,
@@ -81,19 +81,19 @@ const NumoraInput = react.forwardRef((props, ref) => {
81
81
  // Memoize to give callbacks a stable reference - avoids recreating all
82
82
  // useCallback functions on every render when primitive props haven't changed.
83
83
  const formattingOptions = react.useMemo(() => {
84
- const resolved = numora.resolveLocaleOptions({ thousandSeparator, thousandStyle, decimalSeparator });
84
+ const separators = numora.applyLocale(locale, { thousandSeparator, decimalSeparator });
85
85
  return {
86
86
  formatOn,
87
- thousandSeparator: resolved.thousandSeparator,
88
- ThousandStyle: resolved.thousandStyle,
89
- decimalSeparator: resolved.decimalSeparator,
87
+ thousandSeparator: separators.thousandSeparator,
88
+ ThousandStyle: thousandStyle,
89
+ decimalSeparator: separators.decimalSeparator,
90
90
  decimalMinLength,
91
91
  enableCompactNotation,
92
92
  enableNegative,
93
93
  enableLeadingZeros,
94
94
  rawValueMode,
95
95
  };
96
- }, [formatOn, thousandSeparator, thousandStyle, decimalSeparator, decimalMinLength,
96
+ }, [locale, formatOn, thousandSeparator, thousandStyle, decimalSeparator, decimalMinLength,
97
97
  enableCompactNotation, enableNegative, enableLeadingZeros, rawValueMode]);
98
98
  const getInitialValue = () => {
99
99
  const valueToFormat = controlledValue !== undefined ? controlledValue : defaultValue;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../src/handlers.ts","../src/index.tsx"],"sourcesContent":["import type React from 'react';\nimport {\n handleOnChangeNumoraInput,\n handleOnKeyDownNumoraInput,\n handleOnPasteNumoraInput,\n formatValueForDisplay,\n type CaretPositionInfo,\n type FormattingOptions,\n FormatOn,\n} from 'numora';\n\ntype ChangeResult = {\n value: string;\n rawValue?: string;\n};\n\ntype PasteResult = ChangeResult;\ntype BlurResult = ChangeResult;\n\ntype BaseOptions = {\n decimalMaxLength: number;\n caretPositionBeforeChange?: CaretPositionInfo;\n formattingOptions: FormattingOptions & { rawValueMode?: boolean };\n};\n\nexport function handleNumoraOnChange(\n e: React.ChangeEvent<HTMLInputElement>,\n options: BaseOptions\n): ChangeResult {\n const { formatted, raw } = handleOnChangeNumoraInput(\n e.nativeEvent as unknown as Event,\n options.decimalMaxLength,\n options.caretPositionBeforeChange,\n options.formattingOptions\n );\n\n return {\n value: formatted,\n rawValue: raw,\n };\n}\n\nexport function handleNumoraOnPaste(\n e: React.ClipboardEvent<HTMLInputElement>,\n options: Omit<BaseOptions, 'caretPositionBeforeChange'>\n): PasteResult {\n const { formatted, raw } = handleOnPasteNumoraInput(\n e.nativeEvent as ClipboardEvent,\n options.decimalMaxLength,\n options.formattingOptions\n );\n\n return {\n value: formatted,\n rawValue: raw,\n };\n}\n\nexport function handleNumoraOnKeyDown(\n e: React.KeyboardEvent<HTMLInputElement>,\n formattingOptions: FormattingOptions\n): CaretPositionInfo | undefined {\n return handleOnKeyDownNumoraInput(\n e.nativeEvent as unknown as KeyboardEvent,\n formattingOptions\n );\n}\n\nexport function handleNumoraOnBlur(\n e: React.FocusEvent<HTMLInputElement>,\n options: {\n decimalMaxLength: number;\n formattingOptions: FormattingOptions & { rawValueMode?: boolean };\n }\n): BlurResult {\n if (options.formattingOptions.formatOn === FormatOn.Blur) {\n const { formatted, raw } = formatValueForDisplay(\n e.target.value,\n options.decimalMaxLength,\n { ...options.formattingOptions, formatOn: FormatOn.Change }\n );\n return {\n value: formatted,\n rawValue: raw,\n };\n }\n\n return {\n value: e.target.value,\n rawValue: undefined,\n };\n}\n","import {\n useRef,\n useState,\n useEffect,\n useLayoutEffect,\n forwardRef,\n useCallback,\n useMemo,\n ClipboardEvent,\n ChangeEvent,\n FocusEvent,\n KeyboardEvent,\n InputHTMLAttributes\n} from 'react';\nimport {\n FormatOn,\n ThousandStyle,\n resolveLocaleOptions,\n formatValueForDisplay,\n removeThousandSeparators,\n validateNumoraInputOptions,\n type CaretPositionInfo,\n type FormattingOptions,\n} from 'numora';\nimport {\n handleNumoraOnBlur,\n handleNumoraOnChange,\n handleNumoraOnKeyDown,\n handleNumoraOnPaste,\n} from './handlers';\n\nexport interface NumoraHTMLInputElement extends HTMLInputElement {\n rawValue?: string;\n}\n\nexport type NumoraInputChangeEvent = Omit<ChangeEvent<HTMLInputElement>, 'target'> & {\n target: NumoraHTMLInputElement;\n};\n\n/**\n * Creates a complete synthetic change event from a real HTMLInputElement.\n * Used when a change needs to be signalled without an actual DOM change event\n * (e.g. after paste with preventDefault, or after a controlled-value reformat).\n */\nfunction createSyntheticChangeEvent(input: HTMLInputElement): NumoraInputChangeEvent {\n const nativeEvent = new Event('change', { bubbles: true, cancelable: false });\n return {\n nativeEvent,\n target: input as NumoraHTMLInputElement,\n currentTarget: input,\n type: 'change',\n bubbles: true,\n cancelable: false,\n defaultPrevented: false,\n eventPhase: Event.AT_TARGET,\n isTrusted: false,\n timeStamp: Date.now(),\n isDefaultPrevented: () => false,\n isPropagationStopped: () => false,\n persist: () => {},\n preventDefault: () => {},\n stopPropagation: () => {},\n stopImmediatePropagation: () => {},\n } as unknown as NumoraInputChangeEvent;\n}\n\nexport interface NumoraInputProps\n extends Omit<\n InputHTMLAttributes<HTMLInputElement>,\n 'onChange' | 'type' | 'inputMode' | 'onFocus' | 'onBlur'\n > {\n maxDecimals?: number;\n onChange?: (e: NumoraInputChangeEvent) => void;\n onFocus?: (e: FocusEvent<HTMLInputElement>) => void;\n onBlur?: (e: FocusEvent<HTMLInputElement>) => void;\n /** Called with the raw (unformatted) numeric string on every value change. */\n onRawValueChange?: (rawValue: string | undefined) => void;\n\n formatOn?: FormatOn;\n thousandSeparator?: string;\n thousandStyle?: ThousandStyle;\n decimalSeparator?: string;\n decimalMinLength?: number;\n\n enableCompactNotation?: boolean;\n enableNegative?: boolean;\n enableLeadingZeros?: boolean;\n rawValueMode?: boolean;\n}\n\nconst NumoraInput = forwardRef<HTMLInputElement, NumoraInputProps>((props, ref) => {\n const {\n maxDecimals = 2,\n onChange,\n onPaste,\n onBlur,\n onKeyDown,\n onFocus,\n onRawValueChange,\n formatOn = FormatOn.Blur,\n thousandSeparator,\n thousandStyle = ThousandStyle.Thousand,\n decimalSeparator,\n decimalMinLength,\n enableCompactNotation = false,\n enableNegative = false,\n enableLeadingZeros = false,\n rawValueMode = false,\n value: controlledValue,\n defaultValue,\n ...rest\n } = props;\n\n validateNumoraInputOptions({\n decimalMaxLength: maxDecimals,\n decimalMinLength,\n formatOn,\n thousandSeparator,\n thousandStyle,\n decimalSeparator,\n enableCompactNotation,\n enableNegative,\n enableLeadingZeros,\n rawValueMode,\n });\n\n const internalInputRef = useRef<HTMLInputElement>(null);\n const caretInfoRef = useRef<CaretPositionInfo | undefined>(undefined);\n const lastCaretPosRef = useRef<number | null>(null);\n\n // Memoize to give callbacks a stable reference - avoids recreating all\n // useCallback functions on every render when primitive props haven't changed.\n const formattingOptions: FormattingOptions = useMemo(() => {\n const resolved = resolveLocaleOptions({ thousandSeparator, thousandStyle, decimalSeparator });\n\n return {\n formatOn,\n thousandSeparator: resolved.thousandSeparator,\n ThousandStyle: resolved.thousandStyle,\n decimalSeparator: resolved.decimalSeparator,\n decimalMinLength,\n enableCompactNotation,\n enableNegative,\n enableLeadingZeros,\n rawValueMode,\n };\n }, [formatOn, thousandSeparator, thousandStyle, decimalSeparator, decimalMinLength,\n enableCompactNotation, enableNegative, enableLeadingZeros, rawValueMode]);\n\n const getInitialValue = (): string => {\n const valueToFormat = controlledValue !== undefined ? controlledValue : defaultValue;\n if (valueToFormat !== undefined) {\n const { formatted } = formatValueForDisplay(String(valueToFormat), maxDecimals, formattingOptions);\n return formatted;\n }\n return '';\n };\n\n const [displayValue, setDisplayValue] = useState<string>(getInitialValue);\n\n // Track the current displayValue via a ref so the controlled-value useEffect\n // can compare against it without adding displayValue as a dependency (which\n // would cause the effect to re-run on every keystroke).\n const displayValueRef = useRef<string>(displayValue);\n displayValueRef.current = displayValue;\n\n // Sync external ref with internal ref\n useLayoutEffect(() => {\n if (!ref) return;\n if (typeof ref === 'function') {\n ref(internalInputRef.current);\n } else {\n ref.current = internalInputRef.current;\n }\n }, [ref]);\n\n // When the controlled value or formatting options change, reformat the display.\n // Uses displayValueRef (not displayValue in deps) to avoid re-running on every keystroke.\n // Does NOT call onChange - that would create a circular loop with react-hook-form Controller.\n useEffect(() => {\n if (controlledValue !== undefined) {\n const { formatted, raw } = formatValueForDisplay(String(controlledValue), maxDecimals, formattingOptions);\n if (formatted !== displayValueRef.current) {\n setDisplayValue(formatted);\n\n if (internalInputRef.current) {\n (internalInputRef.current as NumoraHTMLInputElement).rawValue = raw;\n }\n onRawValueChange?.(raw);\n }\n }\n }, [controlledValue, maxDecimals, formattingOptions, onRawValueChange]);\n\n // Restore cursor position after render.\n // No dependency array is intentional: this must run after every render so it catches\n // the re-render triggered by setDisplayValue in handleChange/handlePaste.\n // lastCaretPosRef is a ref (not reactive), so it cannot be a dependency.\n useLayoutEffect(() => {\n if (internalInputRef.current && lastCaretPosRef.current !== null) {\n const input = internalInputRef.current;\n const pos = lastCaretPosRef.current;\n input.setSelectionRange(pos, pos);\n lastCaretPosRef.current = null;\n }\n });\n\n const handleChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {\n const { value, rawValue } = handleNumoraOnChange(e, {\n decimalMaxLength: maxDecimals,\n caretPositionBeforeChange: caretInfoRef.current,\n formattingOptions,\n });\n\n if (internalInputRef.current) {\n const cursorPos = internalInputRef.current.selectionStart;\n if (cursorPos !== null && cursorPos !== undefined) {\n lastCaretPosRef.current = cursorPos;\n }\n }\n\n caretInfoRef.current = undefined;\n\n (e.target as NumoraHTMLInputElement).rawValue = rawValue;\n\n onRawValueChange?.(rawValue);\n\n setDisplayValue(value);\n\n if (onChange) {\n onChange(e as unknown as NumoraInputChangeEvent);\n }\n }, [maxDecimals, formattingOptions, onChange, onRawValueChange]);\n\n const handleKeyDown = useCallback((e: KeyboardEvent<HTMLInputElement>) => {\n const coreCaretInfo = handleNumoraOnKeyDown(e, formattingOptions);\n\n if (!coreCaretInfo && internalInputRef.current) {\n const selectionStart = internalInputRef.current.selectionStart ?? 0;\n const selectionEnd = internalInputRef.current.selectionEnd ?? 0;\n caretInfoRef.current = {\n selectionStart,\n selectionEnd,\n };\n } else {\n caretInfoRef.current = coreCaretInfo;\n }\n\n if (onKeyDown) {\n onKeyDown(e);\n }\n }, [formattingOptions, onKeyDown]);\n\n const handlePaste = useCallback((e: ClipboardEvent<HTMLInputElement>) => {\n const { value, rawValue } = handleNumoraOnPaste(e, {\n decimalMaxLength: maxDecimals,\n formattingOptions,\n });\n\n lastCaretPosRef.current = (e.target as HTMLInputElement).selectionStart;\n (e.target as NumoraHTMLInputElement).rawValue = rawValue;\n\n onRawValueChange?.(rawValue);\n\n setDisplayValue(value);\n\n if (onPaste) {\n onPaste(e);\n }\n\n // Paste calls e.preventDefault() internally, so React's onChange never fires.\n // We synthesise a proper change event so consumers see a typed ChangeEvent.\n if (onChange) {\n onChange(createSyntheticChangeEvent(e.target as HTMLInputElement));\n }\n }, [maxDecimals, formattingOptions, onPaste, onChange, onRawValueChange]);\n\n const handleFocus = useCallback((e: FocusEvent<HTMLInputElement>) => {\n if (\n formattingOptions.formatOn === FormatOn.Blur &&\n formattingOptions.thousandSeparator &&\n formattingOptions.ThousandStyle !== ThousandStyle.None\n ) {\n // Read directly from the DOM element to avoid a stale displayValue closure\n // and to eliminate displayValue from the deps array (which would recreate\n // this callback on every keystroke).\n const currentValue = (e.target as HTMLInputElement).value;\n setDisplayValue(removeThousandSeparators(currentValue, formattingOptions.thousandSeparator!));\n }\n\n if (onFocus) {\n onFocus(e);\n }\n }, [formattingOptions, onFocus]);\n\n const handleBlur = useCallback((e: FocusEvent<HTMLInputElement>) => {\n const { value, rawValue } = handleNumoraOnBlur(e, {\n decimalMaxLength: maxDecimals,\n formattingOptions,\n });\n\n (e.target as NumoraHTMLInputElement).rawValue = rawValue;\n\n onRawValueChange?.(rawValue);\n setDisplayValue(value);\n\n if (onBlur) {\n onBlur(e);\n }\n }, [maxDecimals, formattingOptions, onBlur, onRawValueChange]);\n\n return (\n <input\n {...rest}\n ref={internalInputRef}\n value={displayValue}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n onPaste={handlePaste}\n onFocus={handleFocus}\n onBlur={handleBlur}\n type=\"text\"\n inputMode=\"decimal\"\n spellCheck={false}\n autoComplete=\"off\"\n />\n );\n});\n\nNumoraInput.displayName = 'NumoraInput';\n\nexport { NumoraInput };\nexport { FormatOn, ThousandStyle } from 'numora';\nexport type { FormattingOptions, CaretPositionInfo } from 'numora';\n"],"names":["handleOnChangeNumoraInput","handleOnPasteNumoraInput","handleOnKeyDownNumoraInput","FormatOn","formatValueForDisplay","forwardRef","ThousandStyle","validateNumoraInputOptions","useRef","useMemo","resolveLocaleOptions","useState","useLayoutEffect","useEffect","useCallback","removeThousandSeparators","_jsx"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../src/handlers.ts","../src/index.tsx"],"sourcesContent":["import type React from 'react';\nimport {\n handleOnChangeNumoraInput,\n handleOnKeyDownNumoraInput,\n handleOnPasteNumoraInput,\n formatValueForDisplay,\n type CaretPositionInfo,\n type FormattingOptions,\n FormatOn,\n} from 'numora';\n\ntype ChangeResult = {\n value: string;\n rawValue?: string;\n};\n\ntype PasteResult = ChangeResult;\ntype BlurResult = ChangeResult;\n\ntype BaseOptions = {\n decimalMaxLength: number;\n caretPositionBeforeChange?: CaretPositionInfo;\n formattingOptions: FormattingOptions & { rawValueMode?: boolean };\n};\n\nexport function handleNumoraOnChange(\n e: React.ChangeEvent<HTMLInputElement>,\n options: BaseOptions\n): ChangeResult {\n const { formatted, raw } = handleOnChangeNumoraInput(\n e.nativeEvent as unknown as Event,\n options.decimalMaxLength,\n options.caretPositionBeforeChange,\n options.formattingOptions\n );\n\n return {\n value: formatted,\n rawValue: raw,\n };\n}\n\nexport function handleNumoraOnPaste(\n e: React.ClipboardEvent<HTMLInputElement>,\n options: Omit<BaseOptions, 'caretPositionBeforeChange'>\n): PasteResult {\n const { formatted, raw } = handleOnPasteNumoraInput(\n e.nativeEvent as ClipboardEvent,\n options.decimalMaxLength,\n options.formattingOptions\n );\n\n return {\n value: formatted,\n rawValue: raw,\n };\n}\n\nexport function handleNumoraOnKeyDown(\n e: React.KeyboardEvent<HTMLInputElement>,\n formattingOptions: FormattingOptions\n): CaretPositionInfo | undefined {\n return handleOnKeyDownNumoraInput(\n e.nativeEvent as unknown as KeyboardEvent,\n formattingOptions\n );\n}\n\nexport function handleNumoraOnBlur(\n e: React.FocusEvent<HTMLInputElement>,\n options: {\n decimalMaxLength: number;\n formattingOptions: FormattingOptions & { rawValueMode?: boolean };\n }\n): BlurResult {\n if (options.formattingOptions.formatOn === FormatOn.Blur) {\n const { formatted, raw } = formatValueForDisplay(\n e.target.value,\n options.decimalMaxLength,\n { ...options.formattingOptions, formatOn: FormatOn.Change }\n );\n return {\n value: formatted,\n rawValue: raw,\n };\n }\n\n return {\n value: e.target.value,\n rawValue: undefined,\n };\n}\n","import {\n useRef,\n useState,\n useEffect,\n useLayoutEffect,\n forwardRef,\n useCallback,\n useMemo,\n ClipboardEvent,\n ChangeEvent,\n FocusEvent,\n KeyboardEvent,\n InputHTMLAttributes\n} from 'react';\nimport {\n FormatOn,\n ThousandStyle,\n applyLocale,\n formatValueForDisplay,\n removeThousandSeparators,\n validateNumoraInputOptions,\n type CaretPositionInfo,\n type FormattingOptions,\n} from 'numora';\nimport {\n handleNumoraOnBlur,\n handleNumoraOnChange,\n handleNumoraOnKeyDown,\n handleNumoraOnPaste,\n} from './handlers';\n\nexport interface NumoraHTMLInputElement extends HTMLInputElement {\n rawValue?: string;\n}\n\nexport type NumoraInputChangeEvent = Omit<ChangeEvent<HTMLInputElement>, 'target'> & {\n target: NumoraHTMLInputElement;\n};\n\n/**\n * Creates a complete synthetic change event from a real HTMLInputElement.\n * Used when a change needs to be signalled without an actual DOM change event\n * (e.g. after paste with preventDefault, or after a controlled-value reformat).\n */\nfunction createSyntheticChangeEvent(input: HTMLInputElement): NumoraInputChangeEvent {\n const nativeEvent = new Event('change', { bubbles: true, cancelable: false });\n return {\n nativeEvent,\n target: input as NumoraHTMLInputElement,\n currentTarget: input,\n type: 'change',\n bubbles: true,\n cancelable: false,\n defaultPrevented: false,\n eventPhase: Event.AT_TARGET,\n isTrusted: false,\n timeStamp: Date.now(),\n isDefaultPrevented: () => false,\n isPropagationStopped: () => false,\n persist: () => {},\n preventDefault: () => {},\n stopPropagation: () => {},\n stopImmediatePropagation: () => {},\n } as unknown as NumoraInputChangeEvent;\n}\n\nexport interface NumoraInputProps\n extends Omit<\n InputHTMLAttributes<HTMLInputElement>,\n 'onChange' | 'type' | 'inputMode' | 'onFocus' | 'onBlur'\n > {\n maxDecimals?: number;\n onChange?: (e: NumoraInputChangeEvent) => void;\n onFocus?: (e: FocusEvent<HTMLInputElement>) => void;\n onBlur?: (e: FocusEvent<HTMLInputElement>) => void;\n /** Called with the raw (unformatted) numeric string on every value change. */\n onRawValueChange?: (rawValue: string | undefined) => void;\n\n locale?: string | true;\n formatOn?: FormatOn;\n thousandSeparator?: string;\n thousandStyle?: ThousandStyle;\n decimalSeparator?: string;\n decimalMinLength?: number;\n\n enableCompactNotation?: boolean;\n enableNegative?: boolean;\n enableLeadingZeros?: boolean;\n rawValueMode?: boolean;\n}\n\nconst NumoraInput = forwardRef<HTMLInputElement, NumoraInputProps>((props, ref) => {\n const {\n maxDecimals = 2,\n onChange,\n onPaste,\n onBlur,\n onKeyDown,\n onFocus,\n onRawValueChange,\n locale,\n formatOn = FormatOn.Blur,\n thousandSeparator,\n thousandStyle = ThousandStyle.Thousand,\n decimalSeparator,\n decimalMinLength,\n enableCompactNotation = false,\n enableNegative = false,\n enableLeadingZeros = false,\n rawValueMode = false,\n value: controlledValue,\n defaultValue,\n ...rest\n } = props;\n\n validateNumoraInputOptions({\n decimalMaxLength: maxDecimals,\n decimalMinLength,\n formatOn,\n thousandSeparator,\n thousandStyle,\n decimalSeparator,\n enableCompactNotation,\n enableNegative,\n enableLeadingZeros,\n rawValueMode,\n });\n\n const internalInputRef = useRef<HTMLInputElement>(null);\n const caretInfoRef = useRef<CaretPositionInfo | undefined>(undefined);\n const lastCaretPosRef = useRef<number | null>(null);\n\n // Memoize to give callbacks a stable reference - avoids recreating all\n // useCallback functions on every render when primitive props haven't changed.\n const formattingOptions: FormattingOptions = useMemo(() => {\n const separators = applyLocale(locale, { thousandSeparator, decimalSeparator });\n\n return {\n formatOn,\n thousandSeparator: separators.thousandSeparator,\n ThousandStyle: thousandStyle,\n decimalSeparator: separators.decimalSeparator,\n decimalMinLength,\n enableCompactNotation,\n enableNegative,\n enableLeadingZeros,\n rawValueMode,\n };\n }, [locale, formatOn, thousandSeparator, thousandStyle, decimalSeparator, decimalMinLength,\n enableCompactNotation, enableNegative, enableLeadingZeros, rawValueMode]);\n\n const getInitialValue = (): string => {\n const valueToFormat = controlledValue !== undefined ? controlledValue : defaultValue;\n if (valueToFormat !== undefined) {\n const { formatted } = formatValueForDisplay(String(valueToFormat), maxDecimals, formattingOptions);\n return formatted;\n }\n return '';\n };\n\n const [displayValue, setDisplayValue] = useState<string>(getInitialValue);\n\n // Track the current displayValue via a ref so the controlled-value useEffect\n // can compare against it without adding displayValue as a dependency (which\n // would cause the effect to re-run on every keystroke).\n const displayValueRef = useRef<string>(displayValue);\n displayValueRef.current = displayValue;\n\n // Sync external ref with internal ref\n useLayoutEffect(() => {\n if (!ref) return;\n if (typeof ref === 'function') {\n ref(internalInputRef.current);\n } else {\n ref.current = internalInputRef.current;\n }\n }, [ref]);\n\n // When the controlled value or formatting options change, reformat the display.\n // Uses displayValueRef (not displayValue in deps) to avoid re-running on every keystroke.\n // Does NOT call onChange - that would create a circular loop with react-hook-form Controller.\n useEffect(() => {\n if (controlledValue !== undefined) {\n const { formatted, raw } = formatValueForDisplay(String(controlledValue), maxDecimals, formattingOptions);\n if (formatted !== displayValueRef.current) {\n setDisplayValue(formatted);\n\n if (internalInputRef.current) {\n (internalInputRef.current as NumoraHTMLInputElement).rawValue = raw;\n }\n onRawValueChange?.(raw);\n }\n }\n }, [controlledValue, maxDecimals, formattingOptions, onRawValueChange]);\n\n // Restore cursor position after render.\n // No dependency array is intentional: this must run after every render so it catches\n // the re-render triggered by setDisplayValue in handleChange/handlePaste.\n // lastCaretPosRef is a ref (not reactive), so it cannot be a dependency.\n useLayoutEffect(() => {\n if (internalInputRef.current && lastCaretPosRef.current !== null) {\n const input = internalInputRef.current;\n const pos = lastCaretPosRef.current;\n input.setSelectionRange(pos, pos);\n lastCaretPosRef.current = null;\n }\n });\n\n const handleChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {\n const { value, rawValue } = handleNumoraOnChange(e, {\n decimalMaxLength: maxDecimals,\n caretPositionBeforeChange: caretInfoRef.current,\n formattingOptions,\n });\n\n if (internalInputRef.current) {\n const cursorPos = internalInputRef.current.selectionStart;\n if (cursorPos !== null && cursorPos !== undefined) {\n lastCaretPosRef.current = cursorPos;\n }\n }\n\n caretInfoRef.current = undefined;\n\n (e.target as NumoraHTMLInputElement).rawValue = rawValue;\n\n onRawValueChange?.(rawValue);\n\n setDisplayValue(value);\n\n if (onChange) {\n onChange(e as unknown as NumoraInputChangeEvent);\n }\n }, [maxDecimals, formattingOptions, onChange, onRawValueChange]);\n\n const handleKeyDown = useCallback((e: KeyboardEvent<HTMLInputElement>) => {\n const coreCaretInfo = handleNumoraOnKeyDown(e, formattingOptions);\n\n if (!coreCaretInfo && internalInputRef.current) {\n const selectionStart = internalInputRef.current.selectionStart ?? 0;\n const selectionEnd = internalInputRef.current.selectionEnd ?? 0;\n caretInfoRef.current = {\n selectionStart,\n selectionEnd,\n };\n } else {\n caretInfoRef.current = coreCaretInfo;\n }\n\n if (onKeyDown) {\n onKeyDown(e);\n }\n }, [formattingOptions, onKeyDown]);\n\n const handlePaste = useCallback((e: ClipboardEvent<HTMLInputElement>) => {\n const { value, rawValue } = handleNumoraOnPaste(e, {\n decimalMaxLength: maxDecimals,\n formattingOptions,\n });\n\n lastCaretPosRef.current = (e.target as HTMLInputElement).selectionStart;\n (e.target as NumoraHTMLInputElement).rawValue = rawValue;\n\n onRawValueChange?.(rawValue);\n\n setDisplayValue(value);\n\n if (onPaste) {\n onPaste(e);\n }\n\n // Paste calls e.preventDefault() internally, so React's onChange never fires.\n // We synthesise a proper change event so consumers see a typed ChangeEvent.\n if (onChange) {\n onChange(createSyntheticChangeEvent(e.target as HTMLInputElement));\n }\n }, [maxDecimals, formattingOptions, onPaste, onChange, onRawValueChange]);\n\n const handleFocus = useCallback((e: FocusEvent<HTMLInputElement>) => {\n if (\n formattingOptions.formatOn === FormatOn.Blur &&\n formattingOptions.thousandSeparator &&\n formattingOptions.ThousandStyle !== ThousandStyle.None\n ) {\n // Read directly from the DOM element to avoid a stale displayValue closure\n // and to eliminate displayValue from the deps array (which would recreate\n // this callback on every keystroke).\n const currentValue = (e.target as HTMLInputElement).value;\n setDisplayValue(removeThousandSeparators(currentValue, formattingOptions.thousandSeparator!));\n }\n\n if (onFocus) {\n onFocus(e);\n }\n }, [formattingOptions, onFocus]);\n\n const handleBlur = useCallback((e: FocusEvent<HTMLInputElement>) => {\n const { value, rawValue } = handleNumoraOnBlur(e, {\n decimalMaxLength: maxDecimals,\n formattingOptions,\n });\n\n (e.target as NumoraHTMLInputElement).rawValue = rawValue;\n\n onRawValueChange?.(rawValue);\n setDisplayValue(value);\n\n if (onBlur) {\n onBlur(e);\n }\n }, [maxDecimals, formattingOptions, onBlur, onRawValueChange]);\n\n return (\n <input\n {...rest}\n ref={internalInputRef}\n value={displayValue}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n onPaste={handlePaste}\n onFocus={handleFocus}\n onBlur={handleBlur}\n type=\"text\"\n inputMode=\"decimal\"\n spellCheck={false}\n autoComplete=\"off\"\n />\n );\n});\n\nNumoraInput.displayName = 'NumoraInput';\n\nexport { NumoraInput };\nexport { FormatOn, ThousandStyle } from 'numora';\nexport type { FormattingOptions, CaretPositionInfo } from 'numora';\n"],"names":["handleOnChangeNumoraInput","handleOnPasteNumoraInput","handleOnKeyDownNumoraInput","FormatOn","formatValueForDisplay","forwardRef","ThousandStyle","validateNumoraInputOptions","useRef","useMemo","applyLocale","useState","useLayoutEffect","useEffect","useCallback","removeThousandSeparators","_jsx"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -13,6 +13,7 @@ export interface NumoraInputProps extends Omit<InputHTMLAttributes<HTMLInputElem
13
13
  onBlur?: (e: FocusEvent<HTMLInputElement>) => void;
14
14
  /** Called with the raw (unformatted) numeric string on every value change. */
15
15
  onRawValueChange?: (rawValue: string | undefined) => void;
16
+ locale?: string | true;
16
17
  formatOn?: FormatOn;
17
18
  thousandSeparator?: string;
18
19
  thousandStyle?: ThousandStyle;
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import { forwardRef, useRef, useMemo, useState, useLayoutEffect, useEffect, useCallback } from 'react';
3
- import { handleOnChangeNumoraInput, handleOnKeyDownNumoraInput, handleOnPasteNumoraInput, FormatOn, formatValueForDisplay, ThousandStyle, validateNumoraInputOptions, resolveLocaleOptions, removeThousandSeparators } from 'numora';
3
+ import { handleOnChangeNumoraInput, handleOnKeyDownNumoraInput, handleOnPasteNumoraInput, FormatOn, formatValueForDisplay, ThousandStyle, validateNumoraInputOptions, applyLocale, removeThousandSeparators } from 'numora';
4
4
  export { FormatOn, ThousandStyle } from 'numora';
5
5
 
6
6
  function handleNumoraOnChange(e, options) {
@@ -61,7 +61,7 @@ function createSyntheticChangeEvent(input) {
61
61
  };
62
62
  }
63
63
  const NumoraInput = forwardRef((props, ref) => {
64
- const { maxDecimals = 2, onChange, onPaste, onBlur, onKeyDown, onFocus, onRawValueChange, formatOn = FormatOn.Blur, thousandSeparator, thousandStyle = ThousandStyle.Thousand, decimalSeparator, decimalMinLength, enableCompactNotation = false, enableNegative = false, enableLeadingZeros = false, rawValueMode = false, value: controlledValue, defaultValue, ...rest } = props;
64
+ const { maxDecimals = 2, onChange, onPaste, onBlur, onKeyDown, onFocus, onRawValueChange, locale, formatOn = FormatOn.Blur, thousandSeparator, thousandStyle = ThousandStyle.Thousand, decimalSeparator, decimalMinLength, enableCompactNotation = false, enableNegative = false, enableLeadingZeros = false, rawValueMode = false, value: controlledValue, defaultValue, ...rest } = props;
65
65
  validateNumoraInputOptions({
66
66
  decimalMaxLength: maxDecimals,
67
67
  decimalMinLength,
@@ -80,19 +80,19 @@ const NumoraInput = forwardRef((props, ref) => {
80
80
  // Memoize to give callbacks a stable reference - avoids recreating all
81
81
  // useCallback functions on every render when primitive props haven't changed.
82
82
  const formattingOptions = useMemo(() => {
83
- const resolved = resolveLocaleOptions({ thousandSeparator, thousandStyle, decimalSeparator });
83
+ const separators = applyLocale(locale, { thousandSeparator, decimalSeparator });
84
84
  return {
85
85
  formatOn,
86
- thousandSeparator: resolved.thousandSeparator,
87
- ThousandStyle: resolved.thousandStyle,
88
- decimalSeparator: resolved.decimalSeparator,
86
+ thousandSeparator: separators.thousandSeparator,
87
+ ThousandStyle: thousandStyle,
88
+ decimalSeparator: separators.decimalSeparator,
89
89
  decimalMinLength,
90
90
  enableCompactNotation,
91
91
  enableNegative,
92
92
  enableLeadingZeros,
93
93
  rawValueMode,
94
94
  };
95
- }, [formatOn, thousandSeparator, thousandStyle, decimalSeparator, decimalMinLength,
95
+ }, [locale, formatOn, thousandSeparator, thousandStyle, decimalSeparator, decimalMinLength,
96
96
  enableCompactNotation, enableNegative, enableLeadingZeros, rawValueMode]);
97
97
  const getInitialValue = () => {
98
98
  const valueToFormat = controlledValue !== undefined ? controlledValue : defaultValue;
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../src/handlers.ts","../src/index.tsx"],"sourcesContent":["import type React from 'react';\nimport {\n handleOnChangeNumoraInput,\n handleOnKeyDownNumoraInput,\n handleOnPasteNumoraInput,\n formatValueForDisplay,\n type CaretPositionInfo,\n type FormattingOptions,\n FormatOn,\n} from 'numora';\n\ntype ChangeResult = {\n value: string;\n rawValue?: string;\n};\n\ntype PasteResult = ChangeResult;\ntype BlurResult = ChangeResult;\n\ntype BaseOptions = {\n decimalMaxLength: number;\n caretPositionBeforeChange?: CaretPositionInfo;\n formattingOptions: FormattingOptions & { rawValueMode?: boolean };\n};\n\nexport function handleNumoraOnChange(\n e: React.ChangeEvent<HTMLInputElement>,\n options: BaseOptions\n): ChangeResult {\n const { formatted, raw } = handleOnChangeNumoraInput(\n e.nativeEvent as unknown as Event,\n options.decimalMaxLength,\n options.caretPositionBeforeChange,\n options.formattingOptions\n );\n\n return {\n value: formatted,\n rawValue: raw,\n };\n}\n\nexport function handleNumoraOnPaste(\n e: React.ClipboardEvent<HTMLInputElement>,\n options: Omit<BaseOptions, 'caretPositionBeforeChange'>\n): PasteResult {\n const { formatted, raw } = handleOnPasteNumoraInput(\n e.nativeEvent as ClipboardEvent,\n options.decimalMaxLength,\n options.formattingOptions\n );\n\n return {\n value: formatted,\n rawValue: raw,\n };\n}\n\nexport function handleNumoraOnKeyDown(\n e: React.KeyboardEvent<HTMLInputElement>,\n formattingOptions: FormattingOptions\n): CaretPositionInfo | undefined {\n return handleOnKeyDownNumoraInput(\n e.nativeEvent as unknown as KeyboardEvent,\n formattingOptions\n );\n}\n\nexport function handleNumoraOnBlur(\n e: React.FocusEvent<HTMLInputElement>,\n options: {\n decimalMaxLength: number;\n formattingOptions: FormattingOptions & { rawValueMode?: boolean };\n }\n): BlurResult {\n if (options.formattingOptions.formatOn === FormatOn.Blur) {\n const { formatted, raw } = formatValueForDisplay(\n e.target.value,\n options.decimalMaxLength,\n { ...options.formattingOptions, formatOn: FormatOn.Change }\n );\n return {\n value: formatted,\n rawValue: raw,\n };\n }\n\n return {\n value: e.target.value,\n rawValue: undefined,\n };\n}\n","import {\n useRef,\n useState,\n useEffect,\n useLayoutEffect,\n forwardRef,\n useCallback,\n useMemo,\n ClipboardEvent,\n ChangeEvent,\n FocusEvent,\n KeyboardEvent,\n InputHTMLAttributes\n} from 'react';\nimport {\n FormatOn,\n ThousandStyle,\n resolveLocaleOptions,\n formatValueForDisplay,\n removeThousandSeparators,\n validateNumoraInputOptions,\n type CaretPositionInfo,\n type FormattingOptions,\n} from 'numora';\nimport {\n handleNumoraOnBlur,\n handleNumoraOnChange,\n handleNumoraOnKeyDown,\n handleNumoraOnPaste,\n} from './handlers';\n\nexport interface NumoraHTMLInputElement extends HTMLInputElement {\n rawValue?: string;\n}\n\nexport type NumoraInputChangeEvent = Omit<ChangeEvent<HTMLInputElement>, 'target'> & {\n target: NumoraHTMLInputElement;\n};\n\n/**\n * Creates a complete synthetic change event from a real HTMLInputElement.\n * Used when a change needs to be signalled without an actual DOM change event\n * (e.g. after paste with preventDefault, or after a controlled-value reformat).\n */\nfunction createSyntheticChangeEvent(input: HTMLInputElement): NumoraInputChangeEvent {\n const nativeEvent = new Event('change', { bubbles: true, cancelable: false });\n return {\n nativeEvent,\n target: input as NumoraHTMLInputElement,\n currentTarget: input,\n type: 'change',\n bubbles: true,\n cancelable: false,\n defaultPrevented: false,\n eventPhase: Event.AT_TARGET,\n isTrusted: false,\n timeStamp: Date.now(),\n isDefaultPrevented: () => false,\n isPropagationStopped: () => false,\n persist: () => {},\n preventDefault: () => {},\n stopPropagation: () => {},\n stopImmediatePropagation: () => {},\n } as unknown as NumoraInputChangeEvent;\n}\n\nexport interface NumoraInputProps\n extends Omit<\n InputHTMLAttributes<HTMLInputElement>,\n 'onChange' | 'type' | 'inputMode' | 'onFocus' | 'onBlur'\n > {\n maxDecimals?: number;\n onChange?: (e: NumoraInputChangeEvent) => void;\n onFocus?: (e: FocusEvent<HTMLInputElement>) => void;\n onBlur?: (e: FocusEvent<HTMLInputElement>) => void;\n /** Called with the raw (unformatted) numeric string on every value change. */\n onRawValueChange?: (rawValue: string | undefined) => void;\n\n formatOn?: FormatOn;\n thousandSeparator?: string;\n thousandStyle?: ThousandStyle;\n decimalSeparator?: string;\n decimalMinLength?: number;\n\n enableCompactNotation?: boolean;\n enableNegative?: boolean;\n enableLeadingZeros?: boolean;\n rawValueMode?: boolean;\n}\n\nconst NumoraInput = forwardRef<HTMLInputElement, NumoraInputProps>((props, ref) => {\n const {\n maxDecimals = 2,\n onChange,\n onPaste,\n onBlur,\n onKeyDown,\n onFocus,\n onRawValueChange,\n formatOn = FormatOn.Blur,\n thousandSeparator,\n thousandStyle = ThousandStyle.Thousand,\n decimalSeparator,\n decimalMinLength,\n enableCompactNotation = false,\n enableNegative = false,\n enableLeadingZeros = false,\n rawValueMode = false,\n value: controlledValue,\n defaultValue,\n ...rest\n } = props;\n\n validateNumoraInputOptions({\n decimalMaxLength: maxDecimals,\n decimalMinLength,\n formatOn,\n thousandSeparator,\n thousandStyle,\n decimalSeparator,\n enableCompactNotation,\n enableNegative,\n enableLeadingZeros,\n rawValueMode,\n });\n\n const internalInputRef = useRef<HTMLInputElement>(null);\n const caretInfoRef = useRef<CaretPositionInfo | undefined>(undefined);\n const lastCaretPosRef = useRef<number | null>(null);\n\n // Memoize to give callbacks a stable reference - avoids recreating all\n // useCallback functions on every render when primitive props haven't changed.\n const formattingOptions: FormattingOptions = useMemo(() => {\n const resolved = resolveLocaleOptions({ thousandSeparator, thousandStyle, decimalSeparator });\n\n return {\n formatOn,\n thousandSeparator: resolved.thousandSeparator,\n ThousandStyle: resolved.thousandStyle,\n decimalSeparator: resolved.decimalSeparator,\n decimalMinLength,\n enableCompactNotation,\n enableNegative,\n enableLeadingZeros,\n rawValueMode,\n };\n }, [formatOn, thousandSeparator, thousandStyle, decimalSeparator, decimalMinLength,\n enableCompactNotation, enableNegative, enableLeadingZeros, rawValueMode]);\n\n const getInitialValue = (): string => {\n const valueToFormat = controlledValue !== undefined ? controlledValue : defaultValue;\n if (valueToFormat !== undefined) {\n const { formatted } = formatValueForDisplay(String(valueToFormat), maxDecimals, formattingOptions);\n return formatted;\n }\n return '';\n };\n\n const [displayValue, setDisplayValue] = useState<string>(getInitialValue);\n\n // Track the current displayValue via a ref so the controlled-value useEffect\n // can compare against it without adding displayValue as a dependency (which\n // would cause the effect to re-run on every keystroke).\n const displayValueRef = useRef<string>(displayValue);\n displayValueRef.current = displayValue;\n\n // Sync external ref with internal ref\n useLayoutEffect(() => {\n if (!ref) return;\n if (typeof ref === 'function') {\n ref(internalInputRef.current);\n } else {\n ref.current = internalInputRef.current;\n }\n }, [ref]);\n\n // When the controlled value or formatting options change, reformat the display.\n // Uses displayValueRef (not displayValue in deps) to avoid re-running on every keystroke.\n // Does NOT call onChange - that would create a circular loop with react-hook-form Controller.\n useEffect(() => {\n if (controlledValue !== undefined) {\n const { formatted, raw } = formatValueForDisplay(String(controlledValue), maxDecimals, formattingOptions);\n if (formatted !== displayValueRef.current) {\n setDisplayValue(formatted);\n\n if (internalInputRef.current) {\n (internalInputRef.current as NumoraHTMLInputElement).rawValue = raw;\n }\n onRawValueChange?.(raw);\n }\n }\n }, [controlledValue, maxDecimals, formattingOptions, onRawValueChange]);\n\n // Restore cursor position after render.\n // No dependency array is intentional: this must run after every render so it catches\n // the re-render triggered by setDisplayValue in handleChange/handlePaste.\n // lastCaretPosRef is a ref (not reactive), so it cannot be a dependency.\n useLayoutEffect(() => {\n if (internalInputRef.current && lastCaretPosRef.current !== null) {\n const input = internalInputRef.current;\n const pos = lastCaretPosRef.current;\n input.setSelectionRange(pos, pos);\n lastCaretPosRef.current = null;\n }\n });\n\n const handleChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {\n const { value, rawValue } = handleNumoraOnChange(e, {\n decimalMaxLength: maxDecimals,\n caretPositionBeforeChange: caretInfoRef.current,\n formattingOptions,\n });\n\n if (internalInputRef.current) {\n const cursorPos = internalInputRef.current.selectionStart;\n if (cursorPos !== null && cursorPos !== undefined) {\n lastCaretPosRef.current = cursorPos;\n }\n }\n\n caretInfoRef.current = undefined;\n\n (e.target as NumoraHTMLInputElement).rawValue = rawValue;\n\n onRawValueChange?.(rawValue);\n\n setDisplayValue(value);\n\n if (onChange) {\n onChange(e as unknown as NumoraInputChangeEvent);\n }\n }, [maxDecimals, formattingOptions, onChange, onRawValueChange]);\n\n const handleKeyDown = useCallback((e: KeyboardEvent<HTMLInputElement>) => {\n const coreCaretInfo = handleNumoraOnKeyDown(e, formattingOptions);\n\n if (!coreCaretInfo && internalInputRef.current) {\n const selectionStart = internalInputRef.current.selectionStart ?? 0;\n const selectionEnd = internalInputRef.current.selectionEnd ?? 0;\n caretInfoRef.current = {\n selectionStart,\n selectionEnd,\n };\n } else {\n caretInfoRef.current = coreCaretInfo;\n }\n\n if (onKeyDown) {\n onKeyDown(e);\n }\n }, [formattingOptions, onKeyDown]);\n\n const handlePaste = useCallback((e: ClipboardEvent<HTMLInputElement>) => {\n const { value, rawValue } = handleNumoraOnPaste(e, {\n decimalMaxLength: maxDecimals,\n formattingOptions,\n });\n\n lastCaretPosRef.current = (e.target as HTMLInputElement).selectionStart;\n (e.target as NumoraHTMLInputElement).rawValue = rawValue;\n\n onRawValueChange?.(rawValue);\n\n setDisplayValue(value);\n\n if (onPaste) {\n onPaste(e);\n }\n\n // Paste calls e.preventDefault() internally, so React's onChange never fires.\n // We synthesise a proper change event so consumers see a typed ChangeEvent.\n if (onChange) {\n onChange(createSyntheticChangeEvent(e.target as HTMLInputElement));\n }\n }, [maxDecimals, formattingOptions, onPaste, onChange, onRawValueChange]);\n\n const handleFocus = useCallback((e: FocusEvent<HTMLInputElement>) => {\n if (\n formattingOptions.formatOn === FormatOn.Blur &&\n formattingOptions.thousandSeparator &&\n formattingOptions.ThousandStyle !== ThousandStyle.None\n ) {\n // Read directly from the DOM element to avoid a stale displayValue closure\n // and to eliminate displayValue from the deps array (which would recreate\n // this callback on every keystroke).\n const currentValue = (e.target as HTMLInputElement).value;\n setDisplayValue(removeThousandSeparators(currentValue, formattingOptions.thousandSeparator!));\n }\n\n if (onFocus) {\n onFocus(e);\n }\n }, [formattingOptions, onFocus]);\n\n const handleBlur = useCallback((e: FocusEvent<HTMLInputElement>) => {\n const { value, rawValue } = handleNumoraOnBlur(e, {\n decimalMaxLength: maxDecimals,\n formattingOptions,\n });\n\n (e.target as NumoraHTMLInputElement).rawValue = rawValue;\n\n onRawValueChange?.(rawValue);\n setDisplayValue(value);\n\n if (onBlur) {\n onBlur(e);\n }\n }, [maxDecimals, formattingOptions, onBlur, onRawValueChange]);\n\n return (\n <input\n {...rest}\n ref={internalInputRef}\n value={displayValue}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n onPaste={handlePaste}\n onFocus={handleFocus}\n onBlur={handleBlur}\n type=\"text\"\n inputMode=\"decimal\"\n spellCheck={false}\n autoComplete=\"off\"\n />\n );\n});\n\nNumoraInput.displayName = 'NumoraInput';\n\nexport { NumoraInput };\nexport { FormatOn, ThousandStyle } from 'numora';\nexport type { FormattingOptions, CaretPositionInfo } from 'numora';\n"],"names":["_jsx"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","sources":["../src/handlers.ts","../src/index.tsx"],"sourcesContent":["import type React from 'react';\nimport {\n handleOnChangeNumoraInput,\n handleOnKeyDownNumoraInput,\n handleOnPasteNumoraInput,\n formatValueForDisplay,\n type CaretPositionInfo,\n type FormattingOptions,\n FormatOn,\n} from 'numora';\n\ntype ChangeResult = {\n value: string;\n rawValue?: string;\n};\n\ntype PasteResult = ChangeResult;\ntype BlurResult = ChangeResult;\n\ntype BaseOptions = {\n decimalMaxLength: number;\n caretPositionBeforeChange?: CaretPositionInfo;\n formattingOptions: FormattingOptions & { rawValueMode?: boolean };\n};\n\nexport function handleNumoraOnChange(\n e: React.ChangeEvent<HTMLInputElement>,\n options: BaseOptions\n): ChangeResult {\n const { formatted, raw } = handleOnChangeNumoraInput(\n e.nativeEvent as unknown as Event,\n options.decimalMaxLength,\n options.caretPositionBeforeChange,\n options.formattingOptions\n );\n\n return {\n value: formatted,\n rawValue: raw,\n };\n}\n\nexport function handleNumoraOnPaste(\n e: React.ClipboardEvent<HTMLInputElement>,\n options: Omit<BaseOptions, 'caretPositionBeforeChange'>\n): PasteResult {\n const { formatted, raw } = handleOnPasteNumoraInput(\n e.nativeEvent as ClipboardEvent,\n options.decimalMaxLength,\n options.formattingOptions\n );\n\n return {\n value: formatted,\n rawValue: raw,\n };\n}\n\nexport function handleNumoraOnKeyDown(\n e: React.KeyboardEvent<HTMLInputElement>,\n formattingOptions: FormattingOptions\n): CaretPositionInfo | undefined {\n return handleOnKeyDownNumoraInput(\n e.nativeEvent as unknown as KeyboardEvent,\n formattingOptions\n );\n}\n\nexport function handleNumoraOnBlur(\n e: React.FocusEvent<HTMLInputElement>,\n options: {\n decimalMaxLength: number;\n formattingOptions: FormattingOptions & { rawValueMode?: boolean };\n }\n): BlurResult {\n if (options.formattingOptions.formatOn === FormatOn.Blur) {\n const { formatted, raw } = formatValueForDisplay(\n e.target.value,\n options.decimalMaxLength,\n { ...options.formattingOptions, formatOn: FormatOn.Change }\n );\n return {\n value: formatted,\n rawValue: raw,\n };\n }\n\n return {\n value: e.target.value,\n rawValue: undefined,\n };\n}\n","import {\n useRef,\n useState,\n useEffect,\n useLayoutEffect,\n forwardRef,\n useCallback,\n useMemo,\n ClipboardEvent,\n ChangeEvent,\n FocusEvent,\n KeyboardEvent,\n InputHTMLAttributes\n} from 'react';\nimport {\n FormatOn,\n ThousandStyle,\n applyLocale,\n formatValueForDisplay,\n removeThousandSeparators,\n validateNumoraInputOptions,\n type CaretPositionInfo,\n type FormattingOptions,\n} from 'numora';\nimport {\n handleNumoraOnBlur,\n handleNumoraOnChange,\n handleNumoraOnKeyDown,\n handleNumoraOnPaste,\n} from './handlers';\n\nexport interface NumoraHTMLInputElement extends HTMLInputElement {\n rawValue?: string;\n}\n\nexport type NumoraInputChangeEvent = Omit<ChangeEvent<HTMLInputElement>, 'target'> & {\n target: NumoraHTMLInputElement;\n};\n\n/**\n * Creates a complete synthetic change event from a real HTMLInputElement.\n * Used when a change needs to be signalled without an actual DOM change event\n * (e.g. after paste with preventDefault, or after a controlled-value reformat).\n */\nfunction createSyntheticChangeEvent(input: HTMLInputElement): NumoraInputChangeEvent {\n const nativeEvent = new Event('change', { bubbles: true, cancelable: false });\n return {\n nativeEvent,\n target: input as NumoraHTMLInputElement,\n currentTarget: input,\n type: 'change',\n bubbles: true,\n cancelable: false,\n defaultPrevented: false,\n eventPhase: Event.AT_TARGET,\n isTrusted: false,\n timeStamp: Date.now(),\n isDefaultPrevented: () => false,\n isPropagationStopped: () => false,\n persist: () => {},\n preventDefault: () => {},\n stopPropagation: () => {},\n stopImmediatePropagation: () => {},\n } as unknown as NumoraInputChangeEvent;\n}\n\nexport interface NumoraInputProps\n extends Omit<\n InputHTMLAttributes<HTMLInputElement>,\n 'onChange' | 'type' | 'inputMode' | 'onFocus' | 'onBlur'\n > {\n maxDecimals?: number;\n onChange?: (e: NumoraInputChangeEvent) => void;\n onFocus?: (e: FocusEvent<HTMLInputElement>) => void;\n onBlur?: (e: FocusEvent<HTMLInputElement>) => void;\n /** Called with the raw (unformatted) numeric string on every value change. */\n onRawValueChange?: (rawValue: string | undefined) => void;\n\n locale?: string | true;\n formatOn?: FormatOn;\n thousandSeparator?: string;\n thousandStyle?: ThousandStyle;\n decimalSeparator?: string;\n decimalMinLength?: number;\n\n enableCompactNotation?: boolean;\n enableNegative?: boolean;\n enableLeadingZeros?: boolean;\n rawValueMode?: boolean;\n}\n\nconst NumoraInput = forwardRef<HTMLInputElement, NumoraInputProps>((props, ref) => {\n const {\n maxDecimals = 2,\n onChange,\n onPaste,\n onBlur,\n onKeyDown,\n onFocus,\n onRawValueChange,\n locale,\n formatOn = FormatOn.Blur,\n thousandSeparator,\n thousandStyle = ThousandStyle.Thousand,\n decimalSeparator,\n decimalMinLength,\n enableCompactNotation = false,\n enableNegative = false,\n enableLeadingZeros = false,\n rawValueMode = false,\n value: controlledValue,\n defaultValue,\n ...rest\n } = props;\n\n validateNumoraInputOptions({\n decimalMaxLength: maxDecimals,\n decimalMinLength,\n formatOn,\n thousandSeparator,\n thousandStyle,\n decimalSeparator,\n enableCompactNotation,\n enableNegative,\n enableLeadingZeros,\n rawValueMode,\n });\n\n const internalInputRef = useRef<HTMLInputElement>(null);\n const caretInfoRef = useRef<CaretPositionInfo | undefined>(undefined);\n const lastCaretPosRef = useRef<number | null>(null);\n\n // Memoize to give callbacks a stable reference - avoids recreating all\n // useCallback functions on every render when primitive props haven't changed.\n const formattingOptions: FormattingOptions = useMemo(() => {\n const separators = applyLocale(locale, { thousandSeparator, decimalSeparator });\n\n return {\n formatOn,\n thousandSeparator: separators.thousandSeparator,\n ThousandStyle: thousandStyle,\n decimalSeparator: separators.decimalSeparator,\n decimalMinLength,\n enableCompactNotation,\n enableNegative,\n enableLeadingZeros,\n rawValueMode,\n };\n }, [locale, formatOn, thousandSeparator, thousandStyle, decimalSeparator, decimalMinLength,\n enableCompactNotation, enableNegative, enableLeadingZeros, rawValueMode]);\n\n const getInitialValue = (): string => {\n const valueToFormat = controlledValue !== undefined ? controlledValue : defaultValue;\n if (valueToFormat !== undefined) {\n const { formatted } = formatValueForDisplay(String(valueToFormat), maxDecimals, formattingOptions);\n return formatted;\n }\n return '';\n };\n\n const [displayValue, setDisplayValue] = useState<string>(getInitialValue);\n\n // Track the current displayValue via a ref so the controlled-value useEffect\n // can compare against it without adding displayValue as a dependency (which\n // would cause the effect to re-run on every keystroke).\n const displayValueRef = useRef<string>(displayValue);\n displayValueRef.current = displayValue;\n\n // Sync external ref with internal ref\n useLayoutEffect(() => {\n if (!ref) return;\n if (typeof ref === 'function') {\n ref(internalInputRef.current);\n } else {\n ref.current = internalInputRef.current;\n }\n }, [ref]);\n\n // When the controlled value or formatting options change, reformat the display.\n // Uses displayValueRef (not displayValue in deps) to avoid re-running on every keystroke.\n // Does NOT call onChange - that would create a circular loop with react-hook-form Controller.\n useEffect(() => {\n if (controlledValue !== undefined) {\n const { formatted, raw } = formatValueForDisplay(String(controlledValue), maxDecimals, formattingOptions);\n if (formatted !== displayValueRef.current) {\n setDisplayValue(formatted);\n\n if (internalInputRef.current) {\n (internalInputRef.current as NumoraHTMLInputElement).rawValue = raw;\n }\n onRawValueChange?.(raw);\n }\n }\n }, [controlledValue, maxDecimals, formattingOptions, onRawValueChange]);\n\n // Restore cursor position after render.\n // No dependency array is intentional: this must run after every render so it catches\n // the re-render triggered by setDisplayValue in handleChange/handlePaste.\n // lastCaretPosRef is a ref (not reactive), so it cannot be a dependency.\n useLayoutEffect(() => {\n if (internalInputRef.current && lastCaretPosRef.current !== null) {\n const input = internalInputRef.current;\n const pos = lastCaretPosRef.current;\n input.setSelectionRange(pos, pos);\n lastCaretPosRef.current = null;\n }\n });\n\n const handleChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {\n const { value, rawValue } = handleNumoraOnChange(e, {\n decimalMaxLength: maxDecimals,\n caretPositionBeforeChange: caretInfoRef.current,\n formattingOptions,\n });\n\n if (internalInputRef.current) {\n const cursorPos = internalInputRef.current.selectionStart;\n if (cursorPos !== null && cursorPos !== undefined) {\n lastCaretPosRef.current = cursorPos;\n }\n }\n\n caretInfoRef.current = undefined;\n\n (e.target as NumoraHTMLInputElement).rawValue = rawValue;\n\n onRawValueChange?.(rawValue);\n\n setDisplayValue(value);\n\n if (onChange) {\n onChange(e as unknown as NumoraInputChangeEvent);\n }\n }, [maxDecimals, formattingOptions, onChange, onRawValueChange]);\n\n const handleKeyDown = useCallback((e: KeyboardEvent<HTMLInputElement>) => {\n const coreCaretInfo = handleNumoraOnKeyDown(e, formattingOptions);\n\n if (!coreCaretInfo && internalInputRef.current) {\n const selectionStart = internalInputRef.current.selectionStart ?? 0;\n const selectionEnd = internalInputRef.current.selectionEnd ?? 0;\n caretInfoRef.current = {\n selectionStart,\n selectionEnd,\n };\n } else {\n caretInfoRef.current = coreCaretInfo;\n }\n\n if (onKeyDown) {\n onKeyDown(e);\n }\n }, [formattingOptions, onKeyDown]);\n\n const handlePaste = useCallback((e: ClipboardEvent<HTMLInputElement>) => {\n const { value, rawValue } = handleNumoraOnPaste(e, {\n decimalMaxLength: maxDecimals,\n formattingOptions,\n });\n\n lastCaretPosRef.current = (e.target as HTMLInputElement).selectionStart;\n (e.target as NumoraHTMLInputElement).rawValue = rawValue;\n\n onRawValueChange?.(rawValue);\n\n setDisplayValue(value);\n\n if (onPaste) {\n onPaste(e);\n }\n\n // Paste calls e.preventDefault() internally, so React's onChange never fires.\n // We synthesise a proper change event so consumers see a typed ChangeEvent.\n if (onChange) {\n onChange(createSyntheticChangeEvent(e.target as HTMLInputElement));\n }\n }, [maxDecimals, formattingOptions, onPaste, onChange, onRawValueChange]);\n\n const handleFocus = useCallback((e: FocusEvent<HTMLInputElement>) => {\n if (\n formattingOptions.formatOn === FormatOn.Blur &&\n formattingOptions.thousandSeparator &&\n formattingOptions.ThousandStyle !== ThousandStyle.None\n ) {\n // Read directly from the DOM element to avoid a stale displayValue closure\n // and to eliminate displayValue from the deps array (which would recreate\n // this callback on every keystroke).\n const currentValue = (e.target as HTMLInputElement).value;\n setDisplayValue(removeThousandSeparators(currentValue, formattingOptions.thousandSeparator!));\n }\n\n if (onFocus) {\n onFocus(e);\n }\n }, [formattingOptions, onFocus]);\n\n const handleBlur = useCallback((e: FocusEvent<HTMLInputElement>) => {\n const { value, rawValue } = handleNumoraOnBlur(e, {\n decimalMaxLength: maxDecimals,\n formattingOptions,\n });\n\n (e.target as NumoraHTMLInputElement).rawValue = rawValue;\n\n onRawValueChange?.(rawValue);\n setDisplayValue(value);\n\n if (onBlur) {\n onBlur(e);\n }\n }, [maxDecimals, formattingOptions, onBlur, onRawValueChange]);\n\n return (\n <input\n {...rest}\n ref={internalInputRef}\n value={displayValue}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n onPaste={handlePaste}\n onFocus={handleFocus}\n onBlur={handleBlur}\n type=\"text\"\n inputMode=\"decimal\"\n spellCheck={false}\n autoComplete=\"off\"\n />\n );\n});\n\nNumoraInput.displayName = 'NumoraInput';\n\nexport { NumoraInput };\nexport { FormatOn, ThousandStyle } from 'numora';\nexport type { FormattingOptions, CaretPositionInfo } from 'numora';\n"],"names":["_jsx"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "numora-react",
3
- "version": "3.2.0",
3
+ "version": "3.4.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "types": "./dist/index.d.ts",
@@ -16,7 +16,7 @@
16
16
  "peerDependencies": {
17
17
  "react": ">=16.8.0",
18
18
  "react-dom": ">=16.8.0",
19
- "numora": ">=3.2.0"
19
+ "numora": ">=3.4.0"
20
20
  },
21
21
  "peerDependenciesMeta": {
22
22
  "react-dom": {
@@ -71,7 +71,7 @@
71
71
  "rollup-plugin-peer-deps-external": "^2.2.4",
72
72
  "tslib": "^2.8.1",
73
73
  "typescript": "^5.8.2",
74
- "numora": "^3.2.0"
74
+ "numora": "^3.4.0"
75
75
  },
76
76
  "publishConfig": {
77
77
  "access": "public"
package/src/index.tsx CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  import {
16
16
  FormatOn,
17
17
  ThousandStyle,
18
- resolveLocaleOptions,
18
+ applyLocale,
19
19
  formatValueForDisplay,
20
20
  removeThousandSeparators,
21
21
  validateNumoraInputOptions,
@@ -76,6 +76,7 @@ export interface NumoraInputProps
76
76
  /** Called with the raw (unformatted) numeric string on every value change. */
77
77
  onRawValueChange?: (rawValue: string | undefined) => void;
78
78
 
79
+ locale?: string | true;
79
80
  formatOn?: FormatOn;
80
81
  thousandSeparator?: string;
81
82
  thousandStyle?: ThousandStyle;
@@ -97,6 +98,7 @@ const NumoraInput = forwardRef<HTMLInputElement, NumoraInputProps>((props, ref)
97
98
  onKeyDown,
98
99
  onFocus,
99
100
  onRawValueChange,
101
+ locale,
100
102
  formatOn = FormatOn.Blur,
101
103
  thousandSeparator,
102
104
  thousandStyle = ThousandStyle.Thousand,
@@ -131,20 +133,20 @@ const NumoraInput = forwardRef<HTMLInputElement, NumoraInputProps>((props, ref)
131
133
  // Memoize to give callbacks a stable reference - avoids recreating all
132
134
  // useCallback functions on every render when primitive props haven't changed.
133
135
  const formattingOptions: FormattingOptions = useMemo(() => {
134
- const resolved = resolveLocaleOptions({ thousandSeparator, thousandStyle, decimalSeparator });
136
+ const separators = applyLocale(locale, { thousandSeparator, decimalSeparator });
135
137
 
136
138
  return {
137
139
  formatOn,
138
- thousandSeparator: resolved.thousandSeparator,
139
- ThousandStyle: resolved.thousandStyle,
140
- decimalSeparator: resolved.decimalSeparator,
140
+ thousandSeparator: separators.thousandSeparator,
141
+ ThousandStyle: thousandStyle,
142
+ decimalSeparator: separators.decimalSeparator,
141
143
  decimalMinLength,
142
144
  enableCompactNotation,
143
145
  enableNegative,
144
146
  enableLeadingZeros,
145
147
  rawValueMode,
146
148
  };
147
- }, [formatOn, thousandSeparator, thousandStyle, decimalSeparator, decimalMinLength,
149
+ }, [locale, formatOn, thousandSeparator, thousandStyle, decimalSeparator, decimalMinLength,
148
150
  enableCompactNotation, enableNegative, enableLeadingZeros, rawValueMode]);
149
151
 
150
152
  const getInitialValue = (): string => {