Merge pull request '[1252] 일본의 전각 숫자, 반각 함수변경' (#305) from dev into prd-deploy

Reviewed-on: #305
This commit is contained in:
ysCha 2025-08-22 18:37:31 +09:00
commit 3ab6a1ffe1

View File

@ -1,122 +1,183 @@
// 숫자만 입력 가능한 input onChange 함수
export const onlyNumberInputChange = (e, callback) => {
let value = e.target.value
value = value.replace(/[^-0-9]/g, '')
callback(value, e)
// 간단한 IME 감지 함수
function isIMEComposing(e) {
// compositionstart ~ compositionend 사이의 입력은 IME 조합 중
return e.nativeEvent?.isComposing || e.isComposing || false
}
//소수점 둘째자리 숫자만 입력가능
export const onlyNumberWithDotInputChange = (e, callback) => {
const val = e.target.value
const pattern = /^-?(\d{1,4}([.]\d{0,2})?)?$/
if (!pattern.test(val)) {
// prev에서 마지막 자리 제거
callback(val.slice(0, val.length - 1), e)
// 숫자만 입력 가능한 input onChange 함수 (음수 포함)
export const onlyNumberInputChange = (e, callback) => {
// IME 조합 중이면 그대로 전달
if (isIMEComposing(e)) {
callback(e.target.value, e)
return
}
callback(val, e)
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
// =============================
// 마지막으로 유효했던 값 추적
let lastValidDigits = '';
let lastValidDecimal = '';
/**
* 숫자만 포함된 문자열로 정규화 (0-9)
* - 전각 숫자 -> 반각
* - 숫자 제거
* - 결과가 유효하면 길이/증가폭과 무관하게 허용
*/
export function normalizeDigits(value) {
if (value == null || value === '') {
lastValidDigits = '';
return '';
}
// 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');
const converted = String(value).replace(/[-]/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 || '';
}
/**
* 소수점이 포함된 숫자 문자열로 정규화
* - 전각 숫자/ -> 반각
* - 숫자/ 제거
* - 점은 번째만 허용
*/
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;
if (allowNegative) {
// 음수 허용 시
let result = normalized.replace(/[^-0-9]/g, '');
// 음수 기호는 맨 앞에만 허용
if (result.indexOf('-') > 0) {
result = result.replace(/-/g, '');
}
// 연속된 음수 기호 제거
result = result.replace(/^-+/, '-');
return result;
} else {
normalized = converted.replace(/\D/g, '');
// 양수만 허용
return normalized.replace(/[^0-9]/g, '');
}
if (normalized === '') {
lastValidDecimal = '';
return '';
}
if (/^\d+(\.\d*)?$/.test(normalized) || /^\d+$/.test(normalized)) {
lastValidDecimal = normalized;
return normalized;
}
return lastValidDecimal || '';
}
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) {
const s = normalizeDecimal(value)
export function normalizeDecimalLimit(value, maxFractionDigits = 2, maxIntegerDigits = null, allowNegative = false) {
const s = normalizeDecimal(value, allowNegative)
if (!s) return s
const [intPart, fracPart] = s.split('.')
if (fracPart === undefined) return intPart
return `${intPart}.${fracPart.slice(0, Math.max(0, maxFractionDigits))}`
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 (optional): mutate target.value and return normalized value
export function sanitizeIntegerInputEvent(e) {
const v = normalizeDigits(e?.target?.value)
if (e?.target) e.target.value = v
// 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) {
const v = normalizeDecimal(e?.target?.value)
if (e?.target) e.target.value = 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 (-) to half-width (0-9)
return str.replace(/[-]/g, function(s) {
return String.fromCharCode(s.charCodeAt(0) - 0xFEE0);
});
}