dev #337
@ -2,433 +2,439 @@ import React, { useState, useRef, useEffect, forwardRef } from 'react'
|
|||||||
import { createCalculator } from '@/util/calc-utils'
|
import { createCalculator } from '@/util/calc-utils'
|
||||||
import '@/styles/calc.scss'
|
import '@/styles/calc.scss'
|
||||||
|
|
||||||
export const CalculatorInput = forwardRef(({ value, onChange, label, options = {}, id, className = 'calculator-input', readOnly = false, placeholder}, ref) => {
|
export const CalculatorInput = forwardRef(
|
||||||
const [showKeypad, setShowKeypad] = useState(false)
|
({ value, onChange, label, options = {}, id, className = 'calculator-input', readOnly = false, placeholder }, ref) => {
|
||||||
const [displayValue, setDisplayValue] = useState(value || '0')
|
const [showKeypad, setShowKeypad] = useState(false)
|
||||||
const [hasOperation, setHasOperation] = useState(false)
|
const [displayValue, setDisplayValue] = useState(value || '0')
|
||||||
const calculatorRef = useRef(createCalculator(options))
|
const [hasOperation, setHasOperation] = useState(false)
|
||||||
const containerRef = useRef(null)
|
const calculatorRef = useRef(createCalculator(options))
|
||||||
const inputRef = useRef(null)
|
const containerRef = useRef(null)
|
||||||
|
const inputRef = useRef(null)
|
||||||
|
|
||||||
// 외부 ref와 내부 ref를 동기화
|
// 외부 ref와 내부 ref를 동기화
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ref) {
|
if (ref) {
|
||||||
if (typeof ref === 'function') {
|
if (typeof ref === 'function') {
|
||||||
ref(inputRef.current)
|
ref(inputRef.current)
|
||||||
} else {
|
} else {
|
||||||
ref.current = inputRef.current
|
ref.current = inputRef.current
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [ref])
|
|
||||||
|
|
||||||
// Sync displayValue with value prop
|
|
||||||
useEffect(() => {
|
|
||||||
setDisplayValue(value || '0')
|
|
||||||
}, [value])
|
|
||||||
|
|
||||||
// 클릭 외부 감지
|
|
||||||
useEffect(() => {
|
|
||||||
const handleClickOutside = (event) => {
|
|
||||||
if (containerRef.current && !containerRef.current.contains(event.target)) {
|
|
||||||
setShowKeypad(false)
|
|
||||||
if (hasOperation) {
|
|
||||||
// If there's an operation in progress, compute the result when losing focus
|
|
||||||
handleCompute()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}, [ref])
|
||||||
|
|
||||||
document.addEventListener('mousedown', handleClickOutside)
|
// Sync displayValue with value prop
|
||||||
return () => document.removeEventListener('mousedown', handleClickOutside)
|
useEffect(() => {
|
||||||
}, [value, onChange, hasOperation])
|
setDisplayValue(value || '0')
|
||||||
|
}, [value])
|
||||||
|
|
||||||
// 숫자 입력 처리 함수 수정
|
// 클릭 외부 감지
|
||||||
const handleNumber = (num) => {
|
useEffect(() => {
|
||||||
const calculator = calculatorRef.current
|
const handleClickOutside = (event) => {
|
||||||
let newDisplayValue = ''
|
if (containerRef.current && !containerRef.current.contains(event.target)) {
|
||||||
|
setShowKeypad(false)
|
||||||
if (hasOperation) {
|
if (hasOperation) {
|
||||||
// 연산자 이후 숫자 입력 시
|
// If there's an operation in progress, compute the result when losing focus
|
||||||
if (calculator.currentOperand === '0' || calculator.shouldResetDisplay) {
|
handleCompute()
|
||||||
calculator.currentOperand = num.toString()
|
}
|
||||||
calculator.shouldResetDisplay = false
|
}
|
||||||
} else {
|
|
||||||
calculator.currentOperand = (calculator.currentOperand || '') + num
|
|
||||||
}
|
}
|
||||||
newDisplayValue = calculator.previousOperand + calculator.operation + calculator.currentOperand
|
|
||||||
setDisplayValue(newDisplayValue)
|
document.addEventListener('mousedown', handleClickOutside)
|
||||||
} else {
|
return () => document.removeEventListener('mousedown', handleClickOutside)
|
||||||
// 첫 번째 숫자 입력 시
|
}, [value, onChange, hasOperation])
|
||||||
if (displayValue === '0' || calculator.shouldResetDisplay) {
|
|
||||||
calculator.currentOperand = num.toString()
|
// 숫자 입력 처리 함수 수정
|
||||||
calculator.shouldResetDisplay = false
|
const handleNumber = (num) => {
|
||||||
newDisplayValue = calculator.currentOperand
|
const calculator = calculatorRef.current
|
||||||
|
let newDisplayValue = ''
|
||||||
|
|
||||||
|
if (hasOperation) {
|
||||||
|
// 연산자 이후 숫자 입력 시
|
||||||
|
if (calculator.currentOperand === '0' || calculator.shouldResetDisplay) {
|
||||||
|
calculator.currentOperand = num.toString()
|
||||||
|
calculator.shouldResetDisplay = false
|
||||||
|
} else {
|
||||||
|
calculator.currentOperand = (calculator.currentOperand || '') + num
|
||||||
|
}
|
||||||
|
newDisplayValue = calculator.previousOperand + calculator.operation + calculator.currentOperand
|
||||||
setDisplayValue(newDisplayValue)
|
setDisplayValue(newDisplayValue)
|
||||||
if (!hasOperation) {
|
|
||||||
onChange(calculator.currentOperand)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
calculator.currentOperand = (calculator.currentOperand || '') + num
|
// 첫 번째 숫자 입력 시
|
||||||
newDisplayValue = calculator.currentOperand
|
if (displayValue === '0' || calculator.shouldResetDisplay) {
|
||||||
setDisplayValue(newDisplayValue)
|
calculator.currentOperand = num.toString()
|
||||||
if (!hasOperation) {
|
calculator.shouldResetDisplay = false
|
||||||
onChange(newDisplayValue)
|
newDisplayValue = calculator.currentOperand
|
||||||
|
setDisplayValue(newDisplayValue)
|
||||||
|
if (!hasOperation) {
|
||||||
|
onChange(calculator.currentOperand)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
calculator.currentOperand = (calculator.currentOperand || '') + num
|
||||||
|
newDisplayValue = calculator.currentOperand
|
||||||
|
setDisplayValue(newDisplayValue)
|
||||||
|
if (!hasOperation) {
|
||||||
|
onChange(newDisplayValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 커서를 텍스트 끝으로 이동하고 스크롤 처리
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (inputRef.current) {
|
||||||
|
inputRef.current.focus()
|
||||||
|
const len = newDisplayValue.length
|
||||||
|
inputRef.current.setSelectionRange(len, len)
|
||||||
|
// 텍스트 끝으로 스크롤
|
||||||
|
inputRef.current.scrollLeft = inputRef.current.scrollWidth
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 포커스와 커서 위치 설정 (새로운 값의 길이로 설정)
|
// 연산자 처리 함수 수정
|
||||||
requestAnimationFrame(() => {
|
const handleOperation = (operation) => {
|
||||||
if (inputRef.current) {
|
const calculator = calculatorRef.current
|
||||||
inputRef.current.focus()
|
let newDisplayValue = ''
|
||||||
const len = newDisplayValue.length
|
|
||||||
inputRef.current.setSelectionRange(len, len)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 연산자 처리 함수 수정
|
// 현재 입력된 값이 없으면 이전 값 사용 (연속 연산 시)
|
||||||
const handleOperation = (operation) => {
|
if (!calculator.currentOperand && calculator.previousOperand) {
|
||||||
const calculator = calculatorRef.current
|
calculator.operation = operation
|
||||||
let newDisplayValue = ''
|
newDisplayValue = calculator.previousOperand + operation
|
||||||
|
setDisplayValue(newDisplayValue)
|
||||||
// 현재 입력된 값이 없으면 이전 값 사용 (연속 연산 시)
|
setHasOperation(true)
|
||||||
if (!calculator.currentOperand && calculator.previousOperand) {
|
} else if (hasOperation) {
|
||||||
calculator.operation = operation
|
// 이미 연산자가 있는 경우, 계산 실행
|
||||||
newDisplayValue = calculator.previousOperand + operation
|
const result = calculator.compute()
|
||||||
setDisplayValue(newDisplayValue)
|
if (result !== undefined) {
|
||||||
setHasOperation(true)
|
calculator.previousOperand = result.toString()
|
||||||
} else if (hasOperation) {
|
calculator.operation = operation
|
||||||
// 이미 연산자가 있는 경우, 계산 실행
|
calculator.currentOperand = ''
|
||||||
const result = calculator.compute()
|
newDisplayValue = calculator.previousOperand + operation
|
||||||
if (result !== undefined) {
|
setDisplayValue(newDisplayValue)
|
||||||
calculator.previousOperand = result.toString()
|
}
|
||||||
|
} else {
|
||||||
|
// 첫 번째 연산자 입력 시
|
||||||
|
calculator.previousOperand = calculator.currentOperand || '0'
|
||||||
calculator.operation = operation
|
calculator.operation = operation
|
||||||
calculator.currentOperand = ''
|
calculator.currentOperand = ''
|
||||||
|
setHasOperation(true)
|
||||||
newDisplayValue = calculator.previousOperand + operation
|
newDisplayValue = calculator.previousOperand + operation
|
||||||
setDisplayValue(newDisplayValue)
|
setDisplayValue(newDisplayValue)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// 첫 번째 연산자 입력 시
|
// 커서를 텍스트 끝으로 이동하고 스크롤 처리
|
||||||
calculator.previousOperand = calculator.currentOperand || '0'
|
requestAnimationFrame(() => {
|
||||||
calculator.operation = operation
|
if (inputRef.current) {
|
||||||
calculator.currentOperand = ''
|
inputRef.current.focus()
|
||||||
setHasOperation(true)
|
const len = newDisplayValue.length
|
||||||
newDisplayValue = calculator.previousOperand + operation
|
inputRef.current.setSelectionRange(len, len)
|
||||||
setDisplayValue(newDisplayValue)
|
// 텍스트 끝으로 스크롤
|
||||||
|
inputRef.current.scrollLeft = inputRef.current.scrollWidth
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 포커스와 커서 위치 설정 (새로운 값의 길이로 설정)
|
// AC 버튼 클릭 핸들러
|
||||||
requestAnimationFrame(() => {
|
const handleClear = () => {
|
||||||
if (inputRef.current) {
|
|
||||||
inputRef.current.focus()
|
|
||||||
const len = newDisplayValue.length
|
|
||||||
inputRef.current.setSelectionRange(len, len)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// AC 버튼 클릭 핸들러
|
|
||||||
const handleClear = () => {
|
|
||||||
const calculator = calculatorRef.current
|
|
||||||
const newValue = calculator.clear()
|
|
||||||
const displayValue = newValue || '0'
|
|
||||||
setDisplayValue(displayValue)
|
|
||||||
setHasOperation(false)
|
|
||||||
onChange(displayValue)
|
|
||||||
// 포커스와 커서 위치 설정
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
if (inputRef.current) {
|
|
||||||
inputRef.current.focus()
|
|
||||||
const len = displayValue.length
|
|
||||||
inputRef.current.setSelectionRange(len, len)
|
|
||||||
// Ensure focus is maintained
|
|
||||||
inputRef.current.focus()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 계산 실행 함수 수정
|
|
||||||
const handleCompute = (fromEnterKey = false) => {
|
|
||||||
const calculator = calculatorRef.current
|
|
||||||
if (!hasOperation || !calculator.currentOperand) return
|
|
||||||
|
|
||||||
const result = calculator.compute()
|
|
||||||
if (result !== undefined) {
|
|
||||||
const resultStr = result.toString()
|
|
||||||
setDisplayValue(resultStr)
|
|
||||||
setHasOperation(false)
|
|
||||||
// Only call onChange with the final result
|
|
||||||
onChange(resultStr)
|
|
||||||
|
|
||||||
// 엔터키로 호출된 경우 포커스 설정하지 않음
|
|
||||||
if (!fromEnterKey) {
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
if (inputRef.current) {
|
|
||||||
inputRef.current.focus()
|
|
||||||
const len = resultStr.length
|
|
||||||
inputRef.current.setSelectionRange(len, len)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DEL 버튼 클릭 핸들러
|
|
||||||
const handleDelete = () => {
|
|
||||||
const calculator = calculatorRef.current
|
|
||||||
const newValue = calculator.deleteNumber()
|
|
||||||
const displayValue = newValue || '0'
|
|
||||||
setDisplayValue(displayValue)
|
|
||||||
setHasOperation(!!calculator.operation)
|
|
||||||
onChange(displayValue)
|
|
||||||
// 포커스와 커서 위치 설정
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
if (inputRef.current) {
|
|
||||||
inputRef.current.focus()
|
|
||||||
const len = displayValue.length
|
|
||||||
inputRef.current.setSelectionRange(len, len)
|
|
||||||
// Ensure focus is maintained
|
|
||||||
inputRef.current.focus()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// input의 onChange 이벤트 처리 - 허용된 계산기 입력만 처리
|
|
||||||
const handleInputChange = (e) => {
|
|
||||||
if (readOnly) return
|
|
||||||
|
|
||||||
const inputValue = e.target.value
|
|
||||||
|
|
||||||
// 허용된 문자만 필터링 (숫자, 연산자, 소수점)
|
|
||||||
const filteredValue = inputValue.replace(/[^0-9+\-×÷.]/g, '')
|
|
||||||
|
|
||||||
// 연산자 연속 입력 방지
|
|
||||||
const lastChar = filteredValue[filteredValue.length - 1]
|
|
||||||
const prevChar = filteredValue[filteredValue.length - 2]
|
|
||||||
|
|
||||||
if (['+', '×', '÷'].includes(lastChar) && ['+', '-', '×', '÷', '.'].includes(prevChar)) {
|
|
||||||
// 연산자나 소수점이 연속으로 입력된 경우 이전 문자 유지
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 소수점 중복 입력 방지
|
|
||||||
const parts = filteredValue.split(/[+\-×÷]/)
|
|
||||||
if (parts[parts.length - 1].split('.').length > 2) {
|
|
||||||
// 한 숫자에 소수점이 2개 이상인 경우
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setDisplayValue(filteredValue)
|
|
||||||
|
|
||||||
// 계산기 상태 업데이트
|
|
||||||
if (filteredValue !== displayValue) {
|
|
||||||
const calculator = calculatorRef.current
|
const calculator = calculatorRef.current
|
||||||
const hasOperation = /[+\-×÷]/.test(filteredValue)
|
const newValue = calculator.clear()
|
||||||
|
const displayValue = newValue || '0'
|
||||||
|
setDisplayValue(displayValue)
|
||||||
|
setHasOperation(false)
|
||||||
|
onChange(displayValue)
|
||||||
|
// 커서를 텍스트 끝으로 이동하고 스크롤 처리
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (inputRef.current) {
|
||||||
|
inputRef.current.focus()
|
||||||
|
const len = displayValue.length
|
||||||
|
inputRef.current.setSelectionRange(len, len)
|
||||||
|
// 텍스트 끝으로 스크롤
|
||||||
|
inputRef.current.scrollLeft = inputRef.current.scrollWidth
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (hasOperation) {
|
// 계산 실행 함수 수정
|
||||||
const [operand1, operator, operand2] = filteredValue.split(/([+\-×÷])/)
|
const handleCompute = (fromEnterKey = false) => {
|
||||||
calculator.previousOperand = operand1 || ''
|
const calculator = calculatorRef.current
|
||||||
calculator.operation = operator || ''
|
if (!hasOperation || !calculator.currentOperand) return
|
||||||
calculator.currentOperand = operand2 || ''
|
|
||||||
setHasOperation(true)
|
const result = calculator.compute()
|
||||||
} else {
|
if (result !== undefined) {
|
||||||
calculator.currentOperand = filteredValue
|
const resultStr = result.toString()
|
||||||
|
setDisplayValue(resultStr)
|
||||||
setHasOperation(false)
|
setHasOperation(false)
|
||||||
|
// Only call onChange with the final result
|
||||||
|
onChange(resultStr)
|
||||||
|
|
||||||
|
// 엔터키로 호출된 경우 포커스 설정하지 않음
|
||||||
|
if (!fromEnterKey) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (inputRef.current) {
|
||||||
|
inputRef.current.focus()
|
||||||
|
const len = resultStr.length
|
||||||
|
inputRef.current.setSelectionRange(len, len)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEL 버튼 클릭 핸들러
|
||||||
|
const handleDelete = () => {
|
||||||
|
const calculator = calculatorRef.current
|
||||||
|
const newValue = calculator.deleteNumber()
|
||||||
|
const displayValue = newValue || '0'
|
||||||
|
setDisplayValue(displayValue)
|
||||||
|
setHasOperation(!!calculator.operation)
|
||||||
|
onChange(displayValue)
|
||||||
|
// 커서를 텍스트 끝으로 이동하고 스크롤 처리
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (inputRef.current) {
|
||||||
|
inputRef.current.focus()
|
||||||
|
const len = displayValue.length
|
||||||
|
inputRef.current.setSelectionRange(len, len)
|
||||||
|
// 텍스트 끝으로 스크롤
|
||||||
|
inputRef.current.scrollLeft = inputRef.current.scrollWidth
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// input의 onChange 이벤트 처리 - 허용된 계산기 입력만 처리
|
||||||
|
const handleInputChange = (e) => {
|
||||||
|
if (readOnly) return
|
||||||
|
|
||||||
|
const inputValue = e.target.value
|
||||||
|
|
||||||
|
// 허용된 문자만 필터링 (숫자, 연산자, 소수점)
|
||||||
|
const filteredValue = inputValue.replace(/[^0-9+\-×÷.]/g, '')
|
||||||
|
|
||||||
|
// 연산자 연속 입력 방지
|
||||||
|
const lastChar = filteredValue[filteredValue.length - 1]
|
||||||
|
const prevChar = filteredValue[filteredValue.length - 2]
|
||||||
|
|
||||||
|
if (['+', '×', '÷'].includes(lastChar) && ['+', '-', '×', '÷', '.'].includes(prevChar)) {
|
||||||
|
// 연산자나 소수점이 연속으로 입력된 경우 이전 문자 유지
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange(filteredValue)
|
// 소수점 중복 입력 방지
|
||||||
}
|
const parts = filteredValue.split(/[+\-×÷]/)
|
||||||
}
|
if (parts[parts.length - 1].split('.').length > 2) {
|
||||||
|
// 한 숫자에 소수점이 2개 이상인 경우
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setDisplayValue(filteredValue)
|
||||||
|
|
||||||
|
// 계산기 상태 업데이트
|
||||||
|
if (filteredValue !== displayValue) {
|
||||||
|
const calculator = calculatorRef.current
|
||||||
|
const hasOperation = /[+\-×÷]/.test(filteredValue)
|
||||||
|
|
||||||
|
if (hasOperation) {
|
||||||
|
const [operand1, operator, operand2] = filteredValue.split(/([+\-×÷])/)
|
||||||
|
calculator.previousOperand = operand1 || ''
|
||||||
|
calculator.operation = operator || ''
|
||||||
|
calculator.currentOperand = operand2 || ''
|
||||||
|
setHasOperation(true)
|
||||||
|
} else {
|
||||||
|
calculator.currentOperand = filteredValue
|
||||||
|
setHasOperation(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange(filteredValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 키패드 토글 함수
|
||||||
|
const toggleKeypad = (e) => {
|
||||||
|
if (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
}
|
||||||
|
const newShowKeypad = !showKeypad
|
||||||
|
setShowKeypad(newShowKeypad)
|
||||||
|
|
||||||
|
// Show keypad 시에만 포커스 유지
|
||||||
|
if (newShowKeypad) {
|
||||||
|
setTimeout(() => {
|
||||||
|
inputRef.current?.focus()
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 키보드 이벤트 처리 수정
|
||||||
|
const handleKeyDown = (e) => {
|
||||||
|
if (readOnly) return
|
||||||
|
|
||||||
|
// Tab 키는 계산기 숨기고 기본 동작 허용
|
||||||
|
if (e.key === 'Tab') {
|
||||||
|
setShowKeypad(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 모든 방향키는 기본 동작 허용
|
||||||
|
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight' || e.key === 'ArrowUp' || e.key === 'ArrowDown') {
|
||||||
|
setShowKeypad(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 계산기 허용 키들이 입력되면 키패드 표시 (엔터키 제외)
|
||||||
|
if (/^[0-9+\-×÷.=]$/.test(e.key) || e.key === 'Backspace' || e.key === 'Delete' || e.key === '*' || e.key === '/') {
|
||||||
|
setShowKeypad(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 키패드가 숨겨진 상태에서 엔터키는 페이지로 전달
|
||||||
|
if (!showKeypad && e.key === 'Enter') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 키패드 토글 함수
|
|
||||||
const toggleKeypad = (e) => {
|
|
||||||
if (e) {
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
const calculator = calculatorRef.current
|
||||||
}
|
const { allowDecimal } = options
|
||||||
const newShowKeypad = !showKeypad
|
|
||||||
setShowKeypad(newShowKeypad)
|
|
||||||
|
|
||||||
// Show keypad 시에만 포커스 유지
|
if (e.key === '.') {
|
||||||
if (newShowKeypad) {
|
// allowDecimal이 false이면 소수점 입력 무시
|
||||||
setTimeout(() => {
|
if (!allowDecimal) return
|
||||||
inputRef.current?.focus()
|
|
||||||
}, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 키보드 이벤트 처리 수정
|
// 소수점 입력 처리
|
||||||
const handleKeyDown = (e) => {
|
const currentValue = displayValue.toString()
|
||||||
if (readOnly) return
|
const parts = currentValue.split(/[+\-×÷]/)
|
||||||
|
const lastPart = parts[parts.length - 1]
|
||||||
|
|
||||||
// Tab 키는 계산기 숨기고 기본 동작 허용
|
// 이미 소수점이 있으면 무시
|
||||||
if (e.key === 'Tab') {
|
if (!lastPart.includes('.')) {
|
||||||
setShowKeypad(false)
|
handleNumber(e.key)
|
||||||
return
|
}
|
||||||
}
|
} else if (/^[0-9]$/.test(e.key)) {
|
||||||
|
|
||||||
// 모든 방향키는 기본 동작 허용
|
|
||||||
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight' || e.key === 'ArrowUp' || e.key === 'ArrowDown') {
|
|
||||||
setShowKeypad(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 계산기 허용 키들이 입력되면 키패드 표시 (엔터키 제외)
|
|
||||||
if (/^[0-9+\-×÷.=]$/.test(e.key) || e.key === 'Backspace' || e.key === 'Delete' || e.key === '*' || e.key === '/') {
|
|
||||||
setShowKeypad(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 키패드가 숨겨진 상태에서 엔터키는 페이지로 전달
|
|
||||||
if (!showKeypad && e.key === 'Enter') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
e.preventDefault()
|
|
||||||
const calculator = calculatorRef.current
|
|
||||||
const { allowDecimal } = options
|
|
||||||
|
|
||||||
if (e.key === '.') {
|
|
||||||
// allowDecimal이 false이면 소수점 입력 무시
|
|
||||||
if (!allowDecimal) return
|
|
||||||
|
|
||||||
// 소수점 입력 처리
|
|
||||||
const currentValue = displayValue.toString()
|
|
||||||
const parts = currentValue.split(/[+\-×÷]/)
|
|
||||||
const lastPart = parts[parts.length - 1]
|
|
||||||
|
|
||||||
// 이미 소수점이 있으면 무시
|
|
||||||
if (!lastPart.includes('.')) {
|
|
||||||
handleNumber(e.key)
|
handleNumber(e.key)
|
||||||
}
|
} else {
|
||||||
} else if (/^[0-9]$/.test(e.key)) {
|
switch (e.key) {
|
||||||
handleNumber(e.key)
|
case '+':
|
||||||
} else {
|
case '-':
|
||||||
switch (e.key) {
|
case '*':
|
||||||
case '+':
|
case '/':
|
||||||
case '-':
|
const opMap = { '*': '×', '/': '÷' }
|
||||||
case '*':
|
handleOperation(opMap[e.key] || e.key)
|
||||||
case '/':
|
break
|
||||||
const opMap = { '*': '×', '/': '÷' }
|
case 'Enter':
|
||||||
handleOperation(opMap[e.key] || e.key)
|
case '=':
|
||||||
break
|
if (showKeypad) {
|
||||||
case 'Enter':
|
// 키패드가 보이는 상태에서 엔터키: 계산 후 키패드만 숨김
|
||||||
case '=':
|
handleCompute(true) // 엔터키로 호출됨을 표시
|
||||||
if (showKeypad) {
|
setShowKeypad(false)
|
||||||
// 키패드가 보이는 상태에서 엔터키: 계산 후 키패드만 숨김
|
}
|
||||||
handleCompute(true) // 엔터키로 호출됨을 표시
|
// 키패드가 숨겨진 상태에서 엔터키: 페이지로 전달 (preventDefault 하지 않음)
|
||||||
|
break
|
||||||
|
case 'Backspace':
|
||||||
|
case 'Delete':
|
||||||
|
handleDelete()
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'Escape':
|
||||||
|
handleClear()
|
||||||
setShowKeypad(false)
|
setShowKeypad(false)
|
||||||
}
|
break
|
||||||
// 키패드가 숨겨진 상태에서 엔터키: 페이지로 전달 (preventDefault 하지 않음)
|
|
||||||
break
|
|
||||||
case 'Backspace':
|
|
||||||
case 'Delete':
|
|
||||||
handleDelete()
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'Escape':
|
default:
|
||||||
handleClear()
|
break
|
||||||
setShowKeypad(false)
|
}
|
||||||
break
|
|
||||||
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} className="calculator-input-wrapper">
|
<div ref={containerRef} className="calculator-input-wrapper">
|
||||||
{label && (
|
{label && (
|
||||||
<label htmlFor={id} className="calculator-label">
|
<label htmlFor={id} className="calculator-label">
|
||||||
{label}
|
{label}
|
||||||
</label>
|
</label>
|
||||||
)}
|
)}
|
||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
type="text"
|
type="text"
|
||||||
id={id}
|
id={id}
|
||||||
value={displayValue}
|
value={displayValue}
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
className={className}
|
className={className}
|
||||||
onClick={() => !readOnly && setShowKeypad(true)}
|
onClick={() => !readOnly && setShowKeypad(true)}
|
||||||
onFocus={() => !readOnly && setShowKeypad(true)}
|
onFocus={() => !readOnly && setShowKeypad(true)}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
tabIndex={readOnly ? -1 : 0}
|
tabIndex={readOnly ? -1 : 0}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
autoComplete={'off'}
|
autoComplete={'off'}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{showKeypad && !readOnly && (
|
{showKeypad && !readOnly && (
|
||||||
<div className="keypad-container">
|
<div className="keypad-container">
|
||||||
<div className="keypad-grid">
|
<div className="keypad-grid">
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// const newValue = calculatorRef.current.clear()
|
// const newValue = calculatorRef.current.clear()
|
||||||
// setDisplayValue(newValue || '0')
|
// setDisplayValue(newValue || '0')
|
||||||
// onChange(newValue || '0')
|
// onChange(newValue || '0')
|
||||||
handleClear()
|
handleClear()
|
||||||
setHasOperation(false)
|
setHasOperation(false)
|
||||||
}}
|
}}
|
||||||
className="btn-clear"
|
className="btn-clear"
|
||||||
>
|
>
|
||||||
AC
|
AC
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
// const newValue = calculatorRef.current.deleteNumber()
|
|
||||||
// setDisplayValue(newValue || '0')
|
|
||||||
// onChange(newValue || '0')
|
|
||||||
//setHasOperation(!!calculatorRef.current.operation)
|
|
||||||
handleDelete()
|
|
||||||
}}
|
|
||||||
className="btn-delete"
|
|
||||||
>
|
|
||||||
DEL
|
|
||||||
</button>
|
|
||||||
<button onClick={() => handleOperation('÷')} className="btn-operator">
|
|
||||||
÷
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button onClick={() => handleOperation('×')} className="btn-operator">
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
<button onClick={() => handleOperation('-')} className="btn-operator">
|
|
||||||
-
|
|
||||||
</button>
|
|
||||||
<button onClick={() => handleOperation('+')} className="btn-operator">
|
|
||||||
+
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* 숫자 버튼 */}
|
|
||||||
{[1,2,3,4,5,6,7,8,9].map((num) => (
|
|
||||||
<button key={num} onClick={() => handleNumber(num)} className="btn-number">
|
|
||||||
{num}
|
|
||||||
</button>
|
</button>
|
||||||
))}
|
<button
|
||||||
{/* 0 버튼 */}
|
onClick={() => {
|
||||||
<button onClick={() => handleNumber('0')} className="btn-number btn-zero">
|
// const newValue = calculatorRef.current.deleteNumber()
|
||||||
0
|
// setDisplayValue(newValue || '0')
|
||||||
</button>
|
// onChange(newValue || '0')
|
||||||
<button
|
//setHasOperation(!!calculatorRef.current.operation)
|
||||||
onClick={() => {
|
handleDelete()
|
||||||
const newValue = calculatorRef.current.appendNumber('.')
|
}}
|
||||||
onChange(newValue)
|
className="btn-delete"
|
||||||
}}
|
>
|
||||||
className="btn-number"
|
DEL
|
||||||
>
|
</button>
|
||||||
.
|
<button onClick={() => handleOperation('÷')} className="btn-operator">
|
||||||
</button>
|
÷
|
||||||
{/* = 버튼 */}
|
</button>
|
||||||
<button onClick={() => handleCompute(false)} className="btn-equals">
|
|
||||||
=
|
<button onClick={() => handleOperation('×')} className="btn-operator">
|
||||||
</button>
|
×
|
||||||
|
</button>
|
||||||
|
<button onClick={() => handleOperation('-')} className="btn-operator">
|
||||||
|
-
|
||||||
|
</button>
|
||||||
|
<button onClick={() => handleOperation('+')} className="btn-operator">
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* 숫자 버튼 */}
|
||||||
|
{[1, 2, 3, 4, 5, 6, 7, 8, 9].map((num) => (
|
||||||
|
<button key={num} onClick={() => handleNumber(num)} className="btn-number">
|
||||||
|
{num}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
{/* 0 버튼 */}
|
||||||
|
<button onClick={() => handleNumber('0')} className="btn-number btn-zero">
|
||||||
|
0
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
const newValue = calculatorRef.current.appendNumber('.')
|
||||||
|
onChange(newValue)
|
||||||
|
}}
|
||||||
|
className="btn-number"
|
||||||
|
>
|
||||||
|
.
|
||||||
|
</button>
|
||||||
|
{/* = 버튼 */}
|
||||||
|
<button onClick={() => handleCompute(false)} className="btn-equals">
|
||||||
|
=
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
)
|
||||||
)
|
},
|
||||||
})
|
)
|
||||||
|
|
||||||
CalculatorInput.displayName = 'CalculatorInput'
|
CalculatorInput.displayName = 'CalculatorInput'
|
||||||
|
|||||||
@ -213,7 +213,7 @@ export function useEvent() {
|
|||||||
const modulePoints = []
|
const modulePoints = []
|
||||||
const modules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE)
|
const modules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE)
|
||||||
modules.forEach((module) => {
|
modules.forEach((module) => {
|
||||||
module.points.forEach((point) => {
|
module.getCurrentPoints().forEach((point) => {
|
||||||
modulePoints.push({ x: point.x, y: point.y })
|
modulePoints.push({ x: point.x, y: point.y })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
// Variables
|
// Variables
|
||||||
$colors: (
|
$colors: (
|
||||||
'dark-700': #1f2937,
|
'dark-700': #1f2937,
|
||||||
'dark-600': #334155,
|
'dark-600': #334155,
|
||||||
'dark-500': #4b5563,
|
'dark-500': #4b5563,
|
||||||
'dark-400': #6b7280,
|
'dark-400': #6b7280,
|
||||||
'dark-300': #374151,
|
'dark-300': #374151,
|
||||||
'primary': #10b981,
|
'primary': #10b981,
|
||||||
'primary-dark': #059669,
|
'primary-dark': #059669,
|
||||||
'danger': #ef4444,
|
'danger': #ef4444,
|
||||||
'danger-dark': #dc2626,
|
'danger-dark': #dc2626,
|
||||||
'warning': #f59e0b,
|
'warning': #f59e0b,
|
||||||
'warning-dark': #d97706,
|
'warning-dark': #d97706,
|
||||||
'border': #475569
|
'border': #475569,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Mixins
|
// Mixins
|
||||||
@ -46,10 +46,15 @@ $colors: (
|
|||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
text-align: right;
|
text-align: left; // 왼쪽 정렬
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.625rem;
|
font-size: 0.625rem;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
direction: ltr; // 왼쪽에서 오른쪽으로 입력
|
||||||
|
white-space: nowrap; // 줄바꿈 방지
|
||||||
|
// input 요소에서는 overflow 대신 다른 방법 사용
|
||||||
|
text-overflow: clip; // 텍스트 잘림 처리
|
||||||
|
unicode-bidi: bidi-override; // 텍스트 방향 강제
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
@ -67,7 +72,9 @@ $colors: (
|
|||||||
width: 120px;
|
width: 120px;
|
||||||
background-color: map-get($colors, 'dark-700');
|
background-color: map-get($colors, 'dark-700');
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
box-shadow:
|
||||||
|
0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
||||||
|
0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
animation: fadeIn 0.15s ease-out;
|
animation: fadeIn 0.15s ease-out;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user