numora-react 1.0.2 → 1.0.3

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.js CHANGED
@@ -14,22 +14,24 @@ const DEFAULT_PROPS = {
14
14
  spellCheck: false,
15
15
  step: 'any'
16
16
  };
17
- const NumoraInput = /*#__PURE__*/React.forwardRef((_ref, ref) => {
18
- let {
19
- maxDecimals = 2,
20
- onChange,
21
- formatOn = numora.FormatOn.Blur,
22
- thousandSeparator = ',',
23
- thousandStyle = numora.ThousandStyle.Thousand,
24
- decimalSeparator = '.',
25
- decimalMinLength,
26
- enableCompactNotation = false,
27
- enableNegative = false,
28
- enableLeadingZeros = false,
29
- rawValueMode = false,
30
- ...props
31
- } = _ref;
17
+ const NumoraInput = /*#__PURE__*/React.forwardRef(({
18
+ maxDecimals = 2,
19
+ onChange,
20
+ formatOn = numora.FormatOn.Blur,
21
+ thousandSeparator = ',',
22
+ thousandStyle = numora.ThousandStyle.Thousand,
23
+ decimalSeparator = '.',
24
+ decimalMinLength,
25
+ enableCompactNotation = false,
26
+ enableNegative = false,
27
+ enableLeadingZeros = false,
28
+ rawValueMode = false,
29
+ ...props
30
+ }, ref) => {
32
31
  const [caretPositionBeforeChange, setCaretPositionBeforeChange] = React.useState();
32
+ const inputRef = React.useRef(null);
33
+ const isUserInputRef = React.useRef(false);
34
+ const previousValueRef = React.useRef(typeof props.value === 'string' ? props.value : props.value?.toString());
33
35
  const formattingOptions = {
34
36
  formatOn,
35
37
  thousandSeparator,
@@ -41,6 +43,23 @@ const NumoraInput = /*#__PURE__*/React.forwardRef((_ref, ref) => {
41
43
  enableLeadingZeros,
42
44
  rawValueMode
43
45
  };
46
+ // Handle programmatic value changes (when value prop changes externally)
47
+ React.useEffect(() => {
48
+ const input = inputRef.current;
49
+ if (!input) return;
50
+ const currentValue = typeof props.value === 'string' ? props.value : props.value !== undefined ? String(props.value) : undefined;
51
+ const previousValue = previousValueRef.current;
52
+ // Only format if value changed externally (not from user input)
53
+ if (!isUserInputRef.current && currentValue !== previousValue && currentValue !== undefined) {
54
+ const formatted = numora.formatValueForNumora(currentValue, maxDecimals, formattingOptions);
55
+ // Only update if formatted value differs from current input value
56
+ if (input.value !== formatted) {
57
+ input.value = formatted;
58
+ }
59
+ }
60
+ previousValueRef.current = currentValue;
61
+ isUserInputRef.current = false;
62
+ }, [props.value, maxDecimals, formatOn, thousandSeparator, thousandStyle, decimalSeparator, decimalMinLength, enableCompactNotation, enableNegative, enableLeadingZeros, rawValueMode]);
44
63
  function handleOnKeyDown(e) {
45
64
  const caretInfo = numora.handleOnKeyDownNumoraInput(e.nativeEvent, formattingOptions);
46
65
  if (caretInfo) {
@@ -57,11 +76,13 @@ const NumoraInput = /*#__PURE__*/React.forwardRef((_ref, ref) => {
57
76
  }
58
77
  }
59
78
  function handleOnChange(e) {
79
+ isUserInputRef.current = true;
60
80
  numora.handleOnChangeNumoraInput(e.nativeEvent, maxDecimals, caretPositionBeforeChange, formattingOptions);
61
81
  setCaretPositionBeforeChange(undefined);
62
82
  if (onChange) onChange(e);
63
83
  }
64
84
  function handleOnPaste(e) {
85
+ isUserInputRef.current = true;
65
86
  numora.handleOnPasteNumoraInput(e.nativeEvent, maxDecimals, formattingOptions);
66
87
  if (onChange) onChange(e);
67
88
  }
@@ -79,10 +100,19 @@ const NumoraInput = /*#__PURE__*/React.forwardRef((_ref, ref) => {
79
100
  props.onBlur(e);
80
101
  }
81
102
  }
103
+ // Combine refs: forward the ref and also store it internally
104
+ const setRefs = React.useCallback(node => {
105
+ inputRef.current = node;
106
+ if (typeof ref === 'function') {
107
+ ref(node);
108
+ } else if (ref) {
109
+ ref.current = node;
110
+ }
111
+ }, [ref]);
82
112
  return jsxRuntime.jsx("input", {
83
113
  ...DEFAULT_PROPS,
84
114
  ...props,
85
- ref: ref,
115
+ ref: setRefs,
86
116
  onChange: handleOnChange,
87
117
  onKeyDown: handleOnKeyDown,
88
118
  onPaste: handleOnPaste,
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../src/index.tsx"],"sourcesContent":["import React, { ChangeEvent, ClipboardEvent, forwardRef } from 'react';\nimport {\n NumoraInput,\n FormatOn,\n ThousandStyle,\n FormattingOptions,\n CaretPositionInfo,\n handleOnChangeNumoraInput,\n handleOnPasteNumoraInput,\n handleOnKeyDownNumoraInput,\n} from 'numora';\n\ninterface NumoraInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'type' | 'inputMode'> {\n maxDecimals?: number;\n onChange?: (e: ChangeEvent<HTMLInputElement> | ClipboardEvent<HTMLInputElement>) => void;\n\n // Formatting options\n formatOn?: FormatOn;\n thousandSeparator?: string;\n thousandStyle?: ThousandStyle;\n decimalSeparator?: string;\n decimalMinLength?: number;\n\n // Feature flags\n enableCompactNotation?: boolean;\n enableNegative?: boolean;\n enableLeadingZeros?: boolean;\n rawValueMode?: boolean;\n}\n\nconst DEFAULT_PROPS = {\n autoComplete: 'off',\n autoCorrect: 'off',\n autoCapitalize: 'none',\n minLength: 1,\n placeholder: '0.0',\n pattern: '^[0-9]*[.,]?[0-9]*$',\n spellCheck: false,\n step: 'any',\n};\n\nconst NumoraInput = forwardRef<HTMLInputElement, NumoraInputProps>(\n ({\n maxDecimals = 2,\n onChange,\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 ...props\n }: NumoraInputProps, ref: React.Ref<HTMLInputElement>) => {\n const [caretPositionBeforeChange, setCaretPositionBeforeChange] =\n React.useState<CaretPositionInfo>();\n\n const formattingOptions: FormattingOptions = {\n formatOn,\n thousandSeparator,\n ThousandStyle: thousandStyle as any,\n decimalSeparator,\n decimalMinLength,\n enableCompactNotation,\n enableNegative,\n enableLeadingZeros,\n rawValueMode,\n };\n\n function handleOnKeyDown(e: React.KeyboardEvent<HTMLInputElement>): void {\n const caretInfo = handleOnKeyDownNumoraInput(e.nativeEvent, formattingOptions);\n\n if (caretInfo) {\n setCaretPositionBeforeChange(caretInfo);\n } else {\n const input = e.currentTarget;\n setCaretPositionBeforeChange({\n selectionStart: input.selectionStart ?? 0,\n selectionEnd: input.selectionEnd ?? 0,\n });\n }\n\n if (props.onKeyDown) {\n props.onKeyDown(e);\n }\n }\n\n function handleOnChange(e: ChangeEvent<HTMLInputElement>): void {\n handleOnChangeNumoraInput(\n e.nativeEvent,\n maxDecimals,\n caretPositionBeforeChange,\n formattingOptions\n );\n setCaretPositionBeforeChange(undefined);\n if (onChange) onChange(e);\n }\n\n function handleOnPaste(e: ClipboardEvent<HTMLInputElement>): void {\n handleOnPasteNumoraInput(e.nativeEvent, maxDecimals, formattingOptions);\n if (onChange) onChange(e);\n }\n\n function handleOnFocus(e: React.FocusEvent<HTMLInputElement>): void {\n if (formatOn === FormatOn.Blur && thousandSeparator) {\n const target = e.currentTarget;\n target.value = target.value.replace(\n new RegExp(thousandSeparator.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'g'),\n ''\n );\n }\n\n if (props.onFocus) {\n props.onFocus(e);\n }\n }\n\n function handleOnBlur(e: React.FocusEvent<HTMLInputElement>): void {\n if (props.onBlur) {\n props.onBlur(e);\n }\n }\n\n return (\n <input\n {...DEFAULT_PROPS}\n {...props}\n ref={ref}\n onChange={handleOnChange}\n onKeyDown={handleOnKeyDown}\n onPaste={handleOnPaste}\n onFocus={handleOnFocus}\n onBlur={handleOnBlur}\n type=\"text\"\n inputMode=\"decimal\"\n />\n );\n }\n);\n\nNumoraInput.displayName = 'NumoraInput';\n\nexport { NumoraInput };\nexport { FormatOn, ThousandStyle } from 'numora';\nexport type { FormattingOptions, CaretPositionInfo } from 'numora';\n"],"names":["DEFAULT_PROPS","autoComplete","autoCorrect","autoCapitalize","minLength","placeholder","pattern","spellCheck","step","NumoraInput","forwardRef","_ref","ref","maxDecimals","onChange","formatOn","FormatOn","Blur","thousandSeparator","thousandStyle","ThousandStyle","Thousand","decimalSeparator","decimalMinLength","enableCompactNotation","enableNegative","enableLeadingZeros","rawValueMode","props","caretPositionBeforeChange","setCaretPositionBeforeChange","React","useState","formattingOptions","handleOnKeyDown","e","caretInfo","handleOnKeyDownNumoraInput","nativeEvent","input","currentTarget","selectionStart","selectionEnd","onKeyDown","handleOnChange","handleOnChangeNumoraInput","undefined","handleOnPaste","handleOnPasteNumoraInput","handleOnFocus","target","value","replace","RegExp","onFocus","handleOnBlur","onBlur","_jsx","onPaste","type","inputMode","displayName"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs.js","sources":["../src/index.tsx"],"sourcesContent":["import React, { ChangeEvent, ClipboardEvent, forwardRef, useEffect, useRef } from 'react';\nimport {\n FormatOn,\n ThousandStyle,\n FormattingOptions,\n CaretPositionInfo,\n handleOnChangeNumoraInput,\n handleOnPasteNumoraInput,\n handleOnKeyDownNumoraInput,\n formatValueForNumora,\n} from 'numora';\n\ninterface NumoraInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'type' | 'inputMode'> {\n maxDecimals?: number;\n onChange?: (e: ChangeEvent<HTMLInputElement> | ClipboardEvent<HTMLInputElement>) => void;\n\n // Formatting options\n formatOn?: FormatOn;\n thousandSeparator?: string;\n thousandStyle?: ThousandStyle;\n decimalSeparator?: string;\n decimalMinLength?: number;\n\n // Feature flags\n enableCompactNotation?: boolean;\n enableNegative?: boolean;\n enableLeadingZeros?: boolean;\n rawValueMode?: boolean;\n}\n\nconst DEFAULT_PROPS = {\n autoComplete: 'off',\n autoCorrect: 'off',\n autoCapitalize: 'none',\n minLength: 1,\n placeholder: '0.0',\n pattern: '^[0-9]*[.,]?[0-9]*$',\n spellCheck: false,\n step: 'any',\n};\n\nconst NumoraInput = forwardRef<HTMLInputElement, NumoraInputProps>(\n ({\n maxDecimals = 2,\n onChange,\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 ...props\n }: NumoraInputProps, ref: React.Ref<HTMLInputElement>) => {\n const [caretPositionBeforeChange, setCaretPositionBeforeChange] =\n React.useState<CaretPositionInfo>();\n const inputRef = React.useRef<HTMLInputElement>(null);\n const isUserInputRef = useRef(false);\n const previousValueRef = useRef<string | undefined>(\n typeof props.value === 'string' ? props.value : props.value?.toString()\n );\n\n const formattingOptions: FormattingOptions = {\n formatOn,\n thousandSeparator,\n ThousandStyle: thousandStyle as any,\n decimalSeparator,\n decimalMinLength,\n enableCompactNotation,\n enableNegative,\n enableLeadingZeros,\n rawValueMode,\n };\n\n // Handle programmatic value changes (when value prop changes externally)\n useEffect(() => {\n const input = inputRef.current;\n if (!input) return;\n\n const currentValue = typeof props.value === 'string'\n ? props.value\n : props.value !== undefined\n ? String(props.value)\n : undefined;\n const previousValue = previousValueRef.current;\n\n // Only format if value changed externally (not from user input)\n if (!isUserInputRef.current && currentValue !== previousValue && currentValue !== undefined) {\n const formatted = formatValueForNumora(\n currentValue,\n maxDecimals,\n formattingOptions\n );\n\n // Only update if formatted value differs from current input value\n if (input.value !== formatted) {\n input.value = formatted;\n }\n }\n\n previousValueRef.current = currentValue;\n isUserInputRef.current = false;\n }, [props.value, maxDecimals, formatOn, thousandSeparator, thousandStyle, decimalSeparator, decimalMinLength, enableCompactNotation, enableNegative, enableLeadingZeros, rawValueMode]);\n\n function handleOnKeyDown(e: React.KeyboardEvent<HTMLInputElement>): void {\n const caretInfo = handleOnKeyDownNumoraInput(e.nativeEvent, formattingOptions);\n\n if (caretInfo) {\n setCaretPositionBeforeChange(caretInfo);\n } else {\n const input = e.currentTarget;\n setCaretPositionBeforeChange({\n selectionStart: input.selectionStart ?? 0,\n selectionEnd: input.selectionEnd ?? 0,\n });\n }\n\n if (props.onKeyDown) {\n props.onKeyDown(e);\n }\n }\n\n function handleOnChange(e: ChangeEvent<HTMLInputElement>): void {\n isUserInputRef.current = true;\n handleOnChangeNumoraInput(\n e.nativeEvent,\n maxDecimals,\n caretPositionBeforeChange,\n formattingOptions\n );\n setCaretPositionBeforeChange(undefined);\n if (onChange) onChange(e);\n }\n\n function handleOnPaste(e: ClipboardEvent<HTMLInputElement>): void {\n isUserInputRef.current = true;\n handleOnPasteNumoraInput(e.nativeEvent, maxDecimals, formattingOptions);\n if (onChange) onChange(e);\n }\n\n function handleOnFocus(e: React.FocusEvent<HTMLInputElement>): void {\n if (formatOn === FormatOn.Blur && thousandSeparator) {\n const target = e.currentTarget;\n target.value = target.value.replace(\n new RegExp(thousandSeparator.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'g'),\n ''\n );\n }\n\n if (props.onFocus) {\n props.onFocus(e);\n }\n }\n\n function handleOnBlur(e: React.FocusEvent<HTMLInputElement>): void {\n if (props.onBlur) {\n props.onBlur(e);\n }\n }\n\n // Combine refs: forward the ref and also store it internally\n const setRefs = React.useCallback((node: HTMLInputElement | null) => {\n inputRef.current = node;\n if (typeof ref === 'function') {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n }, [ref]);\n\n return (\n <input\n {...DEFAULT_PROPS}\n {...props}\n ref={setRefs}\n onChange={handleOnChange}\n onKeyDown={handleOnKeyDown}\n onPaste={handleOnPaste}\n onFocus={handleOnFocus}\n onBlur={handleOnBlur}\n type=\"text\"\n inputMode=\"decimal\"\n />\n );\n }\n);\n\nNumoraInput.displayName = 'NumoraInput';\n\nexport { NumoraInput };\nexport { FormatOn, ThousandStyle } from 'numora';\nexport type { FormattingOptions, CaretPositionInfo } from 'numora';\n"],"names":["DEFAULT_PROPS","autoComplete","autoCorrect","autoCapitalize","minLength","placeholder","pattern","spellCheck","step","NumoraInput","forwardRef","maxDecimals","onChange","formatOn","FormatOn","Blur","thousandSeparator","thousandStyle","ThousandStyle","Thousand","decimalSeparator","decimalMinLength","enableCompactNotation","enableNegative","enableLeadingZeros","rawValueMode","props","ref","caretPositionBeforeChange","setCaretPositionBeforeChange","React","useState","inputRef","useRef","isUserInputRef","previousValueRef","value","toString","formattingOptions","useEffect","input","current","currentValue","undefined","String","previousValue","formatted","formatValueForNumora","handleOnKeyDown","e","caretInfo","handleOnKeyDownNumoraInput","nativeEvent","currentTarget","selectionStart","selectionEnd","onKeyDown","handleOnChange","handleOnChangeNumoraInput","handleOnPaste","handleOnPasteNumoraInput","handleOnFocus","target","replace","RegExp","onFocus","handleOnBlur","onBlur","setRefs","useCallback","node","_jsx","onPaste","type","inputMode","displayName"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import React, { ChangeEvent, ClipboardEvent } from 'react';
2
- import { NumoraInput, FormatOn, ThousandStyle } from 'numora';
2
+ import { FormatOn, ThousandStyle } from 'numora';
3
3
  interface NumoraInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'type' | 'inputMode'> {
4
4
  maxDecimals?: number;
5
5
  onChange?: (e: ChangeEvent<HTMLInputElement> | ClipboardEvent<HTMLInputElement>) => void;
package/dist/index.esm.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
- import React, { forwardRef } from 'react';
3
- import { FormatOn, ThousandStyle, handleOnPasteNumoraInput, handleOnKeyDownNumoraInput, handleOnChangeNumoraInput } from 'numora';
2
+ import React, { forwardRef, useRef, useEffect } from 'react';
3
+ import { formatValueForNumora, ThousandStyle, FormatOn, handleOnPasteNumoraInput, handleOnKeyDownNumoraInput, handleOnChangeNumoraInput } from 'numora';
4
4
  export { FormatOn, ThousandStyle } from 'numora';
5
5
 
6
6
  const DEFAULT_PROPS = {
@@ -13,22 +13,24 @@ const DEFAULT_PROPS = {
13
13
  spellCheck: false,
14
14
  step: 'any'
15
15
  };
16
- const NumoraInput = /*#__PURE__*/forwardRef((_ref, ref) => {
17
- let {
18
- maxDecimals = 2,
19
- onChange,
20
- formatOn = FormatOn.Blur,
21
- thousandSeparator = ',',
22
- thousandStyle = ThousandStyle.Thousand,
23
- decimalSeparator = '.',
24
- decimalMinLength,
25
- enableCompactNotation = false,
26
- enableNegative = false,
27
- enableLeadingZeros = false,
28
- rawValueMode = false,
29
- ...props
30
- } = _ref;
16
+ const NumoraInput = /*#__PURE__*/forwardRef(({
17
+ maxDecimals = 2,
18
+ onChange,
19
+ formatOn = FormatOn.Blur,
20
+ thousandSeparator = ',',
21
+ thousandStyle = ThousandStyle.Thousand,
22
+ decimalSeparator = '.',
23
+ decimalMinLength,
24
+ enableCompactNotation = false,
25
+ enableNegative = false,
26
+ enableLeadingZeros = false,
27
+ rawValueMode = false,
28
+ ...props
29
+ }, ref) => {
31
30
  const [caretPositionBeforeChange, setCaretPositionBeforeChange] = React.useState();
31
+ const inputRef = React.useRef(null);
32
+ const isUserInputRef = useRef(false);
33
+ const previousValueRef = useRef(typeof props.value === 'string' ? props.value : props.value?.toString());
32
34
  const formattingOptions = {
33
35
  formatOn,
34
36
  thousandSeparator,
@@ -40,6 +42,23 @@ const NumoraInput = /*#__PURE__*/forwardRef((_ref, ref) => {
40
42
  enableLeadingZeros,
41
43
  rawValueMode
42
44
  };
45
+ // Handle programmatic value changes (when value prop changes externally)
46
+ useEffect(() => {
47
+ const input = inputRef.current;
48
+ if (!input) return;
49
+ const currentValue = typeof props.value === 'string' ? props.value : props.value !== undefined ? String(props.value) : undefined;
50
+ const previousValue = previousValueRef.current;
51
+ // Only format if value changed externally (not from user input)
52
+ if (!isUserInputRef.current && currentValue !== previousValue && currentValue !== undefined) {
53
+ const formatted = formatValueForNumora(currentValue, maxDecimals, formattingOptions);
54
+ // Only update if formatted value differs from current input value
55
+ if (input.value !== formatted) {
56
+ input.value = formatted;
57
+ }
58
+ }
59
+ previousValueRef.current = currentValue;
60
+ isUserInputRef.current = false;
61
+ }, [props.value, maxDecimals, formatOn, thousandSeparator, thousandStyle, decimalSeparator, decimalMinLength, enableCompactNotation, enableNegative, enableLeadingZeros, rawValueMode]);
43
62
  function handleOnKeyDown(e) {
44
63
  const caretInfo = handleOnKeyDownNumoraInput(e.nativeEvent, formattingOptions);
45
64
  if (caretInfo) {
@@ -56,11 +75,13 @@ const NumoraInput = /*#__PURE__*/forwardRef((_ref, ref) => {
56
75
  }
57
76
  }
58
77
  function handleOnChange(e) {
78
+ isUserInputRef.current = true;
59
79
  handleOnChangeNumoraInput(e.nativeEvent, maxDecimals, caretPositionBeforeChange, formattingOptions);
60
80
  setCaretPositionBeforeChange(undefined);
61
81
  if (onChange) onChange(e);
62
82
  }
63
83
  function handleOnPaste(e) {
84
+ isUserInputRef.current = true;
64
85
  handleOnPasteNumoraInput(e.nativeEvent, maxDecimals, formattingOptions);
65
86
  if (onChange) onChange(e);
66
87
  }
@@ -78,10 +99,19 @@ const NumoraInput = /*#__PURE__*/forwardRef((_ref, ref) => {
78
99
  props.onBlur(e);
79
100
  }
80
101
  }
102
+ // Combine refs: forward the ref and also store it internally
103
+ const setRefs = React.useCallback(node => {
104
+ inputRef.current = node;
105
+ if (typeof ref === 'function') {
106
+ ref(node);
107
+ } else if (ref) {
108
+ ref.current = node;
109
+ }
110
+ }, [ref]);
81
111
  return jsx("input", {
82
112
  ...DEFAULT_PROPS,
83
113
  ...props,
84
- ref: ref,
114
+ ref: setRefs,
85
115
  onChange: handleOnChange,
86
116
  onKeyDown: handleOnKeyDown,
87
117
  onPaste: handleOnPaste,
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.js","sources":["../src/index.tsx"],"sourcesContent":["import React, { ChangeEvent, ClipboardEvent, forwardRef } from 'react';\nimport {\n NumoraInput,\n FormatOn,\n ThousandStyle,\n FormattingOptions,\n CaretPositionInfo,\n handleOnChangeNumoraInput,\n handleOnPasteNumoraInput,\n handleOnKeyDownNumoraInput,\n} from 'numora';\n\ninterface NumoraInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'type' | 'inputMode'> {\n maxDecimals?: number;\n onChange?: (e: ChangeEvent<HTMLInputElement> | ClipboardEvent<HTMLInputElement>) => void;\n\n // Formatting options\n formatOn?: FormatOn;\n thousandSeparator?: string;\n thousandStyle?: ThousandStyle;\n decimalSeparator?: string;\n decimalMinLength?: number;\n\n // Feature flags\n enableCompactNotation?: boolean;\n enableNegative?: boolean;\n enableLeadingZeros?: boolean;\n rawValueMode?: boolean;\n}\n\nconst DEFAULT_PROPS = {\n autoComplete: 'off',\n autoCorrect: 'off',\n autoCapitalize: 'none',\n minLength: 1,\n placeholder: '0.0',\n pattern: '^[0-9]*[.,]?[0-9]*$',\n spellCheck: false,\n step: 'any',\n};\n\nconst NumoraInput = forwardRef<HTMLInputElement, NumoraInputProps>(\n ({\n maxDecimals = 2,\n onChange,\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 ...props\n }: NumoraInputProps, ref: React.Ref<HTMLInputElement>) => {\n const [caretPositionBeforeChange, setCaretPositionBeforeChange] =\n React.useState<CaretPositionInfo>();\n\n const formattingOptions: FormattingOptions = {\n formatOn,\n thousandSeparator,\n ThousandStyle: thousandStyle as any,\n decimalSeparator,\n decimalMinLength,\n enableCompactNotation,\n enableNegative,\n enableLeadingZeros,\n rawValueMode,\n };\n\n function handleOnKeyDown(e: React.KeyboardEvent<HTMLInputElement>): void {\n const caretInfo = handleOnKeyDownNumoraInput(e.nativeEvent, formattingOptions);\n\n if (caretInfo) {\n setCaretPositionBeforeChange(caretInfo);\n } else {\n const input = e.currentTarget;\n setCaretPositionBeforeChange({\n selectionStart: input.selectionStart ?? 0,\n selectionEnd: input.selectionEnd ?? 0,\n });\n }\n\n if (props.onKeyDown) {\n props.onKeyDown(e);\n }\n }\n\n function handleOnChange(e: ChangeEvent<HTMLInputElement>): void {\n handleOnChangeNumoraInput(\n e.nativeEvent,\n maxDecimals,\n caretPositionBeforeChange,\n formattingOptions\n );\n setCaretPositionBeforeChange(undefined);\n if (onChange) onChange(e);\n }\n\n function handleOnPaste(e: ClipboardEvent<HTMLInputElement>): void {\n handleOnPasteNumoraInput(e.nativeEvent, maxDecimals, formattingOptions);\n if (onChange) onChange(e);\n }\n\n function handleOnFocus(e: React.FocusEvent<HTMLInputElement>): void {\n if (formatOn === FormatOn.Blur && thousandSeparator) {\n const target = e.currentTarget;\n target.value = target.value.replace(\n new RegExp(thousandSeparator.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'g'),\n ''\n );\n }\n\n if (props.onFocus) {\n props.onFocus(e);\n }\n }\n\n function handleOnBlur(e: React.FocusEvent<HTMLInputElement>): void {\n if (props.onBlur) {\n props.onBlur(e);\n }\n }\n\n return (\n <input\n {...DEFAULT_PROPS}\n {...props}\n ref={ref}\n onChange={handleOnChange}\n onKeyDown={handleOnKeyDown}\n onPaste={handleOnPaste}\n onFocus={handleOnFocus}\n onBlur={handleOnBlur}\n type=\"text\"\n inputMode=\"decimal\"\n />\n );\n }\n);\n\nNumoraInput.displayName = 'NumoraInput';\n\nexport { NumoraInput };\nexport { FormatOn, ThousandStyle } from 'numora';\nexport type { FormattingOptions, CaretPositionInfo } from 'numora';\n"],"names":["DEFAULT_PROPS","autoComplete","autoCorrect","autoCapitalize","minLength","placeholder","pattern","spellCheck","step","NumoraInput","forwardRef","_ref","ref","maxDecimals","onChange","formatOn","FormatOn","Blur","thousandSeparator","thousandStyle","ThousandStyle","Thousand","decimalSeparator","decimalMinLength","enableCompactNotation","enableNegative","enableLeadingZeros","rawValueMode","props","caretPositionBeforeChange","setCaretPositionBeforeChange","React","useState","formattingOptions","handleOnKeyDown","e","caretInfo","handleOnKeyDownNumoraInput","nativeEvent","input","currentTarget","selectionStart","selectionEnd","onKeyDown","handleOnChange","handleOnChangeNumoraInput","undefined","handleOnPaste","handleOnPasteNumoraInput","handleOnFocus","target","value","replace","RegExp","onFocus","handleOnBlur","onBlur","_jsx","onPaste","type","inputMode","displayName"],"mappings":"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"}
1
+ {"version":3,"file":"index.esm.js","sources":["../src/index.tsx"],"sourcesContent":["import React, { ChangeEvent, ClipboardEvent, forwardRef, useEffect, useRef } from 'react';\nimport {\n FormatOn,\n ThousandStyle,\n FormattingOptions,\n CaretPositionInfo,\n handleOnChangeNumoraInput,\n handleOnPasteNumoraInput,\n handleOnKeyDownNumoraInput,\n formatValueForNumora,\n} from 'numora';\n\ninterface NumoraInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'type' | 'inputMode'> {\n maxDecimals?: number;\n onChange?: (e: ChangeEvent<HTMLInputElement> | ClipboardEvent<HTMLInputElement>) => void;\n\n // Formatting options\n formatOn?: FormatOn;\n thousandSeparator?: string;\n thousandStyle?: ThousandStyle;\n decimalSeparator?: string;\n decimalMinLength?: number;\n\n // Feature flags\n enableCompactNotation?: boolean;\n enableNegative?: boolean;\n enableLeadingZeros?: boolean;\n rawValueMode?: boolean;\n}\n\nconst DEFAULT_PROPS = {\n autoComplete: 'off',\n autoCorrect: 'off',\n autoCapitalize: 'none',\n minLength: 1,\n placeholder: '0.0',\n pattern: '^[0-9]*[.,]?[0-9]*$',\n spellCheck: false,\n step: 'any',\n};\n\nconst NumoraInput = forwardRef<HTMLInputElement, NumoraInputProps>(\n ({\n maxDecimals = 2,\n onChange,\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 ...props\n }: NumoraInputProps, ref: React.Ref<HTMLInputElement>) => {\n const [caretPositionBeforeChange, setCaretPositionBeforeChange] =\n React.useState<CaretPositionInfo>();\n const inputRef = React.useRef<HTMLInputElement>(null);\n const isUserInputRef = useRef(false);\n const previousValueRef = useRef<string | undefined>(\n typeof props.value === 'string' ? props.value : props.value?.toString()\n );\n\n const formattingOptions: FormattingOptions = {\n formatOn,\n thousandSeparator,\n ThousandStyle: thousandStyle as any,\n decimalSeparator,\n decimalMinLength,\n enableCompactNotation,\n enableNegative,\n enableLeadingZeros,\n rawValueMode,\n };\n\n // Handle programmatic value changes (when value prop changes externally)\n useEffect(() => {\n const input = inputRef.current;\n if (!input) return;\n\n const currentValue = typeof props.value === 'string'\n ? props.value\n : props.value !== undefined\n ? String(props.value)\n : undefined;\n const previousValue = previousValueRef.current;\n\n // Only format if value changed externally (not from user input)\n if (!isUserInputRef.current && currentValue !== previousValue && currentValue !== undefined) {\n const formatted = formatValueForNumora(\n currentValue,\n maxDecimals,\n formattingOptions\n );\n\n // Only update if formatted value differs from current input value\n if (input.value !== formatted) {\n input.value = formatted;\n }\n }\n\n previousValueRef.current = currentValue;\n isUserInputRef.current = false;\n }, [props.value, maxDecimals, formatOn, thousandSeparator, thousandStyle, decimalSeparator, decimalMinLength, enableCompactNotation, enableNegative, enableLeadingZeros, rawValueMode]);\n\n function handleOnKeyDown(e: React.KeyboardEvent<HTMLInputElement>): void {\n const caretInfo = handleOnKeyDownNumoraInput(e.nativeEvent, formattingOptions);\n\n if (caretInfo) {\n setCaretPositionBeforeChange(caretInfo);\n } else {\n const input = e.currentTarget;\n setCaretPositionBeforeChange({\n selectionStart: input.selectionStart ?? 0,\n selectionEnd: input.selectionEnd ?? 0,\n });\n }\n\n if (props.onKeyDown) {\n props.onKeyDown(e);\n }\n }\n\n function handleOnChange(e: ChangeEvent<HTMLInputElement>): void {\n isUserInputRef.current = true;\n handleOnChangeNumoraInput(\n e.nativeEvent,\n maxDecimals,\n caretPositionBeforeChange,\n formattingOptions\n );\n setCaretPositionBeforeChange(undefined);\n if (onChange) onChange(e);\n }\n\n function handleOnPaste(e: ClipboardEvent<HTMLInputElement>): void {\n isUserInputRef.current = true;\n handleOnPasteNumoraInput(e.nativeEvent, maxDecimals, formattingOptions);\n if (onChange) onChange(e);\n }\n\n function handleOnFocus(e: React.FocusEvent<HTMLInputElement>): void {\n if (formatOn === FormatOn.Blur && thousandSeparator) {\n const target = e.currentTarget;\n target.value = target.value.replace(\n new RegExp(thousandSeparator.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'g'),\n ''\n );\n }\n\n if (props.onFocus) {\n props.onFocus(e);\n }\n }\n\n function handleOnBlur(e: React.FocusEvent<HTMLInputElement>): void {\n if (props.onBlur) {\n props.onBlur(e);\n }\n }\n\n // Combine refs: forward the ref and also store it internally\n const setRefs = React.useCallback((node: HTMLInputElement | null) => {\n inputRef.current = node;\n if (typeof ref === 'function') {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n }, [ref]);\n\n return (\n <input\n {...DEFAULT_PROPS}\n {...props}\n ref={setRefs}\n onChange={handleOnChange}\n onKeyDown={handleOnKeyDown}\n onPaste={handleOnPaste}\n onFocus={handleOnFocus}\n onBlur={handleOnBlur}\n type=\"text\"\n inputMode=\"decimal\"\n />\n );\n }\n);\n\nNumoraInput.displayName = 'NumoraInput';\n\nexport { NumoraInput };\nexport { FormatOn, ThousandStyle } from 'numora';\nexport type { FormattingOptions, CaretPositionInfo } from 'numora';\n"],"names":["DEFAULT_PROPS","autoComplete","autoCorrect","autoCapitalize","minLength","placeholder","pattern","spellCheck","step","NumoraInput","forwardRef","maxDecimals","onChange","formatOn","FormatOn","Blur","thousandSeparator","thousandStyle","ThousandStyle","Thousand","decimalSeparator","decimalMinLength","enableCompactNotation","enableNegative","enableLeadingZeros","rawValueMode","props","ref","caretPositionBeforeChange","setCaretPositionBeforeChange","React","useState","inputRef","useRef","isUserInputRef","previousValueRef","value","toString","formattingOptions","useEffect","input","current","currentValue","undefined","String","previousValue","formatted","formatValueForNumora","handleOnKeyDown","e","caretInfo","handleOnKeyDownNumoraInput","nativeEvent","currentTarget","selectionStart","selectionEnd","onKeyDown","handleOnChange","handleOnChangeNumoraInput","handleOnPaste","handleOnPasteNumoraInput","handleOnFocus","target","replace","RegExp","onFocus","handleOnBlur","onBlur","setRefs","useCallback","node","_jsx","onPaste","type","inputMode","displayName"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "numora-react",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Numora for React - Headless finance input library",
5
5
  "homepage": "https://numora.xyz/",
6
6
  "type": "module",
@@ -51,7 +51,7 @@
51
51
  "react-dom": "^18.0.0 || ^19.0.0"
52
52
  },
53
53
  "dependencies": {
54
- "numora": "2.0.1"
54
+ "numora": "^2.0.2"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@babel/core": "^7.26.10",