import { motion } from 'framer-motion' import * as React from 'react' import { useRef, useState } from 'react' import { Checkbox } from '../ui/checkbox' import { Input } from '../ui/input' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select' import { Textarea } from '../ui/textarea' import type { EditInputType } from '../types' export type EditNavigation = 'next' | 'prev' export type CellEditorProps = { inputType: EditInputType selectOptions?: { label: string; value: string }[] initialValue: string disabled?: boolean /** Commit the edit; `nav` requests focus move to the adjacent editable cell. */ onCommit: (value: string | number | boolean, nav?: EditNavigation) => void onCancel: () => void } function parseNumber(raw: string): string | number { const trimmed = raw.trim() if (trimmed === '') return '' const n = Number(trimmed) return Number.isFinite(n) ? n : raw } /** * Renders the editor matching `meta.inputType` (spec §7.3) with the §7.4 * commit/cancel/navigation key handling. */ export function CellEditor({ inputType, selectOptions, initialValue, disabled, onCommit, onCancel, }: CellEditorProps) { const [value, setValue] = useState(initialValue) // Set once an explicit commit/cancel ran, so the trailing blur is ignored. const doneRef = useRef(false) const commit = (v: string | number | boolean, nav?: EditNavigation) => { if (doneRef.current) return doneRef.current = true onCommit(v, nav) } const cancel = () => { if (doneRef.current) return doneRef.current = true onCancel() } const handleNavKeys = (e: React.KeyboardEvent, current: string | number | boolean) => { if (e.key === 'Escape') { e.preventDefault() e.stopPropagation() cancel() return true } if (e.key === 'Tab') { e.preventDefault() e.stopPropagation() commit(current, e.shiftKey ? 'prev' : 'next') return true } return false } const wrap = (children: React.ReactNode) => ( e.stopPropagation()} onDoubleClick={(e) => e.stopPropagation()} > {children} ) if (inputType === 'boolean') { const checked = value === 'true' return wrap( , ) } if (inputType === 'select') { return wrap( , ) } if (inputType === 'number') { return wrap( setValue(e.target.value)} onKeyDown={(e) => { if (handleNavKeys(e, parseNumber(value))) return if (e.key === 'Enter') { e.preventDefault() commit(parseNumber(value)) } }} onBlur={() => commit(parseNumber(value))} onFocus={(e) => e.target.select()} aria-label="Edit number value" />, ) } // text — auto-expanding textarea; Enter commits, Shift+Enter inserts a newline. return wrap(