numora-react 3.0.3 → 3.1.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 ADDED
@@ -0,0 +1,241 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var react = require('react');
5
+ var numora = require('numora');
6
+
7
+ function handleNumoraOnChange(e, options) {
8
+ const { formatted, raw } = numora.handleOnChangeNumoraInput(e.nativeEvent, options.decimalMaxLength, options.caretPositionBeforeChange, options.formattingOptions);
9
+ return {
10
+ value: formatted,
11
+ rawValue: raw,
12
+ };
13
+ }
14
+ function handleNumoraOnPaste(e, options) {
15
+ const { formatted, raw } = numora.handleOnPasteNumoraInput(e.nativeEvent, options.decimalMaxLength, options.formattingOptions);
16
+ return {
17
+ value: formatted,
18
+ rawValue: raw,
19
+ };
20
+ }
21
+ function handleNumoraOnKeyDown(e, formattingOptions) {
22
+ return numora.handleOnKeyDownNumoraInput(e.nativeEvent, formattingOptions);
23
+ }
24
+ function handleNumoraOnBlur(e, options) {
25
+ if (options.formattingOptions.formatOn === numora.FormatOn.Blur) {
26
+ const { formatted, raw } = numora.formatValueForDisplay(e.target.value, options.decimalMaxLength, { ...options.formattingOptions, formatOn: numora.FormatOn.Change });
27
+ return {
28
+ value: formatted,
29
+ rawValue: raw,
30
+ };
31
+ }
32
+ return {
33
+ value: e.target.value,
34
+ rawValue: undefined,
35
+ };
36
+ }
37
+
38
+ /**
39
+ * Creates a complete synthetic change event from a real HTMLInputElement.
40
+ * Used when a change needs to be signalled without an actual DOM change event
41
+ * (e.g. after paste with preventDefault, or after a controlled-value reformat).
42
+ */
43
+ function createSyntheticChangeEvent(input) {
44
+ const nativeEvent = new Event('change', { bubbles: true, cancelable: false });
45
+ return {
46
+ nativeEvent,
47
+ target: input,
48
+ currentTarget: input,
49
+ type: 'change',
50
+ bubbles: true,
51
+ cancelable: false,
52
+ defaultPrevented: false,
53
+ eventPhase: Event.AT_TARGET,
54
+ isTrusted: false,
55
+ timeStamp: Date.now(),
56
+ isDefaultPrevented: () => false,
57
+ isPropagationStopped: () => false,
58
+ persist: () => { },
59
+ preventDefault: () => { },
60
+ stopPropagation: () => { },
61
+ stopImmediatePropagation: () => { },
62
+ };
63
+ }
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;
66
+ numora.validateNumoraInputOptions({
67
+ decimalMaxLength: maxDecimals,
68
+ decimalMinLength,
69
+ formatOn,
70
+ thousandSeparator,
71
+ thousandStyle,
72
+ decimalSeparator,
73
+ enableCompactNotation,
74
+ enableNegative,
75
+ enableLeadingZeros,
76
+ rawValueMode,
77
+ });
78
+ const internalInputRef = react.useRef(null);
79
+ const caretInfoRef = react.useRef(undefined);
80
+ const lastCaretPosRef = react.useRef(null);
81
+ // Memoize to give callbacks a stable reference - avoids recreating all
82
+ // useCallback functions on every render when primitive props haven't changed.
83
+ const formattingOptions = react.useMemo(() => ({
84
+ formatOn,
85
+ thousandSeparator,
86
+ ThousandStyle: thousandStyle,
87
+ decimalSeparator,
88
+ decimalMinLength,
89
+ enableCompactNotation,
90
+ enableNegative,
91
+ enableLeadingZeros,
92
+ rawValueMode,
93
+ }), [formatOn, thousandSeparator, thousandStyle, decimalSeparator, decimalMinLength,
94
+ enableCompactNotation, enableNegative, enableLeadingZeros, rawValueMode]);
95
+ const getInitialValue = () => {
96
+ const valueToFormat = controlledValue !== undefined ? controlledValue : defaultValue;
97
+ if (valueToFormat !== undefined) {
98
+ const { formatted } = numora.formatValueForDisplay(String(valueToFormat), maxDecimals, formattingOptions);
99
+ return formatted;
100
+ }
101
+ return '';
102
+ };
103
+ const [displayValue, setDisplayValue] = react.useState(getInitialValue);
104
+ // Track the current displayValue via a ref so the controlled-value useEffect
105
+ // can compare against it without adding displayValue as a dependency (which
106
+ // would cause the effect to re-run on every keystroke).
107
+ const displayValueRef = react.useRef(displayValue);
108
+ displayValueRef.current = displayValue;
109
+ // Sync external ref with internal ref
110
+ react.useLayoutEffect(() => {
111
+ if (!ref)
112
+ return;
113
+ if (typeof ref === 'function') {
114
+ ref(internalInputRef.current);
115
+ }
116
+ else {
117
+ ref.current = internalInputRef.current;
118
+ }
119
+ }, [ref]);
120
+ // When the controlled value or formatting options change, reformat the display.
121
+ // Uses displayValueRef (not displayValue in deps) to avoid re-running on every keystroke.
122
+ // Does NOT call onChange - that would create a circular loop with react-hook-form Controller.
123
+ react.useEffect(() => {
124
+ if (controlledValue !== undefined) {
125
+ const { formatted, raw } = numora.formatValueForDisplay(String(controlledValue), maxDecimals, formattingOptions);
126
+ if (formatted !== displayValueRef.current) {
127
+ setDisplayValue(formatted);
128
+ if (internalInputRef.current) {
129
+ internalInputRef.current.rawValue = raw;
130
+ }
131
+ onRawValueChange?.(raw);
132
+ }
133
+ }
134
+ }, [controlledValue, maxDecimals, formattingOptions, onRawValueChange]);
135
+ // Restore cursor position after render.
136
+ // No dependency array is intentional: this must run after every render so it catches
137
+ // the re-render triggered by setDisplayValue in handleChange/handlePaste.
138
+ // lastCaretPosRef is a ref (not reactive), so it cannot be a dependency.
139
+ react.useLayoutEffect(() => {
140
+ if (internalInputRef.current && lastCaretPosRef.current !== null) {
141
+ const input = internalInputRef.current;
142
+ const pos = lastCaretPosRef.current;
143
+ input.setSelectionRange(pos, pos);
144
+ lastCaretPosRef.current = null;
145
+ }
146
+ });
147
+ const handleChange = react.useCallback((e) => {
148
+ const { value, rawValue } = handleNumoraOnChange(e, {
149
+ decimalMaxLength: maxDecimals,
150
+ caretPositionBeforeChange: caretInfoRef.current,
151
+ formattingOptions,
152
+ });
153
+ if (internalInputRef.current) {
154
+ const cursorPos = internalInputRef.current.selectionStart;
155
+ if (cursorPos !== null && cursorPos !== undefined) {
156
+ lastCaretPosRef.current = cursorPos;
157
+ }
158
+ }
159
+ caretInfoRef.current = undefined;
160
+ e.target.rawValue = rawValue;
161
+ onRawValueChange?.(rawValue);
162
+ setDisplayValue(value);
163
+ if (onChange) {
164
+ onChange(e);
165
+ }
166
+ }, [maxDecimals, formattingOptions, onChange, onRawValueChange]);
167
+ const handleKeyDown = react.useCallback((e) => {
168
+ const coreCaretInfo = handleNumoraOnKeyDown(e, formattingOptions);
169
+ if (!coreCaretInfo && internalInputRef.current) {
170
+ const selectionStart = internalInputRef.current.selectionStart ?? 0;
171
+ const selectionEnd = internalInputRef.current.selectionEnd ?? 0;
172
+ caretInfoRef.current = {
173
+ selectionStart,
174
+ selectionEnd,
175
+ };
176
+ }
177
+ else {
178
+ caretInfoRef.current = coreCaretInfo;
179
+ }
180
+ if (onKeyDown) {
181
+ onKeyDown(e);
182
+ }
183
+ }, [formattingOptions, onKeyDown]);
184
+ const handlePaste = react.useCallback((e) => {
185
+ const { value, rawValue } = handleNumoraOnPaste(e, {
186
+ decimalMaxLength: maxDecimals,
187
+ formattingOptions,
188
+ });
189
+ lastCaretPosRef.current = e.target.selectionStart;
190
+ e.target.rawValue = rawValue;
191
+ onRawValueChange?.(rawValue);
192
+ setDisplayValue(value);
193
+ if (onPaste) {
194
+ onPaste(e);
195
+ }
196
+ // Paste calls e.preventDefault() internally, so React's onChange never fires.
197
+ // We synthesise a proper change event so consumers see a typed ChangeEvent.
198
+ if (onChange) {
199
+ onChange(createSyntheticChangeEvent(e.target));
200
+ }
201
+ }, [maxDecimals, formattingOptions, onPaste, onChange, onRawValueChange]);
202
+ const handleFocus = react.useCallback((e) => {
203
+ if (formattingOptions.formatOn === numora.FormatOn.Blur &&
204
+ formattingOptions.thousandSeparator &&
205
+ formattingOptions.ThousandStyle !== numora.ThousandStyle.None) {
206
+ // Read directly from the DOM element to avoid a stale displayValue closure
207
+ // and to eliminate displayValue from the deps array (which would recreate
208
+ // this callback on every keystroke).
209
+ const currentValue = e.target.value;
210
+ setDisplayValue(numora.removeThousandSeparators(currentValue, formattingOptions.thousandSeparator));
211
+ }
212
+ if (onFocus) {
213
+ onFocus(e);
214
+ }
215
+ }, [formattingOptions, onFocus]);
216
+ const handleBlur = react.useCallback((e) => {
217
+ const { value, rawValue } = handleNumoraOnBlur(e, {
218
+ decimalMaxLength: maxDecimals,
219
+ formattingOptions,
220
+ });
221
+ e.target.rawValue = rawValue;
222
+ onRawValueChange?.(rawValue);
223
+ setDisplayValue(value);
224
+ if (onBlur) {
225
+ onBlur(e);
226
+ }
227
+ }, [maxDecimals, formattingOptions, onBlur, onRawValueChange]);
228
+ return (jsxRuntime.jsx("input", { ...rest, ref: internalInputRef, value: displayValue, onChange: handleChange, onKeyDown: handleKeyDown, onPaste: handlePaste, onFocus: handleFocus, onBlur: handleBlur, type: "text", inputMode: "decimal", spellCheck: false, autoComplete: "off" }));
229
+ });
230
+ NumoraInput.displayName = 'NumoraInput';
231
+
232
+ Object.defineProperty(exports, "FormatOn", {
233
+ enumerable: true,
234
+ get: function () { return numora.FormatOn; }
235
+ });
236
+ Object.defineProperty(exports, "ThousandStyle", {
237
+ enumerable: true,
238
+ get: function () { return numora.ThousandStyle; }
239
+ });
240
+ exports.NumoraInput = NumoraInput;
241
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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 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 formatOn,\n thousandSeparator,\n ThousandStyle: thousandStyle,\n decimalSeparator,\n decimalMinLength,\n enableCompactNotation,\n enableNegative,\n enableLeadingZeros,\n rawValueMode,\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 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 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 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","useState","useLayoutEffect","useEffect","useCallback","removeThousandSeparators","_jsx"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,10 +1,18 @@
1
1
  import { ChangeEvent, FocusEvent, InputHTMLAttributes } from 'react';
2
2
  import { FormatOn, ThousandStyle } from 'numora';
3
+ export interface NumoraHTMLInputElement extends HTMLInputElement {
4
+ rawValue?: string;
5
+ }
6
+ export type NumoraInputChangeEvent = Omit<ChangeEvent<HTMLInputElement>, 'target'> & {
7
+ target: NumoraHTMLInputElement;
8
+ };
3
9
  export interface NumoraInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'type' | 'inputMode' | 'onFocus' | 'onBlur'> {
4
10
  maxDecimals?: number;
5
- onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
11
+ onChange?: (e: NumoraInputChangeEvent) => void;
6
12
  onFocus?: (e: FocusEvent<HTMLInputElement>) => void;
7
13
  onBlur?: (e: FocusEvent<HTMLInputElement>) => void;
14
+ /** Called with the raw (unformatted) numeric string on every value change. */
15
+ onRawValueChange?: (rawValue: string | undefined) => void;
8
16
  formatOn?: FormatOn;
9
17
  thousandSeparator?: string;
10
18
  thousandStyle?: ThousandStyle;