{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "timecode-field",
  "type": "registry:component",
  "title": "Timecode field",
  "description": "Edits hours, minutes, seconds, and frames with native form validation for a supplied integer non-drop frame rate.",
  "registryDependencies": [
    "https://vlak.dev/r/vlak-base.json",
    "https://vlak.dev/r/inter.json",
    "https://vlak.dev/r/input.json",
    "https://vlak.dev/r/vlak-lib.json"
  ],
  "dependencies": [
    "@stylexjs/stylex"
  ],
  "devDependencies": [
    "@stylexjs/babel-plugin"
  ],
  "docs": "Vlak leaves are StyleX. Compile them with @stylexjs/babel-plugin (Vite: @stylexjs/unplugin, Next: @stylexjs/nextjs-plugin). If you would rather not run a compiler, import @noorddev/vlak-react instead: it ships precompiled with one stylesheet.",
  "files": [
    {
      "path": "vlak/timecode-field.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport { vlak } from \"./tokens.stylex\";\nimport { rs } from \"./rs\";\nimport { Input } from \"./input\";\n\nimport { useInputValue } from \"./use-input-value\";\nimport { useMergedRefs } from \"./merge-refs\";\n\nexport interface TimecodeFieldProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"type\" | \"value\" | \"defaultValue\" | \"onChange\" | \"pattern\"> {\n  label: React.ReactNode;\n  /** Integer non-drop frame rate from 1 to 99. */\n  frameRate: number;\n  value?: string;\n  defaultValue?: string;\n  /** Receives editable text, including incomplete or invalid input. */\n  onValueChange?: (value: string) => void;\n}\n\nconst styles = stylex.create({\n  root: { display: \"grid\", gap: \"0.375rem\", minWidth: 0, color: vlak.ink },\n  label: { fontSize: vlak.controlFs, fontWeight: 500, lineHeight: 1.45 },\n  input: { width: \"100%\", minWidth: 0, fontVariantNumeric: \"tabular-nums\" },\n  hint: { margin: 0, fontSize: vlak.controlLabel, color: vlak.gray, lineHeight: 1.45 },\n});\n\n/** Editable hours, minutes, seconds and frames for integer non-drop timecode. */\nexport const TimecodeField = React.forwardRef<HTMLInputElement, TimecodeFieldProps>(function TimecodeField({ label, frameRate, value, defaultValue = \"\", onValueChange, id, className, style, \"aria-describedby\": ariaDescribedBy, \"aria-invalid\": ariaInvalid, ...props }, ref) {\n  const autoId = React.useId();\n  const inputId = id ?? autoId;\n  const [, refresh] = React.useReducer((n: number) => n + 1, 0);\n  const [current, setValue, inputRef] = useInputValue<string, HTMLInputElement>(value, defaultValue, onValueChange, refresh);\n  const mergedRef = useMergedRefs(ref, inputRef);\n  const validRate = Number.isInteger(frameRate) && frameRate >= 1 && frameRate <= 99;\n  const validText = current === \"\" || (/^\\d{2}:[0-5]\\d:[0-5]\\d:\\d{2}$/.test(current) && Number(current.slice(-2)) < frameRate);\n  const error = !validRate ? \"Use an integer non-drop frame rate from 1 to 99.\" : !validText ? `Use hours:minutes:seconds:frames, with frames from 00 to ${String(frameRate - 1).padStart(2, \"0\")}.` : \"\";\n  React.useEffect(() => { inputRef.current?.setCustomValidity(error); }, [error, inputRef]);\n  const root = rs([\"rs-timecode-field\"], styles.root);\n  const caption = rs([\"rs-timecode-field-label\"], styles.label);\n  const input = rs([\"rs-timecode-field-input\", className], styles.input);\n  const hint = rs([\"rs-timecode-field-hint\"], styles.hint);\n  return <div className={root.className} style={root.style}>\n    <label htmlFor={inputId} className={caption.className} style={caption.style}>{label}</label>\n    <Input plain {...props} ref={mergedRef} id={inputId} type=\"text\" value={current} pattern=\"[0-9]{2}:[0-5][0-9]:[0-5][0-9]:[0-9]{2}\" placeholder={props.placeholder ?? \"00:00:00:00\"} spellCheck={false} aria-invalid={error ? true : ariaInvalid} aria-describedby={[ariaDescribedBy, `${inputId}-hint`].filter(Boolean).join(\" \")} className={input.className} style={{ ...input.style, ...style }} onChange={(event) => setValue(event.currentTarget.value)} />\n    <p id={`${inputId}-hint`} className={hint.className} style={hint.style}>{error || `${frameRate} fps, non-drop. Hours:minutes:seconds:frames.`}</p>\n  </div>;\n});\n",
      "type": "registry:component",
      "target": "components/vlak/timecode-field.tsx"
    },
    {
      "path": "vlak/use-input-value.ts",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\n/** Shared state for compound inputs. Native form reset restores uncontrolled defaults. */\nexport function useInputValue<T, E extends HTMLElement>(\n  value: T | undefined,\n  defaultValue: T,\n  onValueChange?: (value: T) => void,\n  onReset?: () => void,\n) {\n  const [inner, setInner] = React.useState(defaultValue);\n  const element = React.useRef<E>(null);\n  const controlled = value !== undefined;\n  const reset = React.useRef({ controlled, defaultValue, onReset });\n  reset.current = { controlled, defaultValue, onReset };\n  React.useEffect(() => {\n    const node = element.current;\n    const form = node && \"form\" in node ? (node as unknown as HTMLInputElement).form : node?.closest(\"form\");\n    if (!form) return;\n    const handleReset = (event: Event) => {\n      queueMicrotask(() => {\n        if (!event.defaultPrevented) {\n          if (!reset.current.controlled) setInner(reset.current.defaultValue);\n          reset.current.onReset?.();\n        }\n      });\n    };\n    form.addEventListener(\"reset\", handleReset);\n    return () => form.removeEventListener(\"reset\", handleReset);\n  }, []);\n  const setValue = (next: T) => {\n    if (!controlled) setInner(next);\n    onValueChange?.(next);\n  };\n  return [controlled ? value : inner, setValue, element] as const;\n}\n",
      "type": "registry:file",
      "target": "components/vlak/use-input-value.ts"
    },
    {
      "path": "vlak/styles/timecode-field.css",
      "content": "/* ── timecode-field: generated from packages/react/src/components/timecode-field.tsx ── */\n.rs-timecode-field{display:grid;gap:0.375rem;min-width:0;color:var(--text)}\n.rs-timecode-field-label{font-size:var(--control-fs);font-weight:500;line-height:1.45}\n.rs-timecode-field-input{width:100%;min-width:0;font-variant-numeric:tabular-nums}\n.rs-timecode-field-hint{margin:0;font-size:var(--control-label);color:var(--text-secondary);line-height:1.45}\n",
      "type": "registry:file",
      "target": "styles/vlak/timecode-field.css"
    }
  ],
  "meta": {
    "vlak": {
      "category": "creative",
      "classes": [
        "rs-timecode-field",
        "rs-timecode-field-label",
        "rs-timecode-field-input",
        "rs-timecode-field-hint"
      ],
      "snippet": "<div class=\"rs-timecode-field\"><label class=\"rs-timecode-field-label\" for=\"in-point\">In point</label><input class=\"rs-input rs-input-full rs-timecode-field-input\" id=\"in-point\" type=\"text\" value=\"00:01:24:12\" pattern=\"[0-9]{2}:[0-5][0-9]:[0-5][0-9]:[0-9]{2}\" aria-describedby=\"in-point-hint\" /><p class=\"rs-timecode-field-hint\" id=\"in-point-hint\">24 fps, non-drop. Hours:minutes:seconds:frames.</p></div>",
      "cssOnly": false,
      "registryDependencies": [
        "input",
        "vlak-lib"
      ],
      "aliases": [
        "TimecodeField",
        "Timecode input",
        "Frame address",
        "In point",
        "Out point",
        "Non-drop timecode"
      ],
      "example": "import { TimecodeField } from \"@noorddev/vlak-react\";\n\n<TimecodeField label=\"In point\" name=\"inPoint\" frameRate={24} defaultValue=\"00:01:24:12\" required />",
      "usage": {
        "use": [
          "Integer non-drop rates from 1 through 99, supplied explicitly by the application.",
          "Two-digit hours, minutes, seconds, and frames separated by colons.",
          "onValueChange receives editable text, including incomplete input; check native validity before committing."
        ],
        "avoid": [
          "Drop-frame, fractional frame rates, or automatic conversion between timecode standards.",
          "Coercing invalid frame numbers into a different timestamp without user intent."
        ]
      },
      "keyboard": [
        {
          "keys": "Tab",
          "does": "Focuses the native text input."
        },
        {
          "keys": "Text editing keys",
          "does": "Edits and selects timecode text using native input behavior."
        }
      ],
      "a11y": [
        "A visible label names the input, and the frame-rate hint or validation error is linked as its description.",
        "The format pattern and custom validity reject out-of-range frames and unsupported rates; required uses native form validation.",
        "A 44px input target and focus ring accompany aria-invalid on invalid timecode.",
        "The ref and native input attributes reach the input; uncontrolled form reset restores the default text."
      ]
    }
  }
}
