import { Minus, Plus } from "lucide-react"; interface FormNumberStepperProps { value: string; onChange: (nextValue: string) => void; min?: number; max?: number; step?: number; allowDecimal?: boolean; decrementLabel: string; incrementLabel: string; className?: string; } const DECIMAL_ROUNDING_FACTOR = 1000; function clamp(value: number, min: number, max?: number): number { const clampedMin = Math.max(min, value); if (max == null) return clampedMin; return Math.min(max, clampedMin); } function normalizeDecimal(value: number): number { return Math.round(value * DECIMAL_ROUNDING_FACTOR) / DECIMAL_ROUNDING_FACTOR; } function toDisplayValue(value: number, allowDecimal: boolean): string { if (!allowDecimal) return String(Math.max(0, Math.trunc(value))); const normalized = normalizeDecimal(value); return normalized.toString(); } function sanitizeRawInput(raw: string, allowDecimal: boolean): string { const normalizedRaw = raw.replace(",", "."); if (allowDecimal) { const cleaned = normalizedRaw.replace(/[^\d.]/g, ""); const [integerPart = "", ...fractionalParts] = cleaned.split("."); if (fractionalParts.length === 0) return integerPart; return `${integerPart}.${fractionalParts.join("")}`; } return normalizedRaw.replace(/\D/g, ""); } function parseInputValue(raw: string, allowDecimal: boolean): number | null { if (raw.trim() === "") return null; const parsed = allowDecimal ? Number.parseFloat(raw) : Number.parseInt(raw, 10); if (Number.isNaN(parsed)) return null; return parsed; } export function FormNumberStepper({ value, onChange, min = 0, max, step = 1, allowDecimal = false, decrementLabel, incrementLabel, className = "", }: FormNumberStepperProps) { const parsed = parseInputValue(value, allowDecimal); const baseValue = parsed ?? min; const canDecrement = baseValue > min; const canIncrement = max == null || baseValue < max; const normalizedClassName = ["number-stepper", "form-number-stepper", className].filter(Boolean).join(" "); const handleStep = (direction: -1 | 1) => { const nextRaw = clamp(baseValue + direction * step, min, max); onChange(toDisplayValue(nextRaw, allowDecimal)); }; const handleInputChange = (nextRaw: string) => { onChange(sanitizeRawInput(nextRaw, allowDecimal)); }; const handleBlur = () => { const nextParsed = parseInputValue(value, allowDecimal); if (nextParsed == null) return; const clamped = clamp(nextParsed, min, max); onChange(toDisplayValue(clamped, allowDecimal)); }; return (