Compare commits

..

No commits in common. "3ab6a1ffe1cc2a9bfaa362a7bf9f3c1bbece09fe" and "bbbc8cffaa84ea08d5f64f367ed84a19496feb14" have entirely different histories.

View File

@ -1,183 +1,122 @@
// 간단한 IME 감지 함수 // 숫자만 입력 가능한 input onChange 함수
function isIMEComposing(e) {
// compositionstart ~ compositionend 사이의 입력은 IME 조합 중
return e.nativeEvent?.isComposing || e.isComposing || false
}
// 숫자만 입력 가능한 input onChange 함수 (음수 포함)
export const onlyNumberInputChange = (e, callback) => { export const onlyNumberInputChange = (e, callback) => {
// IME 조합 중이면 그대로 전달
if (isIMEComposing(e)) {
callback(e.target.value, e)
return
}
let value = e.target.value let value = e.target.value
value = value.replace(/[^-0-9]/g, '') value = value.replace(/[^-0-9]/g, '')
// 음수 기호는 맨 앞에만 허용
if (value.indexOf('-') > 0) {
value = value.replace(/-/g, '')
}
// 연속된 음수 기호 제거
value = value.replace(/^-+/, '-')
callback(value, e) callback(value, e)
} }
// 소수점 둘째자리 숫자만 입력가능 (음수 포함, 개선된 로직) //소수점 둘째자리 숫자만 입력가능
export const onlyNumberWithDotInputChange = (e, callback) => { export const onlyNumberWithDotInputChange = (e, callback) => {
// IME 조합 중이면 그대로 전달
if (isIMEComposing(e)) {
callback(e.target.value, e)
return
}
const val = e.target.value const val = e.target.value
// 음수를 포함한 소수점 패턴 (최대 4자리 정수, 2자리 소수) const pattern = /^-?(\d{1,4}([.]\d{0,2})?)?$/
const pattern = /^-?(\d{0,4}([.]\d{0,2})?)?$/
if (!pattern.test(val)) { if (!pattern.test(val)) {
// 패턴에 맞지 않으면 마지막 입력 문자 제거 // prev에서 마지막 자리 제거
const correctedValue = val.slice(0, val.length - 1) callback(val.slice(0, val.length - 1), e)
callback(correctedValue, e)
return return
} }
// 음수 기호가 중간에 있으면 제거 callback(val, e)
let correctedValue = val
if (val.indexOf('-') > 0) {
correctedValue = val.replace(/-/g, '')
} }
callback(correctedValue, e)
}
// ============================= // =============================
// Number normalization utilities // Number normalization utilities
// ============================= // =============================
// 마지막으로 유효했던 값 추적
let lastValidDigits = '';
let lastValidDecimal = '';
// 1) Normalize any string to NFKC and keep only ASCII digits 0-9. /**
export function normalizeDigits(value, allowNegative = false) { * 숫자만 포함된 문자열로 정규화 (0-9)
// 1. 전각 숫자를 반각으로 변환 * - 전각 숫자 -> 반각
const halfWidth = fullToHalf(String(value ?? '')); * - 숫자 제거
// 2. NFKC 정규화 * - 결과가 유효하면 길이/증가폭과 무관하게 허용
const normalized = halfWidth.normalize('NFKC'); */
export function normalizeDigits(value) {
if (allowNegative) { if (value == null || value === '') {
// 음수 허용 시 lastValidDigits = '';
let result = normalized.replace(/[^-0-9]/g, ''); return '';
// 음수 기호는 맨 앞에만 허용
if (result.indexOf('-') > 0) {
result = result.replace(/-/g, '');
} }
// 연속된 음수 기호 제거
result = result.replace(/^-+/, '-'); const converted = String(value).replace(/[-]/g, s =>
return result; 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 || '';
}
/**
* 소수점이 포함된 숫자 문자열로 정규화
* - 전각 숫자/ -> 반각
* - 숫자/ 제거
* - 점은 번째만 허용
*/
export function normalizeDecimal(value) {
if (value == null || value === '') {
lastValidDecimal = '';
return '';
}
let converted = String(value).replace(/[-]/g, s =>
s === '' ? '.' : String.fromCharCode(s.charCodeAt(0) - 0xFEE0)
);
converted = converted.replace(/[^0-9.]/g, '');
const firstDot = converted.indexOf('.');
let normalized;
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 { } else {
// 양수만 허용 normalized = converted.replace(/\D/g, '');
return normalized.replace(/[^0-9]/g, '');
}
} }
export function normalizeDecimal(value, allowNegative = false) { if (normalized === '') {
// 1. 전각 숫자와 기호를 반각으로 변환 lastValidDecimal = '';
const halfWidth = fullToHalf(String(value ?? '')); return '';
// 2. NFKC 정규화
const normalized = halfWidth.normalize('NFKC');
let result;
if (allowNegative) {
// 음수와 소수점 허용
result = normalized.replace(/[^-0-9.]/g, '');
// 음수 기호는 맨 앞에만 허용
if (result.indexOf('-') > 0) {
result = result.replace(/-/g, '');
}
// 연속된 음수 기호 제거
result = result.replace(/^-+/, '-');
} else {
// 양수만 허용 (소수점 포함)
result = normalized.replace(/[^0-9.]/g, '');
} }
// 소수점은 하나만 허용 if (/^\d+(\.\d*)?$/.test(normalized) || /^\d+$/.test(normalized)) {
const [head, ...rest] = result.split('.'); lastValidDecimal = normalized;
return rest.length ? `${head}.${rest.join('').replace(/\./g, '')}` : head; return normalized;
} }
return lastValidDecimal || '';
}
// 2-1) Limit fractional digits for decimal numbers. Default to 2 digits. // 2-1) Limit fractional digits for decimal numbers. Default to 2 digits.
export function normalizeDecimalLimit(value, maxFractionDigits = 2, maxIntegerDigits = null, allowNegative = false) { export function normalizeDecimalLimit(value, maxFractionDigits = 2) {
const s = normalizeDecimal(value, allowNegative) const s = normalizeDecimal(value)
if (!s) return s if (!s) return s
const [intPart, fracPart] = s.split('.')
const isNegative = s.startsWith('-') if (fracPart === undefined) return intPart
const absoluteValue = isNegative ? s.slice(1) : s return `${intPart}.${fracPart.slice(0, Math.max(0, maxFractionDigits))}`
const [intPart, fracPart] = absoluteValue.split('.')
// 정수 부분 자릿수 제한
let limitedIntPart = intPart
if (maxIntegerDigits && intPart.length > maxIntegerDigits) {
limitedIntPart = intPart.slice(0, maxIntegerDigits)
} }
// 소수 부분 자릿수 제한 // 3) DOM input event helpers (optional): mutate target.value and return normalized value
let result = limitedIntPart export function sanitizeIntegerInputEvent(e) {
if (fracPart !== undefined) { const v = normalizeDigits(e?.target?.value)
const limitedFracPart = fracPart.slice(0, Math.max(0, maxFractionDigits)) if (e?.target) e.target.value = v
if (limitedFracPart.length > 0) {
result = `${limitedIntPart}.${limitedFracPart}`
}
}
return isNegative ? `-${result}` : result
}
// 3) DOM input event helpers: mutate target.value and return normalized value
export function sanitizeIntegerInputEvent(e, allowNegative = false) {
if (!e?.target) return ''
// IME 조합 중이면 원본 값 반환
if (isIMEComposing(e)) {
return e.target.value
}
const v = normalizeDigits(e.target.value, allowNegative)
e.target.value = v
return v return v
} }
export function sanitizeDecimalInputEvent(e, allowNegative = false) { export function sanitizeDecimalInputEvent(e) {
if (!e?.target) return '' const v = normalizeDecimal(e?.target?.value)
if (e?.target) e.target.value = v
// IME 조합 중이면 원본 값 반환
if (isIMEComposing(e)) {
return e.target.value
}
const v = normalizeDecimal(e.target.value, allowNegative)
e.target.value = v
return v return v
} }
export function sanitizeDecimalLimitInputEvent(e, maxFractionDigits = 2, maxIntegerDigits = null, allowNegative = false) {
if (!e?.target) return ''
// IME 조합 중이면 원본 값 반환
if (isIMEComposing(e)) {
return e.target.value
}
const v = normalizeDecimalLimit(e.target.value, maxFractionDigits, maxIntegerDigits, allowNegative)
e.target.value = v
return v
}
export function fullToHalf(str) {
if (!str) return '';
// Convert full-width numbers (-) to half-width (0-9)
return str.replace(/[-]/g, function(s) {
return String.fromCharCode(s.charCodeAt(0) - 0xFEE0);
});
}