{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "calendar-popover",
  "type": "registry:component",
  "title": "Calendar popover",
  "description": "Types or picks a date and optional local time. A 1px field opens a calendar in the native top layer.",
  "registryDependencies": [
    "https://vlak.dev/r/vlak-base.json",
    "https://vlak.dev/r/inter.json",
    "https://vlak.dev/r/calendar.json",
    "https://vlak.dev/r/input.json",
    "https://vlak.dev/r/button.json",
    "https://vlak.dev/r/icons.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/calendar-popover.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport { vlak, mq } from \"./tokens.stylex\";\nimport { rs } from \"./rs\";\nimport { cx } from \"./cx\";\nimport { useMergedRefs } from \"./merge-refs\";\nimport { useOverlayPosition } from \"./use-overlay-position\";\nimport { Calendar } from \"./calendar\";\nimport { Input, type InputProps } from \"./input\";\nimport { Button } from \"./button\";\nimport { Icon } from \"./icons\";\n\nexport interface CalendarPopoverProps extends Omit<InputProps, \"type\" | \"value\" | \"defaultValue\" | \"onChange\" | \"min\" | \"max\" | \"plain\" | \"grouped\"> {\n  type?: \"date\" | \"datetime-local\";\n  /** Local canonical value. Typed partial drafts are emitted too. */\n  value?: string;\n  defaultValue?: string;\n  onValueChange?: (value: string) => void;\n  min?: string;\n  max?: string;\n  weekStart?: 0 | 1;\n  locale?: string;\n  triggerLabel?: string;\n  dialogLabel?: string;\n}\n\nconst styles = stylex.create({\n  root: { width: \"100%\", minWidth: 0, display: \"flex\", flexDirection: \"column\", gap: \"0.5rem\" },\n  label: { fontSize: { default: \"0.75rem\", [mq.phone]: vlak.controlLabel }, fontWeight: 600, color: vlak.gray, lineHeight: \"16px\" },\n  control: {\n    display: \"flex\", alignItems: \"stretch\", minWidth: 0, minHeight: vlak.hit, boxSizing: \"border-box\", borderWidth: vlak.hairline, borderStyle: \"solid\", borderColor: vlak.controlBorder, borderRadius: vlak.radiusSm, backgroundColor: vlak.paper,\n    outlineWidth: { default: 0, \":focus-within\": 2 }, outlineStyle: { default: \"none\", \":focus-within\": \"solid\" }, outlineColor: { default: vlak.ink, [mq.forcedColors]: \"Highlight\" }, outlineOffset: 2,\n  },\n  input: { minWidth: 0, width: \"100%\", flex: \"1 1 0\", height: vlak.hit, minHeight: vlak.hit, fontVariantNumeric: \"tabular-nums\", outlineOffset: -2 },\n  trigger: { flexShrink: 0, width: vlak.hit, height: vlak.hit, minWidth: vlak.hit, minHeight: vlak.hit, padding: 0, borderWidth: 0, borderRadius: vlak.radiusSm },\n  panel: {\n    boxSizing: \"border-box\", position: \"fixed\", inset: \"auto\", margin: 0,\n    width: \"calc(19.25rem + 26px)\", maxWidth: \"calc(100vw - 2px)\", padding: \"clamp(4px, calc((100vw - 312px) / 2), 12px)\",\n    borderWidth: vlak.hairline, borderStyle: \"solid\", borderColor: vlak.divider, borderRadius: vlak.radius,\n    backgroundColor: vlak.paper, color: vlak.ink, overflow: \"auto\", overscrollBehavior: \"contain\",\n    boxShadow: { default: \"0 8px 24px rgba(0,0,0,0.08)\", [mq.forcedColors]: \"none\" },\n    \"::backdrop\": { backgroundColor: \"transparent\" },\n  },\n  grid: { width: \"100%\" },\n  time: { display: \"grid\", gridTemplateColumns: \"1fr 1fr\", gap: \"0.5rem\", paddingBlock: \"0.75rem\", paddingInline: \"clamp(4px, calc((100vw - 312px) / 2), 12px)\", marginInline: \"calc(-1 * clamp(4px, calc((100vw - 312px) / 2), 12px))\", borderTopWidth: vlak.hairline, borderTopStyle: \"solid\", borderTopColor: vlak.divider },\n  actions: { display: \"flex\", alignItems: \"center\", gap: \"0.25rem\", flexWrap: \"wrap\", marginTop: \"0.5rem\", marginInline: \"calc(-1 * clamp(4px, calc((100vw - 312px) / 2), 12px))\", paddingTop: \"0.25rem\", paddingInline: \"clamp(4px, calc((100vw - 312px) / 2), 12px)\", borderTopWidth: vlak.hairline, borderTopStyle: \"solid\", borderTopColor: vlak.divider },\n  action: { width: \"auto\", minWidth: vlak.hit, minHeight: vlak.hit, paddingInline: \"0.5rem\", marginInlineEnd: { default: null, \":first-child\": \"auto\" }, borderWidth: 0, backgroundColor: \"transparent\", fontWeight: 500, outlineOffset: -2 },\n  done: { width: \"auto\", minWidth: vlak.hit, paddingInline: \"0.875rem\" },\n  feedback: { margin: 0, fontSize: { default: \"0.75rem\", [mq.phone]: \"0.875rem\" }, color: vlak.gray, lineHeight: 1.45 },\n  error: { color: vlak.ink },\n});\n\nconst pad = (value: number) => String(value).padStart(2, \"0\");\nconst dateString = (date: Date) => `${String(date.getFullYear()).padStart(4, \"0\")}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;\nconst canonical = (value: string, type: string) => type === \"datetime-local\" ? value.replace(\" \", \"T\") : value;\nconst display = (value: string, type: string) => type === \"datetime-local\" ? value.replace(\"T\", \" \") : value;\nfunction parseDate(value: string): Date | undefined {\n  if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) return;\n  const [year, month, day] = value.split(\"-\").map(Number) as [number, number, number];\n  if (year < 1 || year > 9999) return;\n  const date = new Date(0);\n  date.setFullYear(year, month - 1, day);\n  date.setHours(12, 0, 0, 0);\n  if (date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day) return date;\n}\nfunction valid(value: string, type: string): boolean {\n  return type === \"date\" ? Boolean(parseDate(value)) : /^\\d{4}-\\d{2}-\\d{2}T(?:[01]\\d|2[0-3]):[0-5]\\d$/.test(value) && Boolean(parseDate(value.slice(0, 10)));\n}\nconst usable = (element: HTMLElement | null) => Boolean(element?.isConnected && !element.closest(\"[hidden],[inert],dialog:not([open])\") && element.getClientRects().length && getComputedStyle(element).visibility !== \"hidden\");\n\n/** Editable civil dates with native form validation and a top-layer calendar. */\nexport const CalendarPopover = React.forwardRef<HTMLInputElement, CalendarPopoverProps>(function CalendarPopover({\n  type = \"date\", value, defaultValue = \"\", onValueChange, min, max, label, hint, error, feedback, ok,\n  weekStart = 1, locale = \"en\", triggerLabel = \"Open calendar\", dialogLabel,\n  required, disabled, readOnly, name, form, id, className, style, placeholder,\n  onKeyDown, onBlur, onInvalid, ...props\n}, forwardedRef) {\n  const generatedId = React.useId(), inputId = id ?? generatedId, panelId = `${inputId}-calendar`;\n  const rootRef = React.useRef<HTMLDivElement>(null), controlRef = React.useRef<HTMLDivElement>(null), inputRef = React.useRef<HTMLInputElement>(null), triggerRef = React.useRef<HTMLButtonElement>(null), panelRef = React.useRef<HTMLDivElement>(null);\n  const mergedRef = useMergedRefs(inputRef, forwardedRef);\n  const controlled = value !== undefined;\n  const [inner, setInner] = React.useState(defaultValue);\n  const current = canonical(controlled ? value : inner, type);\n  const [open, setOpen] = React.useState(false), openRef = React.useRef(false);\n  const [showError, setShowError] = React.useState(false), [panelError, setPanelError] = React.useState(\"\");\n  const [draft, setDraft] = React.useState({ day: \"\", hours: \"09\", minutes: \"00\" });\n  const context = React.useRef<HTMLDialogElement | null>(null);\n  const minValue = min && valid(min, type) ? min : undefined, maxValue = max && valid(max, type) ? max : undefined;\n  const minDay = parseDate(minValue?.slice(0, 10) ?? \"0001-01-01\"), maxDay = parseDate(maxValue?.slice(0, 10) ?? \"9999-12-31\");\n  const placement = useOverlayPosition(open, panelRef, controlRef, undefined, \"bottom\", { popover: \"auto\", edge: 1, matchAnchorWidth: false });\n\n  const issue = (next: string) => {\n    if (!next) return required ? \"Enter a date.\" : \"\";\n    if (!valid(next, type)) return type === \"date\" ? \"Enter a valid date as YYYY-MM-DD.\" : \"Enter a valid date and time as YYYY-MM-DD HH:mm.\";\n    if (minValue && next < minValue) return `Choose ${display(minValue, type)} or later.`;\n    if (maxValue && next > maxValue) return `Choose ${display(maxValue, type)} or earlier.`;\n    return \"\";\n  };\n  const message = issue(current);\n  React.useLayoutEffect(() => { inputRef.current?.setCustomValidity(message); }, [message]);\n  const sameContext = React.useCallback(() => {\n    const owner = rootRef.current;\n    const dialog = owner?.closest<HTMLDialogElement>(\"dialog\") ?? null;\n    return usable(owner) && dialog === context.current && (!dialog || dialog.open);\n  }, []);\n  const close = React.useCallback((restore = false) => {\n    const canRestore = restore && sameContext() && usable(triggerRef.current);\n    openRef.current = false;\n    const panel = panelRef.current;\n    if (typeof panel?.hidePopover === \"function\" && panel.matches(\":popover-open\")) panel.hidePopover();\n    setOpen(false); setPanelError(\"\");\n    if (canRestore) triggerRef.current?.focus({ preventScroll: true });\n  }, [sameContext]);\n  const change = (next: string) => { if (!controlled) setInner(next); onValueChange?.(next); };\n  const choose = (next: string) => {\n    const problem = issue(next);\n    if (problem) { setPanelError(problem); return; }\n    setShowError(false); change(next); close(true);\n  };\n  const draftValue = type === \"date\" ? draft.day : `${draft.day}T${draft.hours.padStart(2, \"0\")}:${draft.minutes.padStart(2, \"0\")}`;\n  const validClock = /^\\d{1,2}$/.test(draft.hours) && Number(draft.hours) <= 23 && /^\\d{1,2}$/.test(draft.minutes) && Number(draft.minutes) <= 59;\n  const draftIssue = type === \"datetime-local\" && !validClock ? \"Enter hours from 0 to 23 and minutes from 0 to 59.\" : issue(draftValue);\n  const clampTime = (day: string, hours: string, minutes: string) => {\n    let next = `${day}T${hours.padStart(2, \"0\")}:${minutes.padStart(2, \"0\")}`;\n    if (minValue?.slice(0, 10) === day && next < minValue) next = minValue;\n    if (maxValue?.slice(0, 10) === day && next > maxValue) next = maxValue;\n    return { day, hours: next.slice(11, 13), minutes: next.slice(14, 16) };\n  };\n  const show = () => {\n    if (disabled || readOnly) return;\n    context.current = rootRef.current?.closest<HTMLDialogElement>(\"dialog\") ?? null;\n    let seed = valid(current, type) ? current : `${dateString(new Date())}${type === \"datetime-local\" ? \"T09:00\" : \"\"}`;\n    if (minValue && seed < minValue) seed = minValue;\n    if (maxValue && seed > maxValue) seed = maxValue;\n    setDraft({ day: seed.slice(0, 10), hours: type === \"datetime-local\" ? seed.slice(11, 13) : \"09\", minutes: type === \"datetime-local\" ? seed.slice(14, 16) : \"00\" });\n    setPanelError(\"\"); openRef.current = true; setOpen(true);\n  };\n  const selectDay = (day: Date) => {\n    const next = dateString(day);\n    if (type === \"date\") choose(next);\n    else { setDraft(state => clampTime(next, state.hours, state.minutes)); setPanelError(\"\"); }\n  };\n\n  React.useEffect(() => {\n    if (!open) return;\n    const owner = rootRef.current;\n    const onFocus = (event: FocusEvent) => { if (event.target instanceof Node && !owner?.contains(event.target)) close(); };\n    const onPointer = (event: PointerEvent) => { if (event.target instanceof Node && !owner?.contains(event.target)) close(); };\n    const observer = new MutationObserver(() => { if (!sameContext()) close(); });\n    for (let ancestor: HTMLElement | null = owner; ancestor; ancestor = ancestor.parentElement) observer.observe(ancestor, { attributes: true, attributeFilter: [\"hidden\", \"inert\", \"open\", \"class\", \"style\"] });\n    document.addEventListener(\"focusin\", onFocus);\n    document.addEventListener(\"pointerdown\", onPointer, true);\n    return () => { observer.disconnect(); document.removeEventListener(\"focusin\", onFocus); document.removeEventListener(\"pointerdown\", onPointer, true); };\n  }, [open, close, sameContext]);\n  const seen = React.useRef({ current, type, min, max, disabled, readOnly });\n  React.useLayoutEffect(() => {\n    const before = seen.current;\n    seen.current = { current, type, min, max, disabled, readOnly };\n    if (openRef.current && (before.current !== current || before.type !== type || before.min !== min || before.max !== max || disabled || readOnly)) close();\n  }, [current, type, min, max, disabled, readOnly, close]);\n  React.useEffect(() => {\n    const reset = (event: Event) => {\n      if (event.target !== inputRef.current?.form) return;\n      queueMicrotask(() => {\n        if (event.defaultPrevented || !inputRef.current?.isConnected) return;\n        if (!controlled) setInner(defaultValue);\n        setShowError(false); close();\n      });\n    };\n    document.addEventListener(\"reset\", reset, true);\n    return () => document.removeEventListener(\"reset\", reset, true);\n  }, [controlled, defaultValue, close]);\n\n  const root = rs([\"rs-calendar-popover\", className], styles.root);\n  const lab = rs([\"rs-calendar-popover-label\"], styles.label);\n  const control = rs([\"rs-calendar-popover-control\"], styles.control);\n  const field = rs([\"rs-calendar-popover-input\"], styles.input);\n  const trigger = rs([\"rs-calendar-popover-trigger\"], styles.trigger);\n  const panel = rs([\"rs-calendar-popover-panel\"], styles.panel);\n  const grid = rs([\"rs-calendar-popover-grid\"], styles.grid);\n  const time = rs([\"rs-calendar-popover-time\"], styles.time);\n  const actions = rs([\"rs-calendar-popover-actions\"], styles.actions);\n  const action = rs([\"rs-calendar-popover-action\"], styles.action);\n  const done = rs([\"rs-calendar-popover-done\"], styles.done);\n  const quiet = rs([\"rs-calendar-popover-feedback\"], styles.feedback);\n  const err = rs([\"rs-calendar-popover-error\"], styles.feedback, styles.error);\n  const shownError = error ?? (showError ? message : \"\");\n  const describedBy = cx(props[\"aria-describedby\"], hint != null && `${inputId}-hint`, Boolean(shownError) && `${inputId}-error`) || undefined;\n  const today = dateString(new Date());\n  const todayDisabled = Boolean(minValue && today < minValue.slice(0, 10) || maxValue && today > maxValue.slice(0, 10));\n  return <div ref={rootRef} className={root.className} style={{ ...root.style, ...style }}>\n    {label != null && <label htmlFor={inputId} className={lab.className} style={lab.style}>{label}</label>}\n    <div ref={controlRef} className={control.className} style={control.style}>\n      <Input {...props} ref={mergedRef} id={inputId} plain grouped type=\"text\" value={display(current, type)} required={required} disabled={disabled} readOnly={readOnly} form={form} ok={ok} aria-describedby={describedBy} aria-invalid={shownError ? true : props[\"aria-invalid\"]} placeholder={placeholder ?? (type === \"date\" ? \"YYYY-MM-DD\" : \"YYYY-MM-DD HH:mm\")} className={field.className} style={field.style}\n        onChange={event => { change(canonical(event.target.value, type)); setShowError(false); }}\n        onBlur={event => { onBlur?.(event); setShowError(true); }}\n        onInvalid={event => { setShowError(true); onInvalid?.(event); }}\n        onKeyDown={event => { onKeyDown?.(event); if (event.defaultPrevented) return; if (event.key === \"ArrowDown\") { event.preventDefault(); show(); } else if (event.key === \"Escape\" && openRef.current) { event.preventDefault(); event.stopPropagation(); close(true); } }} />\n      <Button ref={triggerRef} type=\"button\" variant=\"ghost\" className={trigger.className} style={trigger.style} aria-label={triggerLabel} aria-haspopup=\"dialog\" aria-expanded={open} aria-controls={open ? panelId : undefined} disabled={disabled || readOnly}\n        onClick={() => openRef.current ? close(true) : show()} onKeyDown={event => { if (event.key === \"ArrowDown\") { event.preventDefault(); show(); } else if (event.key === \"Escape\" && openRef.current) { event.preventDefault(); event.stopPropagation(); close(true); } }}><Icon name=\"calendar\" size={16} /></Button>\n    </div>\n    {name != null && <input type=\"hidden\" name={name} value={current} form={form} disabled={disabled} />}\n    {feedback != null && <span className={quiet.className} style={quiet.style}>{feedback}</span>}\n    {hint != null && <span id={`${inputId}-hint`} className={quiet.className} style={quiet.style}>{hint}</span>}\n    {shownError && <span id={`${inputId}-error`} role=\"alert\" className={err.className} style={err.style}>{shownError}</span>}\n    <div ref={panelRef} id={panelId} hidden={!open} popover=\"auto\" role=\"dialog\" aria-label={dialogLabel ?? (type === \"date\" ? \"Choose date\" : \"Choose date and time\")} className={panel.className} style={{ ...panel.style, ...placement }}\n      onToggle={event => { if ((event.nativeEvent as ToggleEvent).newState === \"closed\" && openRef.current) close(); }}\n      onKeyDown={event => {\n        if (event.key === \"Escape\") { event.preventDefault(); event.stopPropagation(); close(true); }\n        else if (event.key === \"Enter\" && event.target instanceof HTMLInputElement) { event.preventDefault(); if (draftIssue) setPanelError(draftIssue); else choose(draftValue); }\n      }}>\n      {open && <>\n        <Calendar fixedWeeks={false} className={grid.className} style={grid.style} value={parseDate(draft.day)} defaultMonth={parseDate(draft.day)} min={minDay} max={maxDay} weekStart={weekStart} locale={locale} autoFocus={placement.visibility === \"visible\"} onValueChange={selectDay} />\n        {type === \"datetime-local\" && <div className={time.className} style={time.style}>\n          <Input label=\"Hours\" type=\"number\" inputMode=\"numeric\" min={0} max={23} step={1} value={draft.hours} onChange={event => { setDraft(state => ({ ...state, hours: event.target.value })); setPanelError(\"\"); }} />\n          <Input label=\"Minutes\" type=\"number\" inputMode=\"numeric\" min={0} max={59} step={1} value={draft.minutes} onChange={event => { setDraft(state => ({ ...state, minutes: event.target.value })); setPanelError(\"\"); }} />\n        </div>}\n        {panelError && <p role=\"alert\" className={err.className} style={err.style}>{panelError}</p>}\n        <div className={actions.className} style={actions.style}>\n          <Button type=\"button\" variant=\"ghost\" className={action.className} style={action.style} disabled={todayDisabled} onClick={() => selectDay(new Date())}>Today</Button>\n          {!required && <Button type=\"button\" variant=\"ghost\" className={action.className} style={action.style} onClick={() => { change(\"\"); setShowError(false); close(true); }}>Clear</Button>}\n          {type === \"datetime-local\" && <Button type=\"button\" className={done.className} style={done.style} onClick={() => { if (draftIssue) setPanelError(draftIssue); else choose(draftValue); }}>Done</Button>}\n        </div>\n      </>}\n    </div>\n  </div>;\n});\n",
      "type": "registry:component",
      "target": "components/vlak/calendar-popover.tsx"
    },
    {
      "path": "vlak/use-overlay-position.ts",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\n/** Keep lightweight overlays inside the visual viewport. The native top layer avoids clipping. */\nexport function useOverlayPosition(\n  open: boolean,\n  panel: React.RefObject<HTMLElement | null>,\n  anchor: React.RefObject<HTMLElement | null>,\n  point?: { x: number; y: number } | null,\n  placement: \"bottom\" | \"inline-end\" = \"bottom\",\n  options?: { popover?: \"auto\" | \"manual\"; edge?: number; matchAnchorWidth?: boolean },\n) {\n  const [position, setPosition] = React.useState<React.CSSProperties>({ visibility: \"hidden\" });\n  React.useLayoutEffect(() => {\n    const element = panel.current;\n    if (!open || !element) return;\n    const native = typeof element.showPopover === \"function\";\n    if (native) {\n      element.setAttribute(\"popover\", options?.popover ?? \"manual\");\n      if (!element.matches(\":popover-open\")) element.showPopover();\n    }\n    const place = () => {\n      const viewport = window.visualViewport;\n      const edge = Math.max(0, options?.edge ?? 8);\n      const leftEdge = (viewport?.offsetLeft ?? 0) + edge;\n      const topEdge = (viewport?.offsetTop ?? 0) + edge;\n      const viewportWidth = viewport?.width ?? window.innerWidth;\n      const viewportHeight = viewport?.height ?? window.innerHeight;\n      const rightEdge = leftEdge + viewportWidth - edge * 2;\n      const bottomEdge = topEdge + viewportHeight - edge * 2;\n      const target = anchor.current?.getBoundingClientRect();\n      const paint = getComputedStyle(element);\n      const borderX = (Number.parseFloat(paint.borderLeftWidth) || 0) + (Number.parseFloat(paint.borderRightWidth) || 0);\n      const borderY = (Number.parseFloat(paint.borderTopWidth) || 0) + (Number.parseFloat(paint.borderBottomWidth) || 0);\n      const matchAnchor = options?.matchAnchorWidth !== false;\n      let panelWidth = element.scrollWidth + borderX;\n      if (!matchAnchor) {\n        // Measure the authored width without the last placement’s viewport clamp.\n        // Restoring it here also keeps resize observation tied to the final layout.\n        const { width, minWidth, maxWidth } = element.style;\n        element.style.width = \"\";\n        element.style.minWidth = \"\";\n        element.style.maxWidth = \"\";\n        panelWidth = element.getBoundingClientRect().width;\n        Object.assign(element.style, { width, minWidth, maxWidth });\n      }\n      const width = Math.min(Math.max(panelWidth, matchAnchor ? target?.width ?? 0 : 0), viewportWidth - edge * 2);\n      const height = Math.min(element.scrollHeight + borderY, viewportHeight - edge * 2);\n      let left = point?.x ?? target?.left ?? leftEdge;\n      let top = point?.y ?? (target ? target.bottom + 6 : topEdge);\n      if (placement === \"inline-end\" && target) {\n        const rtl = getComputedStyle(anchor.current!).direction === \"rtl\";\n        left = rtl ? target.left - width - 4 : target.right + 4;\n        if (left + width > rightEdge) left = target.left - width - 4;\n        if (left < leftEdge) left = target.right + 4;\n        top = target.top;\n      }\n      if (top + height > bottomEdge) {\n        top = placement === \"inline-end\" ? bottomEdge - height : point?.y !== undefined ? point.y - height : target ? target.top - height - 6 : bottomEdge - height;\n      }\n      left = Math.max(leftEdge, Math.min(left, rightEdge - width));\n      top = Math.max(topEdge, Math.min(top, bottomEdge - height));\n      const next: React.CSSProperties = {\n        position: \"fixed\", inset: \"auto\", left, top, margin: 0,\n        minWidth: matchAnchor ? Math.min(target?.width ?? 0, viewportWidth - edge * 2) : 0,\n        maxWidth: viewportWidth - edge * 2, maxHeight: viewportHeight - edge * 2,\n        width, overflow: \"auto\", visibility: \"visible\",\n      };\n      setPosition((previous) => JSON.stringify(previous) === JSON.stringify(next) ? previous : next);\n    };\n    place();\n    const observer = typeof ResizeObserver === \"undefined\" ? undefined : new ResizeObserver(place);\n    observer?.observe(element);\n    if (anchor.current) observer?.observe(anchor.current);\n    window.addEventListener(\"resize\", place);\n    window.addEventListener(\"scroll\", place, true);\n    window.visualViewport?.addEventListener(\"resize\", place);\n    window.visualViewport?.addEventListener(\"scroll\", place);\n    return () => {\n      observer?.disconnect();\n      window.removeEventListener(\"resize\", place);\n      window.removeEventListener(\"scroll\", place, true);\n      window.visualViewport?.removeEventListener(\"resize\", place);\n      window.visualViewport?.removeEventListener(\"scroll\", place);\n    };\n  }, [open, panel, anchor, point?.x, point?.y, placement, options?.popover, options?.edge, options?.matchAnchorWidth]);\n  return position;\n}\n",
      "type": "registry:file",
      "target": "components/vlak/use-overlay-position.ts"
    },
    {
      "path": "vlak/styles/calendar-popover.css",
      "content": "/* ── calendar-popover: generated from packages/react/src/components/calendar-popover.tsx ── */\n.rs-calendar-popover{width:100%;min-width:0;display:flex;flex-direction:column;gap:0.5rem}\n.rs-calendar-popover-label{font-size:0.75rem;font-weight:600;color:var(--text-secondary);line-height:16px}\n@media (max-width: 640px){.rs-calendar-popover-label{font-size:var(--control-label)}}\n.rs-calendar-popover-control{display:flex;align-items:stretch;min-width:0;min-height:var(--hit);box-sizing:border-box;border-width:1px;border-style:solid;border-color:var(--control-border);border-radius:var(--radius-sm);background-color:var(--bg);outline-width:0;outline-style:none;outline-color:var(--text);outline-offset:2px}\n.rs-calendar-popover-control:focus-within{outline-width:2px;outline-style:solid}\n@media (forced-colors: active){.rs-calendar-popover-control{outline-color:Highlight}}\n.rs-calendar-popover-input{min-width:0;width:100%;flex:1 1 0;height:var(--hit);min-height:var(--hit);font-variant-numeric:tabular-nums;outline-offset:-2px}\n.rs-calendar-popover-trigger{flex-shrink:0;width:var(--hit);height:var(--hit);min-width:var(--hit);min-height:var(--hit);padding:0;border-width:0;border-radius:var(--radius-sm)}\n.rs-calendar-popover-panel{box-sizing:border-box;position:fixed;inset:auto;margin:0;width:calc(19.25rem + 26px);max-width:calc(100vw - 2px);padding:clamp(4px, calc((100vw - 312px) / 2), 12px);border-width:1px;border-style:solid;border-color:var(--divider);border-radius:var(--radius);background-color:var(--bg);color:var(--text);overflow:auto;overscroll-behavior:contain;box-shadow:0 8px 24px rgba(0,0,0,0.08)}\n.rs-calendar-popover-panel::backdrop{background-color:transparent}\n@media (forced-colors: active){.rs-calendar-popover-panel{box-shadow:none}}\n.rs-calendar-popover-grid{width:100%}\n.rs-calendar-popover-time{display:grid;grid-template-columns:1fr 1fr;gap:0.5rem;padding-block:0.75rem;padding-inline:clamp(4px, calc((100vw - 312px) / 2), 12px);margin-inline:calc(-1 * clamp(4px, calc((100vw - 312px) / 2), 12px));border-top-width:1px;border-top-style:solid;border-top-color:var(--divider)}\n.rs-calendar-popover-actions{display:flex;align-items:center;gap:0.25rem;flex-wrap:wrap;margin-top:0.5rem;margin-inline:calc(-1 * clamp(4px, calc((100vw - 312px) / 2), 12px));padding-top:0.25rem;padding-inline:clamp(4px, calc((100vw - 312px) / 2), 12px);border-top-width:1px;border-top-style:solid;border-top-color:var(--divider)}\n.rs-calendar-popover-action{width:auto;min-width:var(--hit);min-height:var(--hit);padding-inline:0.5rem;border-width:0;background-color:transparent;font-weight:500;outline-offset:-2px}\n.rs-calendar-popover-action:first-child{margin-inline-end:auto}\n.rs-calendar-popover-done{width:auto;min-width:var(--hit);padding-inline:0.875rem}\n.rs-calendar-popover-feedback,.rs-calendar-popover-error{margin:0;font-size:0.75rem;color:var(--text-secondary);line-height:1.45}\n@media (max-width: 640px){.rs-calendar-popover-feedback,.rs-calendar-popover-error{font-size:0.875rem}}\n.rs-calendar-popover-error{color:var(--text)}\n",
      "type": "registry:file",
      "target": "styles/vlak/calendar-popover.css"
    }
  ],
  "meta": {
    "vlak": {
      "category": "forms",
      "classes": [
        "rs-calendar-popover",
        "rs-calendar-popover-label",
        "rs-calendar-popover-control",
        "rs-calendar-popover-input",
        "rs-calendar-popover-trigger",
        "rs-calendar-popover-panel",
        "rs-calendar-popover-grid",
        "rs-calendar-popover-time",
        "rs-calendar-popover-actions",
        "rs-calendar-popover-action",
        "rs-calendar-popover-done",
        "rs-calendar-popover-feedback",
        "rs-calendar-popover-error"
      ],
      "snippet": "<div class=\"rs-calendar-popover\"><label class=\"rs-calendar-popover-label\" for=\"deadline\">Deadline</label><div class=\"rs-calendar-popover-control\"><input id=\"deadline\" class=\"rs-input rs-input-grouped rs-calendar-popover-input\" type=\"text\" value=\"2026-07-24\" /><button class=\"rs-btn-ghost rs-calendar-popover-trigger\" type=\"button\" aria-label=\"Open calendar\" aria-haspopup=\"dialog\" aria-expanded=\"false\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" aria-hidden=\"true\"><path d=\"M3 3.5h10v10H3zM3 6.5h10M5.5 1.5v4M10.5 1.5v4\" /></svg></button></div><input type=\"hidden\" name=\"deadline\" value=\"2026-07-24\" /></div>",
      "cssOnly": false,
      "registryDependencies": [
        "calendar",
        "input",
        "button",
        "icons",
        "vlak-lib"
      ],
      "aliases": [
        "Calendar popover",
        "CalendarPopover",
        "Date input",
        "Date field",
        "Date time picker",
        "Local date and time",
        "shadcn date picker"
      ],
      "example": "import { useState } from \"react\";\nimport { CalendarPopover } from \"@noorddev/vlak-react\";\n\nconst [date, setDate] = useState(\"2026-07-24\");\nconst [review, setReview] = useState(\"2026-07-23T10:30\");\n\n<CalendarPopover label=\"Print date\" name=\"printDate\" value={date} onValueChange={setDate} required />\n<CalendarPopover\n  label=\"Proof review\"\n  name=\"review\"\n  type=\"datetime-local\"\n  value={review}\n  onValueChange={setReview}\n  hint=\"Local date and time, without a time zone.\"\n/>",
      "usage": {
        "use": [
          "Form dates that can be typed or chosen from a month grid.",
          "type=\"datetime-local\" for a date with hour and minute controls; the value has no time zone.",
          "String values such as 2026-07-24 or 2026-07-24T10:30; min and max use the same format."
        ],
        "avoid": [
          "A permanently visible month grid; use Calendar.",
          "Date ranges; use DateRangePicker.",
          "An absolute timestamp shared across time zones; resolve the local value and its time zone in the application."
        ]
      },
      "keyboard": [
        {
          "keys": "Tab",
          "does": "Moves through the date field, calendar trigger, and open panel controls; leaving closes the panel"
        },
        {
          "keys": "Enter, Space on the trigger",
          "does": "Opens the calendar"
        },
        {
          "keys": "Arrow down in the field or on the trigger",
          "does": "Opens the calendar and focuses the selected day"
        },
        {
          "keys": "Arrow keys, Home, End",
          "does": "Moves between calendar days or to the ends of the week"
        },
        {
          "keys": "Page up, Page down",
          "does": "Moves by month; hold Shift to move by year"
        },
        {
          "keys": "Enter, Space on a day",
          "does": "Selects the date; local date and time fields wait for Done"
        },
        {
          "keys": "Escape",
          "does": "Closes the panel and discards unconfirmed date and time changes"
        }
      ],
      "a11y": [
        "An editable input keeps its native label, hint, error, required, disabled and readOnly behavior. The ref resolves to this input.",
        "Typing emits partial drafts as well as complete values. Invalid dates and values outside min or max fail native form validation; local date and time fields display a space and emit a T between date and time.",
        "The named calendar trigger opens a non-modal dialog using the native Popover API, above surrounding overflow and dialogs.",
        "The compact panel keeps square 44px day targets independently of the input width. The calendar shows only the four to six weeks needed for its month.",
        "The local date and time panel has separately labelled hour and minute inputs. Done confirms the draft; Escape and outside dismissal cancel it.",
        "A named hidden input submits the canonical string. Controlled with value and onValueChange, or uncontrolled with defaultValue."
      ]
    }
  }
}
