{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "date-cue",
  "type": "registry:ui",
  "title": "Date cue",
  "description": "An inline civil date: day/week detents, nearby relative source words, a resolved date and a Calendar opened by hold or keyboard. Explicit today, bounds and footprint keep date/source semantics stable.",
  "dependencies": [
    "@base-ui/react@^1.8.0"
  ],
  "registryDependencies": [
    "https://metalui.dev/r/calendar.json",
    "https://metalui.dev/r/motion.json",
    "https://metalui.dev/r/numeric-cue.json",
    "https://metalui.dev/r/popover.json",
    "https://metalui.dev/r/tokens.json"
  ],
  "files": [
    {
      "path": "packages/metalui/src/components/date-cue/date-cue.tsx",
      "type": "registry:ui",
      "target": "components/metalui/components/date-cue/date-cue.tsx",
      "content": "'use client';\nimport * as React from 'react';\nimport { NumericCue, type NumericCueProps, type NumericCueUnit } from '../numeric-cue/numeric-cue';\nimport { Calendar } from '../calendar/calendar';\nimport { Popover } from '../popover/popover';\nimport { haptic } from '../../motion/haptic';\nimport './date-cue.css';\n\nconst DAY = 24 * 60 * 60 * 1000;\nfunction utcDay(value: string): Date {\n  const match = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(value);\n  if (!match) throw new Error('DateCue requires civil dates in YYYY-MM-DD form.');\n  const date = new Date(0); date.setUTCFullYear(Number(match[1]), Number(match[2]) - 1, Number(match[3])); date.setUTCHours(0, 0, 0, 0);\n  if (date.getUTCFullYear() < 1 || date.toISOString().slice(0, 10) !== value) throw new Error('DateCue requires valid civil dates.');\n  return date;\n}\nconst ordinal = (value: string) => utcDay(value).getTime() / DAY;\nconst civil = (number: number) => new Date(Math.round(number) * DAY).toISOString().slice(0, 10);\nconst localDay = (value: string) => { const date = utcDay(value); const local = new Date(0); local.setFullYear(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()); local.setHours(0, 0, 0, 0); return local; };\nconst fromLocal = (date: Date) => `${String(date.getFullYear()).padStart(4, '0')}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;\n\n/** Default English source grammar; the explicit today snapshot makes nearby words unambiguous. */\nexport function relativeDateWords(value: string, today: string): string {\n  const delta = ordinal(value) - ordinal(today);\n  if (delta === -1) return 'yesterday';\n  if (delta === 0) return 'today';\n  if (delta === 1) return 'tomorrow';\n  if (Math.abs(delta) <= 7) {\n    const weekday = new Intl.DateTimeFormat('en', { weekday: 'long', timeZone: 'UTC' }).format(utcDay(value));\n    return `${delta < 0 ? 'last' : 'next'} ${weekday}`;\n  }\n  return value;\n}\nexport interface DateCueProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'onChange'> {\n  value: string;\n  today: string;\n  min: string;\n  max: string;\n  footprint: readonly string[];\n  label: string;\n  onValueChange: (day: string) => void;\n  format?: (day: string) => string;\n  source?: (day: string) => string;\n  locale?: string;\n  raw?: boolean;\n  hint?: boolean;\n  inputAria?: NumericCueProps['inputAria'];\n  disabled?: boolean;\n  readOnly?: boolean;\n  onBegin?: (() => void) | (() => boolean);\n  onSourceChange?: ((words: string) => void) | ((words: string) => boolean);\n  onCommit?: () => void;\n  onCancel?: NumericCueProps['onCancel'];\n}\n\n/** An operable civil day. Day arithmetic crosses DST without adding an instant's 24 hours. */\nexport const DateCue = React.forwardRef<HTMLSpanElement, DateCueProps>(function DateCue({ value, today, min, max, footprint, label, onValueChange,\n  format, source, locale = 'en-GB', raw, hint = true, inputAria, disabled = false, readOnly = false, onBegin, onSourceChange, onCommit, onCancel, className, ...props }, forwardedRef) {\n  const root = React.useRef<HTMLSpanElement>(null);\n  const changed = React.useRef(false);\n  const capturedWords = React.useRef<string | undefined>(undefined);\n  const pending = React.useRef<(() => void) | null>(null);\n  const [open, setOpen] = React.useState(false);\n  const popupId = React.useId();\n  const hintId = React.useId();\n  const sourceWords = React.useCallback((day: string) => source?.(day) ?? relativeDateWords(day, today), [source, today]);\n  const resolved = React.useCallback((day: string) => new Intl.DateTimeFormat(locale, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC' }).format(utcDay(day)), [locale]);\n  const display = React.useCallback((day: string) => {\n    if (format) return format(day);\n    if (Math.abs(ordinal(day) - ordinal(today)) <= 7) return sourceWords(day);\n    return new Intl.DateTimeFormat(locale, { weekday: 'short', day: 'numeric', month: 'short', year: day.slice(0, 4) !== today.slice(0, 4) ? 'numeric' : undefined, timeZone: 'UTC' }).format(utcDay(day));\n  }, [format, today, sourceWords, locale]);\n  const units = React.useMemo<readonly NumericCueUnit[]>(() => [{ id: 'day', label: 'calendar day', factor: 1, step: 1, smallStep: 1, largeStep: 7,\n    format: amount => display(civil(amount)), source: amount => sourceWords(civil(amount)) }], [display, sourceWords]);\n  const stopHold = React.useCallback(() => { pending.current?.(); pending.current = null; }, []);\n  React.useEffect(() => stopHold, [stopHold]);\n  React.useEffect(() => { if (disabled || readOnly) { stopHold(); setOpen(false); } }, [disabled, readOnly, stopHold]);\n  const input = React.useCallback(() => root.current?.querySelector<HTMLInputElement>('input[type=\"text\"]') ?? null, []);\n  const openCalendar = () => {\n    if (disabled || readOnly) return;\n    stopHold();\n    try { document.exitPointerLock(); } catch { /* A normal touch press does not use pointer lock. */ }\n    setOpen(true);\n  };\n  const hold = (event: React.PointerEvent<HTMLSpanElement>) => {\n    props.onPointerDownCapture?.(event);\n    if (event.defaultPrevented || disabled || readOnly || event.button !== 0) return;\n    stopHold();\n    const owner = root.current?.ownerDocument.defaultView ?? window;\n    const css = getComputedStyle(root.current!);\n    const delay = parseFloat(css.getPropertyValue('--mu-r-button-hold-duration'));\n    const tolerance = parseFloat(css.getPropertyValue('--mu-space-2'));\n    if (!Number.isFinite(delay) || delay <= 0) return;\n    let distance = 0;\n    const cancel = () => stopHold();\n    const move = (movement: PointerEvent) => { distance += Math.abs(movement.movementX) + Math.abs(movement.movementY); if (distance >= tolerance) stopHold(); };\n    const timer = owner.setTimeout(openCalendar, delay);\n    owner.addEventListener('pointermove', move, true); owner.addEventListener('pointerup', cancel, true); owner.addEventListener('pointercancel', cancel, true);\n    pending.current = () => { owner.clearTimeout(timer); owner.removeEventListener('pointermove', move, true); owner.removeEventListener('pointerup', cancel, true); owner.removeEventListener('pointercancel', cancel, true); };\n  };\n  const begin = () => { changed.current = false; capturedWords.current = sourceWords(value); };\n  const write = (words: string) => {\n    if (!changed.current && words === capturedWords.current) return true;\n    if (!changed.current) {\n      if (onBegin?.() === false) return false;\n      changed.current = true;\n    }\n    return onSourceChange?.(words) !== false;\n  };\n  const commit = () => { if (changed.current) onCommit?.(); changed.current = false; };\n  return <Popover open={open} onOpenChange={(next, details) => {\n    if (!next && details.reason === 'outside-press' && details.event.target instanceof Node && root.current?.contains(details.event.target)) { details.cancel(); return; }\n    setOpen(next);\n  }}>\n    <NumericCue {...props} ref={element => { root.current = element; if (typeof forwardedRef === 'function') forwardedRef(element); else if (forwardedRef) forwardedRef.current = element; }}\n      value={{ value: ordinal(value), unit: 'day' }} units={units} min={ordinal(min)} max={ordinal(max)} footprint={footprint} label={label}\n      allowTyping={false} hint={hint} resolved={resolved(value)} kind=\"date\" meaning=\"time\" locale={locale} raw={raw} disabled={disabled} readOnly={readOnly}\n      inputAria={{ ...inputAria, 'aria-haspopup': 'dialog', 'aria-expanded': open, 'aria-controls': open ? popupId : undefined, 'aria-describedby': [hintId, inputAria?.['aria-describedby']].filter(Boolean).join(' ') }}\n      className={`mu-date-cue date-cue${className ? ` ${className}` : ''}`} onValueChange={next => { const day = civil(next.value); if (day !== value) onValueChange(day); }}\n      onBegin={begin} onSourceChange={write} onCommit={commit} onCancel={reason => { if (changed.current) onCancel?.(reason); changed.current = false; }}\n      onPointerDownCapture={hold} onKeyDownCapture={event => {\n        props.onKeyDownCapture?.(event);\n        if (event.defaultPrevented || disabled || readOnly) return;\n        if (event.altKey && event.key === 'ArrowDown' || event.key === ' ' || event.key === 'Enter') { event.preventDefault(); event.stopPropagation(); openCalendar(); }\n      }} title={hint ? `${resolved(value)} · Hold or Alt+Down for Calendar` : undefined} />\n    <span id={hintId} className=\"sr-only\">{resolved(value)}. Drag or arrow keys change days; Shift changes weeks. Hold, Alt+Down, Enter or Space opens Calendar.</span>\n    <Popover.Content id={popupId} anchor={input} finalFocus={input} initialFocus={() => root.current?.ownerDocument.querySelector<HTMLButtonElement>(`#${CSS.escape(popupId)} button[data-selected]`) ?? false} align=\"start\">\n      <Popover.Title>{label}</Popover.Title>\n      <Popover.Description>{resolved(value)}</Popover.Description>\n      <Calendar aria-label={`${label} calendar`} value={localDay(value)} min={localDay(min)} max={localDay(max)} locale={locale}\n        onValueChange={date => {\n          if (!date || disabled || readOnly) return;\n          const next = fromLocal(date);\n          if (next !== value) {\n            if (onBegin?.() === false) return;\n            if (onSourceChange?.(sourceWords(next)) === false) { onCancel?.('external'); return; }\n            onValueChange(next); onCommit?.(); haptic('detent');\n          }\n          setOpen(false);\n        }} />\n    </Popover.Content>\n  </Popover>;\n});\n"
    },
    {
      "path": "packages/metalui/src/components/date-cue/date-cue.css",
      "type": "registry:file",
      "target": "components/metalui/components/date-cue/date-cue.css",
      "content": "/* DateCue composes NumericCue, semantic Mark, Calendar and Popover recipes. */\n"
    }
  ],
  "docs": "Agent guide: https://metalui.dev/r/date-cue.md. SwiftUI: MetalDateCue in the MetalUI Swift package."
}
