import { useState, type ComponentProps } from "react"; import { formatTimecode, parseTimecode } from "../../lib/timecode"; import { Input } from "./Input"; // A field a cut's time is typed into (lib/timecode): the editor's segment ends, the clip panel's In / // Out. A typed draft is the field's until Enter or leaving the field takes it; then the field shows // the value as it landed — the caller may clamp it (a time past the end) — and a time it cannot read // stays, marked invalid, for fixing rather than vanishing or seeming taken. One element throughout, // so Enter leaves the focus in the field (a field re-created per value dropped it on ). type Props = Omit< ComponentProps, "value" | "defaultValue" | "onChange" | "invalid" > & { /** Seconds. */ value: number; /** Show hours (pass `duration >= 3600`, so one item's fields agree). */ hours?: boolean; /** A readable time was typed (not called when it equals `value`). */ onCommit: (seconds: number) => void; }; export function TimecodeField({ value, hours = false, onCommit, onBlur, onKeyDown, ...rest }: Props) { const [draft, setDraft] = useState(null); const invalid = draft != null && parseTimecode(draft) == null; const commit = () => { if (draft == null) return; const v = parseTimecode(draft); if (v == null) return; setDraft(null); if (Math.abs(v - value) > 1e-3) onCommit(v); }; return ( setDraft(e.target.value)} onBlur={(e) => { commit(); onBlur?.(e); }} onKeyDown={(e) => { onKeyDown?.(e); if (e.key !== "Enter" || e.defaultPrevented) return; e.preventDefault(); commit(); }} /> ); }