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