import { useState, useRef, useCallback, useEffect } from 'react' import { cn } from '@/lib/utils' import { useHistoryStore } from '@/stores/history-store' import { useDocumentStore } from '@/stores/document-store' interface NumberInputProps { value: number onChange: (value: number) => void min?: number max?: number step?: number label?: string icon?: React.ReactNode suffix?: string className?: string } export default function NumberInput({ value, onChange, min, max, step = 1, label, icon, suffix, className = '', }: NumberInputProps) { const [localValue, setLocalValue] = useState(String(value)) const [isDragging, setIsDragging] = useState(false) const dragStartY = useRef(0) const dragStartValue = useRef(0) useEffect(() => { if (!isDragging) { setLocalValue(String(Math.round(value * 100) / 100)) } }, [value, isDragging]) const clamp = useCallback( (v: number) => { let result = v if (min !== undefined) result = Math.max(min, result) if (max !== undefined) result = Math.min(max, result) return result }, [min, max], ) const handleBlur = () => { const parsed = parseFloat(localValue) if (!isNaN(parsed)) { onChange(clamp(parsed)) } else { setLocalValue(String(value)) } } const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { handleBlur() } else if (e.key === 'ArrowUp') { e.preventDefault() onChange(clamp(value + step)) } else if (e.key === 'ArrowDown') { e.preventDefault() onChange(clamp(value - step)) } } const handleMouseDown = (e: React.MouseEvent) => { if (e.target instanceof HTMLInputElement) return setIsDragging(true) dragStartY.current = e.clientY dragStartValue.current = value // Batch all scrub-drag onChange calls into a single undo entry useHistoryStore.getState().startBatch(useDocumentStore.getState().document) const handleMouseMove = (ev: MouseEvent) => { const delta = dragStartY.current - ev.clientY const newValue = clamp(dragStartValue.current + delta * step) onChange(newValue) } const handleMouseUp = () => { setIsDragging(false) useHistoryStore.getState().endBatch() document.removeEventListener('mousemove', handleMouseMove) document.removeEventListener('mouseup', handleMouseUp) } document.addEventListener('mousemove', handleMouseMove) document.addEventListener('mouseup', handleMouseUp) } return (