{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"date-picker","type":"registry:ui","title":"DatePicker","description":"A field that opens a floating month grid, with full keyboard navigation (arrow keys across days, Page Up/Down across months, Shift for years), min/max and per-day disabling, and a fully localized accessible name on every day cell.","author":"fancy-ui <https://fancy-ui.rama.app>","categories":["forms"],"registryDependencies":["https://fancy-ui.rama.app/r/internals.json","https://fancy-ui.rama.app/r/internals-dom.json","https://fancy-ui.rama.app/r/internals-motion.json","https://fancy-ui.rama.app/r/sound.json","https://fancy-ui.rama.app/r/utils.json"],"files":[{"path":"react/src/components/date-picker/DatePicker.tsx","content":"\"use client\";\n\nimport { forwardRef, useEffect, useMemo, useState } from \"react\";\nimport type { ComponentPropsWithoutRef, KeyboardEvent, MouseEvent as ReactMouseEvent } from \"react\";\n\nimport { cn } from \"../../utils\";\nimport { Portal } from \"../../internals/Portal\";\nimport { useAnchorPosition } from \"../../internals/use-anchor-position\";\nimport { useDismissable } from \"../../internals/dismissable\";\nimport { useField } from \"../../internals/field\";\nimport { useFancyId } from \"../../internals/use-id\";\nimport { useComposedRefs } from \"../../internals/dom/use-composed-refs\";\nimport { useElementRef } from \"../../internals/dom/use-element-ref\";\nimport { anchored, originFor } from \"../../internals/motion/anchored\";\nimport { usePresence } from \"../../internals/motion/presence\";\nimport { useSoundCue } from \"../../sound/use-sound\";\nimport {\n\tgetMonthGrid,\n\taddMonths,\n\tisSameDay,\n\tclampDate,\n\tformatISODate,\n} from \"../../internals/calendar-core\";\nimport type { WeekStartsOn } from \"../../internals/calendar-core\";\nimport {\n\taddDays,\n\tdayOnly,\n\tfindEnabledDay,\n\tfindEnabledInRow,\n\tformatDayAccessibleName,\n\tformatMonthYear,\n\tformatTriggerDate,\n\tgetWeekdayNames,\n\tisDayInRange,\n} from \"./date-utils\";\nimport \"./date-picker.css\";\n\n/**\n * Every other native `<button>` attribute and listener (`aria-*`, `data-*`,\n * `onFocus`, `onBlur`, `onKeyDown`, `form`, ...) is forwarded to the trigger\n * button. The managed ARIA (`role`, `aria-expanded`, `aria-controls`,\n * `aria-haspopup`, `aria-invalid`, `aria-required`) stays the component's own.\n */\nexport interface DatePickerProps extends Omit<\n\tComponentPropsWithoutRef<\"button\">,\n\t\"className\" | \"id\" | \"name\" | \"value\" | \"disabled\" | \"type\" | \"role\" | \"children\"\n> {\n\t/**\n\t * The selected date, or `null` for none. Always a local-midnight `Date` —\n\t * see the README.\n\t *\n\t * Controlled when passed (`null` included): the caller owns it and writes\n\t * the value back from `onValueChange`. Left out entirely, the component\n\t * keeps its own copy and still reports every change — the React spelling\n\t * of the source's `$bindable(null)`.\n\t */\n\tvalue?: Date | null;\n\t/** Called with the new value whenever a day is picked. */\n\tonValueChange?: (value: Date | null) => void;\n\t/** Earliest selectable day (inclusive), compared at day granularity. */\n\tmin?: Date;\n\t/** Latest selectable day (inclusive), compared at day granularity. */\n\tmax?: Date;\n\t/** 0 for Sunday, 1 for Monday. Defaults to 1, matching the mockup. */\n\tweekStartsOn?: WeekStartsOn;\n\t/** Blocks opening the panel; excluded from form submission. Overridden by a surrounding FormField. */\n\tdisabled?: boolean;\n\t/** Marks the field required for the surrounding form. Overridden by a surrounding FormField. */\n\trequired?: boolean;\n\t/** Drives the error border and `aria-invalid`. Overridden by a surrounding FormField. */\n\tinvalid?: boolean;\n\t/** Element id. Overridden by a surrounding FormField's own `controlId`. */\n\tid?: string;\n\t/** Native `name` — when set, a hidden input carries the ISO date so the value participates in real form submission. */\n\tname?: string;\n\t/** Accessible name — for a control with no visible Label next to it. */\n\tlabel?: string;\n\t/** Shown in the trigger while no day is selected. */\n\tplaceholder?: string;\n\t/** BCP 47 locale for month, weekday, day and trigger-label formatting. Defaults to the runtime's own locale. */\n\tlocale?: string;\n\t/** Rejects individual days beyond `min`/`max` — e.g. weekends, holidays. */\n\tisDateDisabled?: (date: Date) => boolean;\n\t/** Additional CSS classes, merged onto the trigger button. */\n\tclassName?: string;\n\t/**\n\t * Plays the matching interface cue through the sound controller. Off\n\t * by default; only audible once the user has enabled sound.\n\t */\n\tsound?: boolean;\n}\n\n/**\n * A calendar picker behind a combobox trigger, with a portalled, anchored\n * panel and full roving-focus keyboard navigation over the day grid.\n *\n * The trigger button arrives through the ref channel rather than a `ref`\n * prop, per PORTING.md — the Svelte source declares `ref = $bindable(null)`.\n *\n * Rest props are spread onto the trigger `<button>` BEFORE the invariants, as the\n * source spreads `...restProps` there.\n */\nexport const DatePicker = forwardRef<HTMLButtonElement, DatePickerProps>(function DatePicker(\n\t{\n\t\tvalue: valueProp,\n\t\tonValueChange,\n\t\tmin,\n\t\tmax,\n\t\tweekStartsOn = 1,\n\t\tdisabled = false,\n\t\trequired = false,\n\t\tinvalid = false,\n\t\tid,\n\t\tname,\n\t\tlabel,\n\t\tplaceholder = \"Pick a date\",\n\t\tlocale,\n\t\tisDateDisabled,\n\t\tclassName,\n\t\tsound = false,\n\t\tonClick,\n\t\t...restProps\n\t},\n\tforwardedRef\n) {\n\t// Undefined outside a FormField — every `effective*` below then falls back\n\t// to this component's own props instead of the context, so the control\n\t// works standalone exactly as it does wrapped. A `<button>` is one of the\n\t// elements `<label for>` can target, so this control uses `controlId`\n\t// rather than `labelId`/`aria-labelledby`, the same choice Input makes.\n\tconst field = useField();\n\n\tconst effectiveId = field?.controlId ?? id;\n\tconst effectiveDisabled = field?.disabled ?? disabled;\n\tconst effectiveRequired = field?.required ?? required;\n\tconst effectiveInvalid = field?.invalid ?? invalid;\n\n\tconst uid = useFancyId();\n\tconst panelId = `${uid}-panel`;\n\n\t// The source's `value = $bindable(null)`. React has no such channel, so\n\t// the prop is controlled when it is passed and this local copy takes over\n\t// when it is not. Either way `onValueChange` fires identically.\n\tconst [uncontrolledValue, setUncontrolledValue] = useState<Date | null>(null);\n\tconst isControlled = valueProp !== undefined;\n\tconst value = isControlled ? valueProp : uncontrolledValue;\n\n\tconst [open, setOpen] = useState(false);\n\n\t// Convention C-1: the NODE, not a ref, for both elements. The panel is\n\t// created by `presence.mounted`, so a `useRef` + `[]`-deps effect would\n\t// still be holding `null` when the positioning and dismissable effects\n\t// fire; the trigger is the anchor those effects read.\n\tconst [panel, setPanelNode] = useElementRef<HTMLDivElement>();\n\tconst [triggerNode, setTriggerNode] = useElementRef<HTMLButtonElement>();\n\tconst triggerRef = useComposedRefs(setTriggerNode, forwardedRef);\n\n\t// The displayed month (only year/month are read off this) and the day\n\t// currently carrying the grid's one `tabindex=\"0\"` — the roving-focus\n\t// position. Both are re-seeded from `value` (or today) every time the\n\t// panel opens, in `openPanel` below, not derived from `value` directly:\n\t// once open, paging through months must not snap back just because\n\t// `value` itself hasn't changed yet.\n\t//\n\t// `null` while the panel has never been opened, rather than seeded with\n\t// `new Date()` at construction: a \"now\" read in a render path or lazy\n\t// initializer is forbidden (convention C-7), and nothing reads either\n\t// value until `openPanel` has seeded them anyway.\n\tconst [viewDate, setViewDate] = useState<Date | null>(null);\n\tconst [focusedDate, setFocusedDateState] = useState<Date | null>(null);\n\n\tconst playCue = useSoundCue(sound);\n\n\tfunction isDayDisabled(date: Date): boolean {\n\t\treturn !isDayInRange(date, min, max) || (isDateDisabled?.(date) ?? false);\n\t}\n\n\t// Memoised like the source's own `$derived`: an arrow-key focus move within\n\t// the same month re-renders but must not reallocate the grid or reformat\n\t// these locale-derived labels, since neither actually changed.\n\tconst weekdayNames = useMemo(() => getWeekdayNames(weekStartsOn, locale), [weekStartsOn, locale]);\n\tconst monthLabel = useMemo(\n\t\t() => (viewDate ? formatMonthYear(viewDate, locale) : \"\"),\n\t\t[viewDate, locale]\n\t);\n\tconst triggerLabel = formatTriggerDate(value, locale);\n\n\t// Re-focuses the DOM to whichever cell matches `focusedDate` whenever it\n\t// (or `open`) changes, once the grid reflecting it has actually rendered —\n\t// which is what a passive effect keyed on the same values guarantees.\n\t// Queried fresh via `data-ft-date` rather than cached refs — the same\n\t// \"requery live DOM\" choice ToggleGroup's roving focus makes — so a month\n\t// switch that swaps every cell out from under a stale ref is never an\n\t// issue.\n\tuseEffect(() => {\n\t\tif (!open || !focusedDate) return;\n\t\tpanel?.querySelector<HTMLElement>(`[data-ft-date=\"${formatISODate(focusedDate)}\"]`)?.focus();\n\t}, [open, focusedDate, panel]);\n\n\tfunction setFocusedDate(date: Date) {\n\t\tsetFocusedDateState(date);\n\t\tsetViewDate((view) =>\n\t\t\t!view || date.getFullYear() !== view.getFullYear() || date.getMonth() !== view.getMonth()\n\t\t\t\t? new Date(date.getFullYear(), date.getMonth(), 1)\n\t\t\t\t: view\n\t\t);\n\t}\n\n\tfunction openPanel() {\n\t\tif (effectiveDisabled) return;\n\t\tconst seed = value ? dayOnly(value) : dayOnly(new Date());\n\t\tsetViewDate(new Date(seed.getFullYear(), seed.getMonth(), 1));\n\t\tsetFocusedDateState(seed);\n\t\tsetOpen(true);\n\t\tplayCue(\"open\");\n\t}\n\n\t// Closing always returns focus to the trigger, whether the close came from\n\t// Enter/click selecting a day, Escape, or an outside click — there is no\n\t// focus trap here (see the README) to fall back on, and without this an\n\t// Escape press would leave focus on a grid cell about to be removed from\n\t// the DOM, which browsers resolve by dropping focus to `<body>` — a real\n\t// loss for a keyboard user, not just a cosmetic one. A plain function, so\n\t// it returns focus in the same tick as the dismiss — this component needs\n\t// no focus-trap handle to satisfy the eager-return rule.\n\t//\n\t// `reason` distinguishes a commit-flavoured close (a day was just picked)\n\t// from a plain dismiss (Escape, an outside click, the trigger toggling\n\t// shut, or re-picking the day already in force). Only a dismiss plays the\n\t// `close` cue — a real commit already played `select` inside `commit`\n\t// above, and the contract is one cue per interaction, never both.\n\tfunction closePanel(reason: \"commit\" | \"dismiss\" = \"dismiss\") {\n\t\tsetOpen(false);\n\t\tif (reason === \"dismiss\") playCue(\"close\");\n\t\ttriggerNode?.focus();\n\t}\n\n\tfunction toggle() {\n\t\tif (open) closePanel();\n\t\telse openPanel();\n\t}\n\n\tfunction commit(date: Date) {\n\t\tif (isDayDisabled(date)) return;\n\t\t// Captured before `value` is overwritten: re-picking the day already in\n\t\t// force changes nothing, so it closes like a dismiss (`close`) instead\n\t\t// of playing a second `select` for a no-op.\n\t\tconst changed = !value || !isSameDay(value, date);\n\t\tif (!isControlled) setUncontrolledValue(date);\n\t\tif (changed) playCue(\"select\");\n\t\tonValueChange?.(date);\n\t\tclosePanel(changed ? \"commit\" : \"dismiss\");\n\t}\n\n\tfunction moveDays(delta: number) {\n\t\tif (!focusedDate) return;\n\t\tconst direction = delta > 0 ? 1 : -1;\n\t\tconst found = findEnabledDay(addDays(focusedDate, delta), direction, isDayDisabled);\n\t\tif (found) setFocusedDate(found);\n\t}\n\n\tfunction moveToWeekEdge(edge: \"start\" | \"end\") {\n\t\tif (!focusedDate) return;\n\t\tconst found = findEnabledInRow(focusedDate, weekStartsOn, edge, isDayDisabled);\n\t\tif (found) setFocusedDate(found);\n\t}\n\n\tfunction moveMonths(n: number) {\n\t\tif (!focusedDate) return;\n\t\tconst raw = addMonths(focusedDate, n);\n\t\tlet target = raw;\n\t\t// Ordinarily the fallback search follows the page direction (`n`'s\n\t\t// sign): paging forward and landing on a disabled day looks further\n\t\t// forward for the next enabled one. But when min/max clamping actually\n\t\t// pulled `target` back into range, the side it moved away from is\n\t\t// guaranteed disabled — that is exactly what `isDayDisabled`'s own\n\t\t// min/max check rejects — so continuing in the original direction\n\t\t// would walk straight back out of range and exhaust the whole bounded\n\t\t// search for nothing, silently leaving the keypress a no-op. Search\n\t\t// toward the interior instead: `raw` landed before `min` and got\n\t\t// pulled forward (`target > raw`) → keep searching forward; `raw`\n\t\t// landed after `max` and got pulled back (`target < raw`) → search\n\t\t// backward.\n\t\tlet searchDirection: 1 | -1 = n >= 0 ? 1 : -1;\n\t\tif (min || max) {\n\t\t\ttarget = clampDate(raw, min, max);\n\t\t\tif (target.getTime() !== raw.getTime()) {\n\t\t\t\tsearchDirection = target.getTime() > raw.getTime() ? 1 : -1;\n\t\t\t}\n\t\t}\n\t\tconst found = isDayDisabled(target)\n\t\t\t? findEnabledDay(target, searchDirection, isDayDisabled)\n\t\t\t: target;\n\t\tif (found) setFocusedDate(found);\n\t}\n\n\tfunction handleGridKeydown(event: KeyboardEvent<HTMLTableElement>) {\n\t\tif (!focusedDate) return;\n\t\tswitch (event.key) {\n\t\t\tcase \"ArrowRight\":\n\t\t\t\tmoveDays(1);\n\t\t\t\tbreak;\n\t\t\tcase \"ArrowLeft\":\n\t\t\t\tmoveDays(-1);\n\t\t\t\tbreak;\n\t\t\tcase \"ArrowDown\":\n\t\t\t\tmoveDays(7);\n\t\t\t\tbreak;\n\t\t\tcase \"ArrowUp\":\n\t\t\t\tmoveDays(-7);\n\t\t\t\tbreak;\n\t\t\tcase \"Home\":\n\t\t\t\tmoveToWeekEdge(\"start\");\n\t\t\t\tbreak;\n\t\t\tcase \"End\":\n\t\t\t\tmoveToWeekEdge(\"end\");\n\t\t\t\tbreak;\n\t\t\tcase \"PageUp\":\n\t\t\t\tmoveMonths(event.shiftKey ? -12 : -1);\n\t\t\t\tbreak;\n\t\t\tcase \"PageDown\":\n\t\t\t\tmoveMonths(event.shiftKey ? 12 : 1);\n\t\t\t\tbreak;\n\t\t\tcase \"Enter\":\n\t\t\tcase \" \":\n\t\t\t\tcommit(focusedDate);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\treturn;\n\t\t}\n\t\tevent.preventDefault();\n\t}\n\n\tconst presence = usePresence(open);\n\n\t// The placement as ACTUALLY resolved, which differs from the requested one\n\t// whenever a flip avoided the viewport edge (side) or clamping slid the\n\t// panel along the cross axis (align). `useAnchorPosition` seeds both with\n\t// the REQUESTED values, so an un-flipped panel reads the right growth\n\t// origin on its very first frame and only a real flip ever moves it.\n\tconst { side: resolvedSide, align: resolvedAlign } = useAnchorPosition(panel, {\n\t\tanchor: triggerNode,\n\t\tside: \"bottom\",\n\t\talign: \"start\",\n\t\toffset: 8,\n\t});\n\n\t// `active: open` — a plain boolean where the source needed `() => open`.\n\t// The layer stays ON the stack for its whole exit and stops being TOP of\n\t// it the instant `open` flips, so a second Escape during the fade is a\n\t// no-op and whatever is underneath gets it instead.\n\tuseDismissable(panel, {\n\t\tonDismiss: closePanel,\n\t\texclude: [triggerNode],\n\t\tactive: open,\n\t});\n\n\t// Convention C-2: composed ABOVE the conditional below.\n\t//\n\t// ONE bidirectional leg, never a split in/out pair: a calendar reopened\n\t// mid-exit continues from where it is instead of snapping to invisible\n\t// first. The factory is called with the direction at the instant each leg\n\t// starts, which is what the source's `entering: open` is for — a single\n\t// bidirectional instance reports `direction: \"both\"` and cannot tell the\n\t// two apart on its own.\n\tconst panelRef = useComposedRefs(\n\t\tsetPanelNode,\n\t\tpresence.register(anchored, (entering) => ({ side: resolvedSide, entering }))\n\t);\n\n\t// Internal handling first, the consumer's listener once after it. A disabled\n\t// control runs neither (a disabled <button> would not fire them anyway).\n\tfunction handleClick(event: ReactMouseEvent<HTMLButtonElement>): void {\n\t\tif (effectiveDisabled) return;\n\t\ttoggle();\n\t\tonClick?.(event);\n\t}\n\n\t// A consumer `aria-describedby` is merged with the FormField's, never replaces it.\n\tconst describedBy =\n\t\t[field?.describedBy, restProps[\"aria-describedby\"]].filter(Boolean).join(\" \") || undefined;\n\n\tconst classes = cn(\n\t\t\"ft-date-picker-trigger flex w-full items-center justify-between gap-2 rounded-[8px] border border-input bg-background px-[12px] py-[9px] text-left text-[13px] text-foreground transition-colors\",\n\t\t\"focus-visible:outline-none\",\n\t\t\"disabled:cursor-not-allowed disabled:opacity-50\",\n\t\teffectiveInvalid && \"border-destructive/50\",\n\t\tclassName\n\t);\n\n\t// Only year/month are ever read off `viewDate`, which is also what the\n\t// memo keys on — an arrow-key focus move that lands in the same month\n\t// must not reallocate the 6x7 grid, exactly as the source's `$derived`\n\t// only recomputes on a real dependency change.\n\tconst weeks = useMemo(\n\t\t() => (viewDate ? getMonthGrid(viewDate.getFullYear(), viewDate.getMonth(), weekStartsOn) : []),\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t\t[viewDate?.getFullYear(), viewDate?.getMonth(), weekStartsOn]\n\t);\n\n\t/*\n\t\t`data-state` on the panel is an ordinary React attribute (divergence\n\t\tD-2) carrying `surfaceState`'s TWO values — never `\"opening\"`\n\t\t(convention C-5). The source had to write it imperatively from a\n\t\ttransition handler because its scheduler skips effects inside a branch\n\t\tit has marked inert; React re-renders an exiting surface normally.\n\n\t\t`inert` is not written by hand either: `usePresence` sets it on the\n\t\tregistered node for the whole exit, so a day cell cannot take a click\n\t\ton its way out.\n\n\t\t`data-align` reports the REQUESTED alignment, as the source does,\n\t\twhile the growth origin uses the RESOLVED one.\n\t*/\n\treturn (\n\t\t<>\n\t\t\t<button\n\t\t\t\t{...restProps}\n\t\t\t\tref={triggerRef}\n\t\t\t\ttype=\"button\"\n\t\t\t\trole=\"combobox\"\n\t\t\t\tid={effectiveId}\n\t\t\t\tname={name}\n\t\t\t\tclassName={classes}\n\t\t\t\tdisabled={effectiveDisabled}\n\t\t\t\taria-haspopup=\"grid\"\n\t\t\t\taria-expanded={open}\n\t\t\t\taria-controls={open ? panelId : undefined}\n\t\t\t\taria-required={effectiveRequired ? \"true\" : restProps[\"aria-required\"]}\n\t\t\t\taria-invalid={effectiveInvalid ? \"true\" : restProps[\"aria-invalid\"]}\n\t\t\t\taria-describedby={describedBy}\n\t\t\t\taria-label={label ?? restProps[\"aria-label\"]}\n\t\t\t\tonClick={handleClick}\n\t\t\t>\n\t\t\t\t<span className={triggerLabel ? undefined : \"text-muted-foreground\"}>\n\t\t\t\t\t{triggerLabel ?? placeholder}\n\t\t\t\t</span>\n\t\t\t\t<span aria-hidden=\"true\" className=\"text-muted-foreground\">\n\t\t\t\t\t📅\n\t\t\t\t</span>\n\t\t\t</button>\n\n\t\t\t{name ? (\n\t\t\t\t/* Real form participation for a control with no native input\n\t\t\t\t   underneath. ISO (YYYY-MM-DD), the same shape a native\n\t\t\t\t   `<input type=\"date\">` submits. `disabled` mirrors the\n\t\t\t\t   trigger's own effective disabled state, so a disabled\n\t\t\t\t   DatePicker is excluded from submission exactly like a\n\t\t\t\t   disabled native control would be. React's missing-onChange\n\t\t\t\t   warning exempts `type=\"hidden\"`, so no handler is needed. */\n\t\t\t\t<input\n\t\t\t\t\ttype=\"hidden\"\n\t\t\t\t\tname={name}\n\t\t\t\t\tvalue={value ? formatISODate(value) : \"\"}\n\t\t\t\t\tform={restProps.form}\n\t\t\t\t\tdisabled={effectiveDisabled}\n\t\t\t\t/>\n\t\t\t) : null}\n\n\t\t\t<Portal>\n\t\t\t\t{presence.mounted && viewDate && focusedDate ? (\n\t\t\t\t\t<div\n\t\t\t\t\t\tref={panelRef}\n\t\t\t\t\t\tid={panelId}\n\t\t\t\t\t\tclassName=\"ft-date-picker-panel border-border bg-popover text-popover-foreground flex w-max flex-col gap-2 rounded-[10px] border p-[12px] shadow-lg outline-none\"\n\t\t\t\t\t\tdata-state={presence.surfaceState}\n\t\t\t\t\t\tdata-side={resolvedSide}\n\t\t\t\t\t\tdata-align=\"start\"\n\t\t\t\t\t\tstyle={{ transformOrigin: originFor(resolvedSide, resolvedAlign) }}\n\t\t\t\t\t>\n\t\t\t\t\t\t<div className=\"flex items-center justify-between gap-2 text-[12px] font-semibold\">\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tclassName=\"ft-date-picker-nav text-muted-foreground cursor-pointer\"\n\t\t\t\t\t\t\t\taria-label=\"Previous month\"\n\t\t\t\t\t\t\t\tonClick={() => moveMonths(-1)}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t‹\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t<span>{monthLabel}</span>\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tclassName=\"ft-date-picker-nav text-muted-foreground cursor-pointer\"\n\t\t\t\t\t\t\t\taria-label=\"Next month\"\n\t\t\t\t\t\t\t\tonClick={() => moveMonths(1)}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t›\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t<table\n\t\t\t\t\t\t\trole=\"grid\"\n\t\t\t\t\t\t\taria-label={monthLabel}\n\t\t\t\t\t\t\tclassName=\"border-collapse text-[11px]\"\n\t\t\t\t\t\t\tonKeyDown={handleGridKeydown}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<thead>\n\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t{weekdayNames.map((weekdayName, weekdayIndex) => (\n\t\t\t\t\t\t\t\t\t\t<th\n\t\t\t\t\t\t\t\t\t\t\tkey={weekdayIndex}\n\t\t\t\t\t\t\t\t\t\t\trole=\"columnheader\"\n\t\t\t\t\t\t\t\t\t\t\tscope=\"col\"\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"text-muted-foreground px-1 py-1 font-normal\"\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t{weekdayName}\n\t\t\t\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t</thead>\n\t\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t\t{weeks.map((week, weekIndex) => (\n\t\t\t\t\t\t\t\t\t/* No explicit role=\"row\": `<tr>` already has an\n\t\t\t\t\t\t\t\t\t   implicit ARIA row role inside a table[role=grid]\n\t\t\t\t\t\t\t\t\t   — restating it is flagged as redundant. */\n\t\t\t\t\t\t\t\t\t<tr key={weekIndex}>\n\t\t\t\t\t\t\t\t\t\t{week.map((day) => {\n\t\t\t\t\t\t\t\t\t\t\tconst iso = formatISODate(day.date);\n\t\t\t\t\t\t\t\t\t\t\tconst dayDisabled = isDayDisabled(day.date);\n\t\t\t\t\t\t\t\t\t\t\tconst dayFocused = isSameDay(day.date, focusedDate);\n\t\t\t\t\t\t\t\t\t\t\tconst daySelected = value ? isSameDay(day.date, value) : false;\n\t\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t\t<td\n\t\t\t\t\t\t\t\t\t\t\t\t\tkey={iso}\n\t\t\t\t\t\t\t\t\t\t\t\t\trole=\"gridcell\"\n\t\t\t\t\t\t\t\t\t\t\t\t\tdata-ft-date={iso}\n\t\t\t\t\t\t\t\t\t\t\t\t\ttabIndex={dayFocused ? 0 : -1}\n\t\t\t\t\t\t\t\t\t\t\t\t\taria-selected={daySelected}\n\t\t\t\t\t\t\t\t\t\t\t\t\taria-disabled={dayDisabled ? \"true\" : undefined}\n\t\t\t\t\t\t\t\t\t\t\t\t\taria-label={formatDayAccessibleName(day.date, locale)}\n\t\t\t\t\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"ft-date-picker-day cursor-pointer rounded-[6px] px-1 py-1 text-center\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t!day.inMonth && \"text-muted-foreground\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdayDisabled && \"cursor-not-allowed opacity-40\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdaySelected && \"bg-accent text-accent-foreground\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tif (dayDisabled) return;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetFocusedDate(day.date);\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tcommit(day.date);\n\t\t\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{day.date.getDate()}\n\t\t\t\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t</table>\n\t\t\t\t\t</div>\n\t\t\t\t) : null}\n\t\t\t</Portal>\n\n\t\t\t{/* Always mounted, closed or open — an id inserted at the same moment\n\t\t\t    as its text usually goes unread, so a screen reader hears the new\n\t\t\t    month the instant paging lands, not only \"something changed\" a\n\t\t\t    beat later. */}\n\t\t\t<div className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n\t\t\t\t{open ? monthLabel : \"\"}\n\t\t\t</div>\n\t\t</>\n\t);\n});\n\nDatePicker.displayName = \"DatePicker\";\n","type":"registry:ui","target":"@ui/fancy/components/date-picker/DatePicker.tsx"},{"path":"react/src/components/date-picker/date-picker.css","content":"/* Ported from the source component's <style> block, verbatim. Every rule was\n   already anchored on the component's own root classes, so no port-added\n   anchor is needed. */\n\n.ft-date-picker-trigger:focus-visible,\n.ft-date-picker-day:focus-visible,\n.ft-date-picker-nav:focus-visible {\n\t--ft-date-picker-accent: var(\n\t\t--ft-accent,\n\t\tlight-dark(oklch(0.5432 0.2528 300.22), oklch(0.604 0.2606 301.75))\n\t);\n}\n\n.ft-date-picker-trigger:focus-visible {\n\tborder-color: var(--ft-date-picker-accent);\n\tbox-shadow: 0 0 0 3px color-mix(in oklab, var(--ft-date-picker-accent) 25%, transparent);\n}\n\n.ft-date-picker-day:focus-visible,\n.ft-date-picker-nav:focus-visible {\n\toutline: none;\n\tbox-shadow: 0 0 0 3px color-mix(in oklab, var(--ft-date-picker-accent) 35%, transparent);\n}\n","type":"registry:ui","target":"@ui/fancy/components/date-picker/date-picker.css"},{"path":"react/src/components/date-picker/date-utils.ts","content":"\"use client\";\n\n// Pure helpers layered on top of `internals/calendar-core.ts` for exactly\n// what DatePicker needs beyond the shared month math: day-granularity\n// comparisons, a bounded \"skip disabled days\" search, week edges, and\n// locale-aware labels. Nothing here reimplements `getMonthGrid`/`addMonths`/\n// `isSameDay`/`clampDate`/`formatISODate` — those come straight from\n// `calendar-core.js`.\n//\n// Every date in and out of this module is a local-time `Date` at, or\n// truncated to, local midnight — never a UTC/ISO round trip. Constructing a\n// `Date` from a Y/M/D triple and formatting it in another time zone can shift\n// the calendar day; staying in local components throughout, the same\n// convention `calendar-core.ts` itself uses, is what avoids that.\n\nimport type { WeekStartsOn } from \"../../internals/calendar-core\";\n\n/** Truncates `date` to local midnight. Always returns a new Date. */\nexport function dayOnly(date: Date): Date {\n\treturn new Date(date.getFullYear(), date.getMonth(), date.getDate());\n}\n\n/** Returns a new date `n` days away from `date`, in local time. */\nexport function addDays(date: Date, n: number): Date {\n\treturn new Date(date.getFullYear(), date.getMonth(), date.getDate() + n);\n}\n\n/** Whether `date` falls strictly before `bound`, comparing whole days only. */\nexport function isBeforeDay(date: Date, bound: Date): boolean {\n\treturn dayOnly(date).getTime() < dayOnly(bound).getTime();\n}\n\n/** Whether `date` falls strictly after `bound`, comparing whole days only. */\nexport function isAfterDay(date: Date, bound: Date): boolean {\n\treturn dayOnly(date).getTime() > dayOnly(bound).getTime();\n}\n\n/** Whether `date` falls within `[min, max]` (either bound optional), comparing whole days only. */\nexport function isDayInRange(date: Date, min?: Date, max?: Date): boolean {\n\tif (min && isBeforeDay(date, min)) return false;\n\tif (max && isAfterDay(date, max)) return false;\n\treturn true;\n}\n\n// A generous but finite ceiling for \"keep stepping one day at a time until an\n// enabled day turns up\" — about ten years. Real usage (a `min`/`max` window,\n// or an `isDateDisabled` blocking weekends/holidays) resolves in a handful of\n// steps; this exists only so a caller who disables every day, accidentally or\n// not, terminates instead of hanging the tab. Mirrors the same \"every option\n// disabled must still terminate\" rule the shared listbox contract documents.\nexport const MAX_DAY_SEARCH = 3660;\n\n/**\n * Steps from `from` one day at a time in `direction` until `isDisabled`\n * returns false, including `from` itself. Returns `null` if nothing enabled\n * turns up within `MAX_DAY_SEARCH` steps.\n */\nexport function findEnabledDay(\n\tfrom: Date,\n\tdirection: 1 | -1,\n\tisDisabled: (date: Date) => boolean\n): Date | null {\n\tlet candidate = from;\n\tlet steps = 0;\n\twhile (isDisabled(candidate)) {\n\t\tif (steps >= MAX_DAY_SEARCH) return null;\n\t\tcandidate = addDays(candidate, direction);\n\t\tsteps++;\n\t}\n\treturn candidate;\n}\n\n/** The first day (in `weekStartsOn` terms) of the week `date` falls in. */\nexport function weekStartOf(date: Date, weekStartsOn: WeekStartsOn): Date {\n\tconst offset = (date.getDay() - weekStartsOn + 7) % 7;\n\treturn addDays(date, -offset);\n}\n\n/**\n * Scans the 7-day row containing `date` from one edge inward for the first\n * enabled day, stopping at the far edge of that same row rather than\n * spilling into the next week. Returns `null` if the whole row is disabled.\n */\nexport function findEnabledInRow(\n\tdate: Date,\n\tweekStartsOn: WeekStartsOn,\n\tedge: \"start\" | \"end\",\n\tisDisabled: (date: Date) => boolean\n): Date | null {\n\tconst start = weekStartOf(date, weekStartsOn);\n\tconst direction = edge === \"start\" ? 1 : -1;\n\tlet candidate = edge === \"start\" ? start : addDays(start, 6);\n\tfor (let steps = 0; steps < 7; steps++) {\n\t\tif (!isDisabled(candidate)) return candidate;\n\t\tcandidate = addDays(candidate, direction);\n\t}\n\treturn null;\n}\n\n/**\n * Short weekday names (\"Mon\", \"Tue\", ...) ordered starting from\n * `weekStartsOn`, formatted with `locale`. A fixed reference week (a known\n * Sunday plus offsets) stands in for \"any week\" — only the weekday, not the\n * date, ever reaches the formatter.\n */\nexport function getWeekdayNames(weekStartsOn: WeekStartsOn, locale?: string): string[] {\n\t// 2023-01-01 is a Sunday; Sunday + weekStartsOn lands on the configured\n\t// first day of the week.\n\tconst referenceSunday = new Date(2023, 0, 1);\n\tconst formatter = new Intl.DateTimeFormat(locale, { weekday: \"short\" });\n\treturn Array.from({ length: 7 }, (_, i) =>\n\t\tformatter.format(addDays(referenceSunday, weekStartsOn + i))\n\t);\n}\n\n/** \"July 2026\"-style label for the currently displayed month, locale-aware. */\nexport function formatMonthYear(date: Date, locale?: string): string {\n\treturn new Intl.DateTimeFormat(locale, { month: \"long\", year: \"numeric\" }).format(date);\n}\n\n/** Full accessible name for a single day cell — never just the bare number. */\nexport function formatDayAccessibleName(date: Date, locale?: string): string {\n\treturn new Intl.DateTimeFormat(locale, {\n\t\tweekday: \"long\",\n\t\tday: \"numeric\",\n\t\tmonth: \"long\",\n\t\tyear: \"numeric\",\n\t}).format(date);\n}\n\n/** Short label for the trigger button, e.g. \"24 Jul 2026\". `null`/`undefined` format to `undefined`. */\nexport function formatTriggerDate(\n\tdate: Date | null | undefined,\n\tlocale?: string\n): string | undefined {\n\tif (!date) return undefined;\n\treturn new Intl.DateTimeFormat(locale, {\n\t\tday: \"numeric\",\n\t\tmonth: \"short\",\n\t\tyear: \"numeric\",\n\t}).format(date);\n}\n","type":"registry:ui","target":"@ui/fancy/components/date-picker/date-utils.ts"},{"path":"react/src/components/date-picker/index.ts","content":"\"use client\";\n\nexport { DatePicker } from \"./DatePicker\";\nexport type { DatePickerProps } from \"./DatePicker\";\n","type":"registry:ui","target":"@ui/fancy/components/date-picker/index.ts"}],"meta":{"license":"MIT","package":"fancy-ui-react","version":"0.7.0","source":"https://fancy-ui.rama.app/docs/components/date-picker?framework=react"}}