diff --git a/src/util/input-utils.js b/src/util/input-utils.js index 86c03fe4..65bb4dd4 100644 --- a/src/util/input-utils.js +++ b/src/util/input-utils.js @@ -23,79 +23,82 @@ export const onlyNumberWithDotInputChange = (e, callback) => { // ============================= // Number normalization utilities // ============================= -// 1) Normalize any string to NFKC and keep only ASCII digits 0-9. -// 1) Normalize any string to keep only ASCII digits 0-9 -// IME composition state tracking -let isComposingDigits = false; -let lastDigitsValue = ''; -let isComposingDecimal = false; -let lastDecimalValue = ''; +// 마지막으로 유효했던 값 추적 +let lastValidDigits = ''; +let lastValidDecimal = ''; /** * 숫자만 포함된 문자열로 정규화 (0-9) - * - 전각 숫자를 반각으로 변환 - * - 숫자가 아닌 문자 제거 - * - IME 입력 대응 + * - 전각 숫자 -> 반각 + * - 숫자 외 제거 + * - 결과가 유효하면 길이/증가폭과 무관하게 허용 */ export function normalizeDigits(value) { - if (value == null) return ''; - if (isComposingDigits) return value; - - // 전각 숫자를 반각으로 변환 - const normalized = String(value).replace(/[0-9]/g, s => - String.fromCharCode(s.charCodeAt(0) - 0xFEE0) - ); - - // IME 조합 중인지 확인 - if (lastDigitsValue && normalized.startsWith(lastDigitsValue) && - normalized.length > lastDigitsValue.length + 1) { - isComposingDigits = true; - setTimeout(() => { isComposingDigits = false; }, 0); - return value; + if (value == null || value === '') { + lastValidDigits = ''; + return ''; } - // 숫자만 남기기 - const result = normalized.replace(/\D/g, ''); - lastDigitsValue = result; - return result; + const converted = String(value).replace(/[0-9]/g, s => + String.fromCharCode(s.charCodeAt(0) - 0xFEE0) + ); + const normalized = converted.replace(/\D/g, ''); + + if (normalized === '') { + lastValidDigits = ''; + return ''; + } + + if (/^\d+$/.test(normalized)) { + lastValidDigits = normalized; + return normalized; + } + + return lastValidDigits || ''; } /** * 소수점이 포함된 숫자 문자열로 정규화 - * - 전각 숫자와 소수점을 반각으로 변환 - * - 소수점은 한 개만 유지 - * - IME 입력 대응 + * - 전각 숫자/점 -> 반각 + * - 숫자/점 외 제거 + * - 점은 첫 번째만 허용 */ export function normalizeDecimal(value) { - if (value == null) return ''; - if (isComposingDecimal) return value; + if (value == null || value === '') { + lastValidDecimal = ''; + return ''; + } - // 전각 숫자와 소수점을 반각으로 변환 - const normalized = String(value).replace(/[0-9.]/g, s => + let converted = String(value).replace(/[0-9.]/g, s => s === '.' ? '.' : String.fromCharCode(s.charCodeAt(0) - 0xFEE0) ); + converted = converted.replace(/[^0-9.]/g, ''); - // IME 조합 중인지 확인 - if (lastDecimalValue && normalized.startsWith(lastDecimalValue) && - normalized.length > lastDecimalValue.length + 1) { - isComposingDecimal = true; - setTimeout(() => { isComposingDecimal = false; }, 0); - return value; - } + const firstDot = converted.indexOf('.'); + let normalized; - // 소수점 처리 - const parts = normalized.split('.'); - let result; - if (parts.length > 1) { - result = parts[0].replace(/\D/g, '') + '.' + parts[1].replace(/\D/g, ''); + if (firstDot !== -1) { + const integerPart = converted.slice(0, firstDot).replace(/\D/g, ''); + const fractionPart = converted.slice(firstDot + 1).replace(/\D/g, ''); + normalized = integerPart + '.' + fractionPart; } else { - result = normalized.replace(/\D/g, ''); + normalized = converted.replace(/\D/g, ''); } - lastDecimalValue = result; - return result; + if (normalized === '') { + lastValidDecimal = ''; + return ''; + } + + if (/^\d+(\.\d*)?$/.test(normalized) || /^\d+$/.test(normalized)) { + lastValidDecimal = normalized; + return normalized; + } + + return lastValidDecimal || ''; } + // 2-1) Limit fractional digits for decimal numbers. Default to 2 digits. export function normalizeDecimalLimit(value, maxFractionDigits = 2) { const s = normalizeDecimal(value)