import { useEffect, useRef, useState } from 'react'; import { cn } from '@/lib/utils'; // ShiftRow — the RIT / XIT offset control, shared by the radio consoles. // // It began inside the Icom panel and is here because the next console needed // exactly it. Consoles that each invent their own way of nudging an offset make // an operator learn the same thing twice, which is the complaint that moved it: // "none of the consoles look alike". // // Three ways to move it, because operators reach for different ones: the ± keys, // the wheel over the number, and TYPING a value straight in. The last one is // what a button row cannot do — 'put me 300 Hz down' is one keystroke sequence, // not thirty clicks. export function ShiftRow({ label, on, hz, accent, disabled, step = 10, onToggle, onSet }: { label: string; on: boolean; hz: number; accent: string; disabled?: boolean; step?: number; onToggle: () => void; onSet: (hz: number) => void; }) { const ref = useRef(null); const [editing, setEditing] = useState(null); const cb = useRef(onSet); cb.current = onSet; const cur = useRef({ hz, on, disabled }); cur.current = { hz, on, disabled }; // Wheel over the row. A native non-passive listener, because React's onWheel // is passive and cannot preventDefault — without that the panel scrolls under // the pointer while the number changes. useEffect(() => { const el = ref.current; if (!el) return; const onWheel = (e: WheelEvent) => { const c = cur.current; if (c.disabled || !c.on) return; e.preventDefault(); cb.current(c.hz + (e.deltaY < 0 ? step : -step)); }; el.addEventListener('wheel', onWheel, { passive: false }); return () => el.removeEventListener('wheel', onWheel); }, [step]); const commit = (raw: string) => { setEditing(null); const v = parseInt(raw.replace(/[^0-9+-]/g, ''), 10); if (!Number.isNaN(v)) onSet(v); }; const dead = disabled || !on; return (
{editing !== null ? ( setEditing(e.target.value)} onBlur={(e) => commit(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') commit((e.target as HTMLInputElement).value); else if (e.key === 'Escape') setEditing(null); }} className="w-20 bg-transparent text-center text-sm font-mono font-bold tabular-nums outline-none" /> ) : ( )}
); }