numeric-input-react 1.0.22 → 1.0.23
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.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/numeric-input.d.ts +2 -16
- package/dist/numeric-input.d.ts.map +1 -1
- package/dist/numeric-input.js +13 -545
- package/dist/numeric-input.js.map +1 -1
- package/dist/numeric-input.types.d.ts +15 -0
- package/dist/numeric-input.types.d.ts.map +1 -0
- package/dist/numeric-input.types.js +2 -0
- package/dist/numeric-input.types.js.map +1 -0
- package/dist/numeric-input.utils.d.ts +27 -0
- package/dist/numeric-input.utils.d.ts.map +1 -0
- package/dist/numeric-input.utils.js +143 -0
- package/dist/numeric-input.utils.js.map +1 -0
- package/dist/use-numeric-input.d.ts +28 -0
- package/dist/use-numeric-input.d.ts.map +1 -0
- package/dist/use-numeric-input.js +420 -0
- package/dist/use-numeric-input.js.map +1 -0
- package/package.json +17 -3
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
export type NumericInputValue = {
|
|
3
|
+
value: number;
|
|
4
|
+
formattedValue: string;
|
|
5
|
+
};
|
|
6
|
+
export type NumericInputProps = ComponentProps<'input'> & {
|
|
7
|
+
onValueChange: (valueObject: NumericInputValue) => void;
|
|
8
|
+
separator?: string;
|
|
9
|
+
allowDecimal?: boolean;
|
|
10
|
+
allowNegative?: boolean;
|
|
11
|
+
minValue?: number;
|
|
12
|
+
maxValue?: number;
|
|
13
|
+
maxDecimalPlaces?: number;
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=numeric-input.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"numeric-input.types.d.ts","sourceRoot":"","sources":["../src/numeric-input.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAE3C,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,cAAc,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,CAAC,GAAG;IACxD,aAAa,EAAE,CAAC,WAAW,EAAE,iBAAiB,KAAK,IAAI,CAAA;IACvD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"numeric-input.types.js","sourceRoot":"","sources":["../src/numeric-input.types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts full-width Japanese characters to half-width equivalents
|
|
3
|
+
* Supports: numbers (0-9), period (.), comma (,), minus (-)
|
|
4
|
+
*/
|
|
5
|
+
export declare const convertFullWidthToHalfWidth: (str: string) => string;
|
|
6
|
+
/**
|
|
7
|
+
* Escapes special regex characters in a string
|
|
8
|
+
*/
|
|
9
|
+
export declare const escapeRegex: (str: string) => string;
|
|
10
|
+
/**
|
|
11
|
+
* Checks if a string is a minus sign (half-width, full-width, katakana, or mathematical)
|
|
12
|
+
*/
|
|
13
|
+
export declare const isMinusSign: (str: string) => boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Converts any minus sign variant to half-width minus
|
|
16
|
+
*/
|
|
17
|
+
export declare const normalizeMinusSign: (str: string) => string;
|
|
18
|
+
/**
|
|
19
|
+
* Parses a value prop (string or number) to a number, removing separator if present
|
|
20
|
+
*/
|
|
21
|
+
export declare const parseValueProp: (value: string | number | readonly string[] | null | undefined, separator?: string) => number;
|
|
22
|
+
/**
|
|
23
|
+
* Normalizes the input string by removing invalid characters
|
|
24
|
+
* and ensuring proper decimal point handling
|
|
25
|
+
*/
|
|
26
|
+
export declare const normalizeNumericInput: (input: string, allowDecimal: boolean, allowNegative: boolean, maxLength?: number) => string;
|
|
27
|
+
//# sourceMappingURL=numeric-input.utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"numeric-input.utils.d.ts","sourceRoot":"","sources":["../src/numeric-input.utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,2BAA2B,GAAI,KAAK,MAAM,KAAG,MAWzD,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,KAAG,MAEzC,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,KAAG,OAEzC,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAAI,KAAK,MAAM,KAAG,MAEhD,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,cAAc,GACzB,OAAO,MAAM,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,EAC7D,YAAY,MAAM,KACjB,MA2BF,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,qBAAqB,GAChC,OAAO,MAAM,EACb,cAAc,OAAO,EACrB,eAAe,OAAO,EACtB,YAAY,MAAM,KACjB,MAiFF,CAAA"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts full-width Japanese characters to half-width equivalents
|
|
3
|
+
* Supports: numbers (0-9), period (.), comma (,), minus (-)
|
|
4
|
+
*/
|
|
5
|
+
export const convertFullWidthToHalfWidth = (str) => {
|
|
6
|
+
return str
|
|
7
|
+
.replace(/[0-9]/g, (char) => {
|
|
8
|
+
// Convert full-width numbers (0-9) to half-width (0-9)
|
|
9
|
+
return String.fromCharCode(char.charCodeAt(0) - 0xfee0);
|
|
10
|
+
})
|
|
11
|
+
.replace(/[.]/g, '.') // Convert full-width period (.) to half-width (.)
|
|
12
|
+
.replace(/[,]/g, ',') // Convert full-width comma (,) to half-width (,)
|
|
13
|
+
.replace(/[-]/g, '-') // Convert full-width minus (-, U+FF0D) to half-width (-)
|
|
14
|
+
.replace(/[ー]/g, '-') // Convert katakana long vowel mark (ー, U+30FC) to minus (-) when used as minus
|
|
15
|
+
.replace(/[−]/g, '-'); // Convert mathematical minus sign (−, U+2212) to half-width (-)
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Escapes special regex characters in a string
|
|
19
|
+
*/
|
|
20
|
+
export const escapeRegex = (str) => {
|
|
21
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Checks if a string is a minus sign (half-width, full-width, katakana, or mathematical)
|
|
25
|
+
*/
|
|
26
|
+
export const isMinusSign = (str) => {
|
|
27
|
+
return str === '-' || str === '-' || str === 'ー' || str === '−';
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Converts any minus sign variant to half-width minus
|
|
31
|
+
*/
|
|
32
|
+
export const normalizeMinusSign = (str) => {
|
|
33
|
+
return isMinusSign(str) ? '-' : str;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Parses a value prop (string or number) to a number, removing separator if present
|
|
37
|
+
*/
|
|
38
|
+
export const parseValueProp = (value, separator) => {
|
|
39
|
+
if (value === null || value === undefined || value === '') {
|
|
40
|
+
return NaN;
|
|
41
|
+
}
|
|
42
|
+
if (typeof value === 'number') {
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
// Handle array case (shouldn't happen for numeric input, but handle gracefully)
|
|
46
|
+
if (Array.isArray(value)) {
|
|
47
|
+
const firstValue = value[0];
|
|
48
|
+
if (!firstValue) {
|
|
49
|
+
return NaN;
|
|
50
|
+
}
|
|
51
|
+
const cleanedValue = separator
|
|
52
|
+
? firstValue.replace(new RegExp(`[${escapeRegex(separator)}]`, 'g'), '')
|
|
53
|
+
: firstValue;
|
|
54
|
+
return Number(cleanedValue);
|
|
55
|
+
}
|
|
56
|
+
// At this point, value must be a string
|
|
57
|
+
const cleanedValue = separator
|
|
58
|
+
? value.replace(new RegExp(`[${escapeRegex(separator)}]`, 'g'), '')
|
|
59
|
+
: value;
|
|
60
|
+
return Number(cleanedValue);
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Normalizes the input string by removing invalid characters
|
|
64
|
+
* and ensuring proper decimal point handling
|
|
65
|
+
*/
|
|
66
|
+
export const normalizeNumericInput = (input, allowDecimal, allowNegative, maxLength) => {
|
|
67
|
+
let normalized = input;
|
|
68
|
+
// Remove all characters except digits, decimal point, and optionally minus sign
|
|
69
|
+
const allowedChars = allowDecimal
|
|
70
|
+
? allowNegative
|
|
71
|
+
? /[^0-9.\-]/g
|
|
72
|
+
: /[^0-9.]/g
|
|
73
|
+
: allowNegative
|
|
74
|
+
? /[^0-9\-]/g
|
|
75
|
+
: /[^0-9]/g;
|
|
76
|
+
normalized = normalized.replace(allowedChars, '');
|
|
77
|
+
// Handle negative sign: only allow at the start
|
|
78
|
+
if (allowNegative) {
|
|
79
|
+
const minusCount = (normalized.match(/-/g) || []).length;
|
|
80
|
+
if (minusCount > 1) {
|
|
81
|
+
// Keep only the first minus sign
|
|
82
|
+
normalized = normalized.replace(/-/g, (match, offset) => {
|
|
83
|
+
return offset === 0 ? match : '';
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
// If minus is not at the start, move it to the start
|
|
87
|
+
if (normalized.includes('-') && !normalized.startsWith('-')) {
|
|
88
|
+
normalized = `-${normalized.replace(/-/g, '')}`;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
normalized = normalized.replace(/-/g, '');
|
|
93
|
+
}
|
|
94
|
+
// Handle decimal point: only allow one, and only if decimals are allowed
|
|
95
|
+
if (allowDecimal) {
|
|
96
|
+
const decimalCount = (normalized.match(/\./g) || []).length;
|
|
97
|
+
if (decimalCount > 1) {
|
|
98
|
+
// Keep only the first decimal point
|
|
99
|
+
const firstDecimalIndex = normalized.indexOf('.');
|
|
100
|
+
normalized =
|
|
101
|
+
normalized.slice(0, firstDecimalIndex + 1) +
|
|
102
|
+
normalized.slice(firstDecimalIndex + 1).replace(/\./g, '');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
normalized = normalized.replace(/\./g, '');
|
|
107
|
+
}
|
|
108
|
+
// Apply maxLength if specified
|
|
109
|
+
// maxLength should only apply to digits, not to minus sign or decimal point
|
|
110
|
+
if (maxLength) {
|
|
111
|
+
// Count only digits in the normalized string
|
|
112
|
+
const digitCount = (normalized.match(/\d/g) || []).length;
|
|
113
|
+
if (digitCount > maxLength) {
|
|
114
|
+
// Remove excess digits from the end, preserving minus sign and decimal point
|
|
115
|
+
const hasMinus = normalized.startsWith('-');
|
|
116
|
+
let result = hasMinus ? '-' : '';
|
|
117
|
+
let digitsSeen = 0;
|
|
118
|
+
let decimalAdded = false;
|
|
119
|
+
// Start from after minus sign if present
|
|
120
|
+
for (let i = hasMinus ? 1 : 0; i < normalized.length; i++) {
|
|
121
|
+
const char = normalized[i];
|
|
122
|
+
if (/\d/.test(char)) {
|
|
123
|
+
// Only keep digits up to maxLength
|
|
124
|
+
if (digitsSeen < maxLength) {
|
|
125
|
+
result += char;
|
|
126
|
+
digitsSeen++;
|
|
127
|
+
}
|
|
128
|
+
// Skip excess digits
|
|
129
|
+
}
|
|
130
|
+
else if (char === '.' && !decimalAdded) {
|
|
131
|
+
// Only keep decimal point if we have at least one digit and haven't added one yet
|
|
132
|
+
if (digitsSeen > 0) {
|
|
133
|
+
result += char;
|
|
134
|
+
decimalAdded = true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
normalized = result;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return normalized;
|
|
142
|
+
};
|
|
143
|
+
//# sourceMappingURL=numeric-input.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"numeric-input.utils.js","sourceRoot":"","sources":["../src/numeric-input.utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type CompositionEvent, type FocusEvent } from 'react';
|
|
2
|
+
import type { NumericInputValue, NumericInputProps } from './numeric-input.types';
|
|
3
|
+
type UseNumericInputOptions = {
|
|
4
|
+
value: NumericInputProps['value'];
|
|
5
|
+
separator?: string;
|
|
6
|
+
allowDecimal?: boolean;
|
|
7
|
+
allowNegative?: boolean;
|
|
8
|
+
minValue?: number;
|
|
9
|
+
maxValue?: number;
|
|
10
|
+
maxDecimalPlaces?: number;
|
|
11
|
+
maxLength?: number;
|
|
12
|
+
onValueChange: (valueObject: NumericInputValue) => void;
|
|
13
|
+
onCompositionStart?: NumericInputProps['onCompositionStart'];
|
|
14
|
+
onCompositionEnd?: NumericInputProps['onCompositionEnd'];
|
|
15
|
+
onBlur?: NumericInputProps['onBlur'];
|
|
16
|
+
};
|
|
17
|
+
export declare const useNumericInput: (options: UseNumericInputOptions) => {
|
|
18
|
+
inputRef: import("react").RefObject<HTMLInputElement | null>;
|
|
19
|
+
inputMode: "decimal" | "numeric";
|
|
20
|
+
displayValue: string;
|
|
21
|
+
hasProcessedComposition: import("react").RefObject<boolean>;
|
|
22
|
+
handleBlur: (e: FocusEvent<HTMLInputElement>) => void;
|
|
23
|
+
handleValueChange: (inputValue: string, skipCompositionCheck?: boolean) => void;
|
|
24
|
+
handleCompositionEnd: (e: CompositionEvent<HTMLInputElement>) => void;
|
|
25
|
+
handleCompositionStart: (e: CompositionEvent<HTMLInputElement>) => void;
|
|
26
|
+
};
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=use-numeric-input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-numeric-input.d.ts","sourceRoot":"","sources":["../src/use-numeric-input.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,UAAU,EAMhB,MAAM,OAAO,CAAA;AAQd,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAEjF,KAAK,sBAAsB,GAAG;IAC5B,KAAK,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAA;IACjC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,CAAC,WAAW,EAAE,iBAAiB,KAAK,IAAI,CAAA;IACvD,kBAAkB,CAAC,EAAE,iBAAiB,CAAC,oBAAoB,CAAC,CAAA;IAC5D,gBAAgB,CAAC,EAAE,iBAAiB,CAAC,kBAAkB,CAAC,CAAA;IACxD,MAAM,CAAC,EAAE,iBAAiB,CAAC,QAAQ,CAAC,CAAA;CACrC,CAAA;AAED,eAAO,MAAM,eAAe,GAAI,SAAS,sBAAsB;;;;;oBAwTvD,UAAU,CAAC,gBAAgB,CAAC;oCAzPnB,MAAM;8BAqOf,gBAAgB,CAAC,gBAAgB,CAAC;gCA/BlC,gBAAgB,CAAC,gBAAgB,CAAC;CAgQzC,CAAA"}
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
|
|
2
|
+
import { convertFullWidthToHalfWidth, isMinusSign, normalizeMinusSign, normalizeNumericInput, parseValueProp, } from './numeric-input.utils';
|
|
3
|
+
export const useNumericInput = (options) => {
|
|
4
|
+
const { value, maxValue, minValue, separator, maxLength, maxDecimalPlaces, allowDecimal = false, allowNegative = false, onBlur, onValueChange, onCompositionEnd, onCompositionStart, } = options;
|
|
5
|
+
// Validate min/max values
|
|
6
|
+
if (minValue !== undefined && maxValue !== undefined && minValue > maxValue) {
|
|
7
|
+
console.warn('NumericInput: minValue should be less than or equal to maxValue');
|
|
8
|
+
}
|
|
9
|
+
const isComposing = useRef(false);
|
|
10
|
+
const inputRef = useRef(null);
|
|
11
|
+
// Store the raw input value during IME composition
|
|
12
|
+
const [composingValue, setComposingValue] = useState('');
|
|
13
|
+
// Track if we've already processed the value from composition end
|
|
14
|
+
const hasProcessedComposition = useRef(false);
|
|
15
|
+
// Store the raw input string to preserve leading zeros
|
|
16
|
+
const [rawInputValue, setRawInputValue] = useState('');
|
|
17
|
+
const formatValue = useCallback((numValue) => {
|
|
18
|
+
if (Number.isNaN(numValue) || !Number.isFinite(numValue)) {
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
21
|
+
const valueStr = numValue.toString();
|
|
22
|
+
// If no separator, return as is
|
|
23
|
+
if (!separator) {
|
|
24
|
+
return valueStr;
|
|
25
|
+
}
|
|
26
|
+
// Split into integer and decimal parts
|
|
27
|
+
const [integerPart, decimalPart] = valueStr.split('.');
|
|
28
|
+
// Format integer part with separator (thousands separator)
|
|
29
|
+
const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, separator);
|
|
30
|
+
// Combine with decimal part if exists
|
|
31
|
+
return decimalPart !== undefined
|
|
32
|
+
? `${formattedInteger}.${decimalPart}`
|
|
33
|
+
: formattedInteger;
|
|
34
|
+
}, [separator]);
|
|
35
|
+
const handleValueChange = useCallback((inputValue, skipCompositionCheck = false) => {
|
|
36
|
+
// During IME composition, update the composing value for display
|
|
37
|
+
// Don't convert full-width to half-width yet - wait for composition end
|
|
38
|
+
if (!skipCompositionCheck && isComposing.current) {
|
|
39
|
+
setComposingValue(inputValue);
|
|
40
|
+
// Store raw input value (could be full-width) for later processing
|
|
41
|
+
setRawInputValue(inputValue);
|
|
42
|
+
// Still notify parent but don't process the value
|
|
43
|
+
onValueChange({
|
|
44
|
+
value: 0,
|
|
45
|
+
formattedValue: inputValue,
|
|
46
|
+
});
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// Convert full-width Japanese characters to half-width
|
|
50
|
+
let rawValue = convertFullWidthToHalfWidth(inputValue);
|
|
51
|
+
// Remove scientific notation (e.g., "1e10", "1E10")
|
|
52
|
+
// This prevents unexpected number conversions
|
|
53
|
+
rawValue = rawValue.replace(/[eE]/g, '');
|
|
54
|
+
// Normalize the input (remove invalid chars, handle decimals, negatives)
|
|
55
|
+
rawValue = normalizeNumericInput(rawValue, allowDecimal, allowNegative, maxLength);
|
|
56
|
+
// Limit decimal places if specified
|
|
57
|
+
if (maxDecimalPlaces !== undefined && allowDecimal) {
|
|
58
|
+
const decimalIndex = rawValue.indexOf('.');
|
|
59
|
+
if (decimalIndex !== -1) {
|
|
60
|
+
const integerPart = rawValue.slice(0, decimalIndex);
|
|
61
|
+
const decimalPart = rawValue.slice(decimalIndex + 1);
|
|
62
|
+
if (decimalPart.length > maxDecimalPlaces) {
|
|
63
|
+
rawValue = `${integerPart}.${decimalPart.slice(0, maxDecimalPlaces)}`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Handle empty input first (before processing leading zeros)
|
|
68
|
+
if (rawValue === '') {
|
|
69
|
+
setRawInputValue('');
|
|
70
|
+
onValueChange({
|
|
71
|
+
value: 0,
|
|
72
|
+
formattedValue: '',
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
// Handle only minus sign (half-width or full-width converted): preserve it if allowNegative is true
|
|
77
|
+
if (rawValue === '-') {
|
|
78
|
+
if (allowNegative) {
|
|
79
|
+
setRawInputValue('-');
|
|
80
|
+
onValueChange({
|
|
81
|
+
value: 0,
|
|
82
|
+
formattedValue: '-',
|
|
83
|
+
});
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
// If negative is not allowed, treat as empty
|
|
88
|
+
setRawInputValue('');
|
|
89
|
+
onValueChange({
|
|
90
|
+
value: 0,
|
|
91
|
+
formattedValue: '',
|
|
92
|
+
});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Remove leading zeros except for single "0" or "0." patterns
|
|
97
|
+
const shouldKeepSingleZero = rawValue === '0' ||
|
|
98
|
+
rawValue === '-0' ||
|
|
99
|
+
rawValue === '0.' ||
|
|
100
|
+
rawValue === '-0.';
|
|
101
|
+
if (!shouldKeepSingleZero) {
|
|
102
|
+
const hasMinus = rawValue.startsWith('-');
|
|
103
|
+
const withoutMinus = hasMinus ? rawValue.slice(1) : rawValue;
|
|
104
|
+
const hasDecimal = withoutMinus.includes('.');
|
|
105
|
+
if (hasDecimal) {
|
|
106
|
+
const [integerPart, decimalPart] = withoutMinus.split('.');
|
|
107
|
+
const cleanedInteger = integerPart.replace(/^0+/, '');
|
|
108
|
+
const prefix = hasMinus ? '-' : '';
|
|
109
|
+
if (cleanedInteger === '' && decimalPart) {
|
|
110
|
+
rawValue = `${prefix}0.${decimalPart}`;
|
|
111
|
+
}
|
|
112
|
+
else if (cleanedInteger === '') {
|
|
113
|
+
rawValue = `${prefix}0`;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
rawValue = `${prefix}${cleanedInteger}.${decimalPart}`;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
const cleaned = withoutMinus.replace(/^0+/, '');
|
|
121
|
+
const prefix = hasMinus ? '-' : '';
|
|
122
|
+
rawValue = cleaned === '' ? `${prefix}0` : `${prefix}${cleaned}`;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Store the raw input value to preserve single "0" only
|
|
126
|
+
setRawInputValue(rawValue);
|
|
127
|
+
// Convert to number
|
|
128
|
+
const valueAsNumber = Number(rawValue);
|
|
129
|
+
// Handle invalid numbers
|
|
130
|
+
if (Number.isNaN(valueAsNumber) || !Number.isFinite(valueAsNumber)) {
|
|
131
|
+
setRawInputValue('');
|
|
132
|
+
onValueChange({
|
|
133
|
+
value: 0,
|
|
134
|
+
formattedValue: '',
|
|
135
|
+
});
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// Handle value exceeding MAX_SAFE_INTEGER
|
|
139
|
+
if (Math.abs(valueAsNumber) > Number.MAX_SAFE_INTEGER) {
|
|
140
|
+
const clampedValue = valueAsNumber > 0 ? Number.MAX_SAFE_INTEGER : -Number.MAX_SAFE_INTEGER;
|
|
141
|
+
const clampedString = clampedValue.toString();
|
|
142
|
+
setRawInputValue(clampedString);
|
|
143
|
+
onValueChange({
|
|
144
|
+
value: clampedValue,
|
|
145
|
+
formattedValue: formatValue(clampedValue),
|
|
146
|
+
});
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// Only preserve single "0" or "0." patterns (not multiple leading zeros like "01", "0123")
|
|
150
|
+
const isSingleZero = rawValue === '0' ||
|
|
151
|
+
rawValue === '-0' ||
|
|
152
|
+
rawValue.startsWith('0.') ||
|
|
153
|
+
rawValue.startsWith('-0.');
|
|
154
|
+
// Check if the value ends with a decimal point (e.g., "2.", "-2.", "123.")
|
|
155
|
+
// This allows users to continue typing decimal digits
|
|
156
|
+
const endsWithDecimalPoint = allowDecimal && rawValue.endsWith('.') && !rawValue.endsWith('..');
|
|
157
|
+
// Apply min/max validation only for complete numbers (not intermediate typing states)
|
|
158
|
+
// Allow intermediate values while typing (e.g., allow "1000" if max is 100, user might be typing "100")
|
|
159
|
+
let finalValue = valueAsNumber;
|
|
160
|
+
let finalRawValue = rawValue;
|
|
161
|
+
let shouldClamp = false;
|
|
162
|
+
// Only clamp if the value is complete (not ending with decimal point and not a single zero pattern)
|
|
163
|
+
if (!isSingleZero && !endsWithDecimalPoint) {
|
|
164
|
+
if (minValue !== undefined && finalValue < minValue) {
|
|
165
|
+
finalValue = minValue;
|
|
166
|
+
finalRawValue = minValue.toString();
|
|
167
|
+
shouldClamp = true;
|
|
168
|
+
}
|
|
169
|
+
if (maxValue !== undefined && finalValue > maxValue) {
|
|
170
|
+
finalValue = maxValue;
|
|
171
|
+
finalRawValue = maxValue.toString();
|
|
172
|
+
shouldClamp = true;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// If clamped, update rawInputValue
|
|
176
|
+
if (shouldClamp) {
|
|
177
|
+
setRawInputValue(finalRawValue);
|
|
178
|
+
}
|
|
179
|
+
// If it's a single zero pattern or ends with decimal point, use the raw value for display
|
|
180
|
+
if (isSingleZero || endsWithDecimalPoint) {
|
|
181
|
+
// Use the raw value as-is to preserve single "0" or trailing decimal point
|
|
182
|
+
onValueChange({
|
|
183
|
+
value: finalValue,
|
|
184
|
+
formattedValue: shouldClamp ? formatValue(finalValue) : rawValue,
|
|
185
|
+
});
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
// Valid number without leading zeros - format and return
|
|
189
|
+
onValueChange({
|
|
190
|
+
value: finalValue,
|
|
191
|
+
formattedValue: formatValue(finalValue),
|
|
192
|
+
});
|
|
193
|
+
}, [
|
|
194
|
+
allowDecimal,
|
|
195
|
+
allowNegative,
|
|
196
|
+
maxLength,
|
|
197
|
+
onValueChange,
|
|
198
|
+
formatValue,
|
|
199
|
+
separator,
|
|
200
|
+
minValue,
|
|
201
|
+
maxValue,
|
|
202
|
+
maxDecimalPlaces,
|
|
203
|
+
]);
|
|
204
|
+
const handleCompositionStart = useCallback((e) => {
|
|
205
|
+
isComposing.current = true;
|
|
206
|
+
hasProcessedComposition.current = false;
|
|
207
|
+
// Store the current input value when composition starts
|
|
208
|
+
setComposingValue(e.currentTarget.value);
|
|
209
|
+
// Handle custom onCompositionStart
|
|
210
|
+
if (onCompositionStart) {
|
|
211
|
+
onCompositionStart(e);
|
|
212
|
+
}
|
|
213
|
+
}, [onCompositionStart]);
|
|
214
|
+
// Helper to process value after conversion (used in composition end and blur)
|
|
215
|
+
const processConvertedValue = useCallback((convertedValue) => {
|
|
216
|
+
if (allowNegative && convertedValue === '-') {
|
|
217
|
+
setRawInputValue('-');
|
|
218
|
+
onValueChange({
|
|
219
|
+
value: 0,
|
|
220
|
+
formattedValue: '-',
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
handleValueChange(convertedValue, true);
|
|
225
|
+
}
|
|
226
|
+
}, [allowNegative, handleValueChange, onValueChange]);
|
|
227
|
+
const handleCompositionEnd = useCallback((e) => {
|
|
228
|
+
isComposing.current = false;
|
|
229
|
+
const finalValue = e.currentTarget.value;
|
|
230
|
+
setComposingValue('');
|
|
231
|
+
hasProcessedComposition.current = true;
|
|
232
|
+
if (onCompositionEnd) {
|
|
233
|
+
onCompositionEnd(e);
|
|
234
|
+
}
|
|
235
|
+
requestAnimationFrame(() => {
|
|
236
|
+
const convertedValue = convertFullWidthToHalfWidth(finalValue);
|
|
237
|
+
processConvertedValue(convertedValue);
|
|
238
|
+
hasProcessedComposition.current = false;
|
|
239
|
+
});
|
|
240
|
+
}, [onCompositionEnd, processConvertedValue]);
|
|
241
|
+
const handleBlur = useCallback((e) => {
|
|
242
|
+
const currentValue = e.target.value;
|
|
243
|
+
const shouldPreserveMinus = allowNegative && (isMinusSign(rawInputValue) || isMinusSign(currentValue));
|
|
244
|
+
// Handle composition states
|
|
245
|
+
if (isComposing.current) {
|
|
246
|
+
isComposing.current = false;
|
|
247
|
+
const convertedValue = convertFullWidthToHalfWidth(e.target.value);
|
|
248
|
+
setComposingValue('');
|
|
249
|
+
hasProcessedComposition.current = true;
|
|
250
|
+
processConvertedValue(convertedValue);
|
|
251
|
+
}
|
|
252
|
+
else if (composingValue !== '') {
|
|
253
|
+
const convertedValue = convertFullWidthToHalfWidth(composingValue);
|
|
254
|
+
processConvertedValue(convertedValue);
|
|
255
|
+
setComposingValue('');
|
|
256
|
+
}
|
|
257
|
+
else if (!hasProcessedComposition.current && e.target.value) {
|
|
258
|
+
const convertedValue = convertFullWidthToHalfWidth(e.target.value);
|
|
259
|
+
handleValueChange(convertedValue, true);
|
|
260
|
+
}
|
|
261
|
+
// Apply min/max validation on blur
|
|
262
|
+
if (rawInputValue !== '' && !(allowNegative && isMinusSign(rawInputValue))) {
|
|
263
|
+
const convertedValue = convertFullWidthToHalfWidth(rawInputValue);
|
|
264
|
+
const numValue = Number(convertedValue);
|
|
265
|
+
if (!Number.isNaN(numValue) && Number.isFinite(numValue)) {
|
|
266
|
+
let clampedValue = numValue;
|
|
267
|
+
let shouldUpdate = false;
|
|
268
|
+
if (minValue !== undefined && clampedValue < minValue) {
|
|
269
|
+
clampedValue = minValue;
|
|
270
|
+
shouldUpdate = true;
|
|
271
|
+
}
|
|
272
|
+
if (maxValue !== undefined && clampedValue > maxValue) {
|
|
273
|
+
clampedValue = maxValue;
|
|
274
|
+
shouldUpdate = true;
|
|
275
|
+
}
|
|
276
|
+
if (shouldUpdate) {
|
|
277
|
+
const clampedString = clampedValue.toString();
|
|
278
|
+
setRawInputValue(clampedString);
|
|
279
|
+
onValueChange({
|
|
280
|
+
value: clampedValue,
|
|
281
|
+
formattedValue: formatValue(clampedValue),
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Normalize minus sign to half-width if needed
|
|
287
|
+
if (shouldPreserveMinus && isMinusSign(rawInputValue) && rawInputValue !== '-') {
|
|
288
|
+
setRawInputValue('-');
|
|
289
|
+
onValueChange({
|
|
290
|
+
value: 0,
|
|
291
|
+
formattedValue: '-',
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
hasProcessedComposition.current = false;
|
|
295
|
+
if (onBlur) {
|
|
296
|
+
onBlur(e);
|
|
297
|
+
}
|
|
298
|
+
}, [
|
|
299
|
+
composingValue,
|
|
300
|
+
onBlur,
|
|
301
|
+
handleValueChange,
|
|
302
|
+
rawInputValue,
|
|
303
|
+
minValue,
|
|
304
|
+
maxValue,
|
|
305
|
+
formatValue,
|
|
306
|
+
allowNegative,
|
|
307
|
+
processConvertedValue,
|
|
308
|
+
]);
|
|
309
|
+
// Reset rawInputValue when value prop changes externally (e.g., form reset)
|
|
310
|
+
useEffect(() => {
|
|
311
|
+
const numValue = parseValueProp(value, separator);
|
|
312
|
+
if (Number.isNaN(numValue)) {
|
|
313
|
+
// Preserve minus sign if allowNegative is true and user is typing
|
|
314
|
+
if (allowNegative && isMinusSign(rawInputValue)) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
setRawInputValue('');
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
// If the value is 0, preserve rawInputValue if it's a special pattern
|
|
321
|
+
if (numValue === 0) {
|
|
322
|
+
const isSingleZero = rawInputValue === '0' ||
|
|
323
|
+
rawInputValue === '-0' ||
|
|
324
|
+
isMinusSign(rawInputValue) ||
|
|
325
|
+
rawInputValue.startsWith('0.') ||
|
|
326
|
+
rawInputValue.startsWith('-0.');
|
|
327
|
+
// Check if rawInputValue is a negative number (preserve it when allowNegative is true)
|
|
328
|
+
if (allowNegative && rawInputValue !== '') {
|
|
329
|
+
const convertedRawValue = convertFullWidthToHalfWidth(rawInputValue);
|
|
330
|
+
const rawAsNumber = Number(convertedRawValue);
|
|
331
|
+
if (!Number.isNaN(rawAsNumber) &&
|
|
332
|
+
Number.isFinite(rawAsNumber) &&
|
|
333
|
+
rawAsNumber < 0) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (!isSingleZero) {
|
|
338
|
+
setRawInputValue('0');
|
|
339
|
+
}
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
// For non-zero values, check if the numeric value matches what we'd get from rawInputValue
|
|
343
|
+
if (rawInputValue !== '') {
|
|
344
|
+
// Preserve minus sign only if allowNegative is true
|
|
345
|
+
if (allowNegative && isMinusSign(rawInputValue)) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const convertedRawValue = convertFullWidthToHalfWidth(rawInputValue);
|
|
349
|
+
const rawAsNumber = Number(convertedRawValue);
|
|
350
|
+
if (allowNegative && convertedRawValue.startsWith('-')) {
|
|
351
|
+
if (rawAsNumber === numValue) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
else if (numValue > 0 && Math.abs(rawAsNumber) === numValue) {
|
|
355
|
+
setRawInputValue('');
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
else if (rawAsNumber !== numValue) {
|
|
362
|
+
setRawInputValue('');
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}, [value, separator, rawInputValue, allowNegative]);
|
|
366
|
+
// Format the display value
|
|
367
|
+
const displayValue = useMemo(() => {
|
|
368
|
+
if (composingValue !== '') {
|
|
369
|
+
return composingValue;
|
|
370
|
+
}
|
|
371
|
+
if (rawInputValue === '') {
|
|
372
|
+
const numValue = parseValueProp(value, separator);
|
|
373
|
+
if (Number.isNaN(numValue)) {
|
|
374
|
+
return '';
|
|
375
|
+
}
|
|
376
|
+
if (numValue === 0) {
|
|
377
|
+
return '0';
|
|
378
|
+
}
|
|
379
|
+
if (Number.isFinite(numValue)) {
|
|
380
|
+
return formatValue(numValue);
|
|
381
|
+
}
|
|
382
|
+
return '';
|
|
383
|
+
}
|
|
384
|
+
if (rawInputValue !== '') {
|
|
385
|
+
const isSingleZero = rawInputValue === '0' ||
|
|
386
|
+
rawInputValue === '-0' ||
|
|
387
|
+
rawInputValue.startsWith('0.') ||
|
|
388
|
+
rawInputValue.startsWith('-0.');
|
|
389
|
+
const isMinusOnly = allowNegative && isMinusSign(rawInputValue);
|
|
390
|
+
const endsWithDecimalPoint = allowDecimal &&
|
|
391
|
+
rawInputValue.endsWith('.') &&
|
|
392
|
+
!rawInputValue.endsWith('..');
|
|
393
|
+
if (isSingleZero || isMinusOnly || endsWithDecimalPoint) {
|
|
394
|
+
return normalizeMinusSign(rawInputValue);
|
|
395
|
+
}
|
|
396
|
+
const rawAsNumber = Number(rawInputValue);
|
|
397
|
+
if (!Number.isNaN(rawAsNumber) && Number.isFinite(rawAsNumber)) {
|
|
398
|
+
return formatValue(rawAsNumber);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
const numValue = parseValueProp(value, separator);
|
|
402
|
+
if (Number.isNaN(numValue) || !Number.isFinite(numValue)) {
|
|
403
|
+
return '';
|
|
404
|
+
}
|
|
405
|
+
return formatValue(numValue);
|
|
406
|
+
}, [value, formatValue, separator, composingValue, rawInputValue, allowNegative, allowDecimal]);
|
|
407
|
+
// Determine appropriate inputMode for mobile keyboards
|
|
408
|
+
const inputMode = allowDecimal ? 'decimal' : 'numeric';
|
|
409
|
+
return {
|
|
410
|
+
inputRef,
|
|
411
|
+
inputMode,
|
|
412
|
+
displayValue,
|
|
413
|
+
hasProcessedComposition,
|
|
414
|
+
handleBlur,
|
|
415
|
+
handleValueChange,
|
|
416
|
+
handleCompositionEnd,
|
|
417
|
+
handleCompositionStart,
|
|
418
|
+
};
|
|
419
|
+
};
|
|
420
|
+
//# sourceMappingURL=use-numeric-input.js.map
|