{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"navigation-menu","type":"registry:ui","title":"NavigationMenu","description":"A horizontal row of triggers that each open a rich, multi-column panel below the row — disclosure-pattern site navigation, not an application menu.","author":"fancy-ui <https://fancy-ui.rama.app>","categories":["navigation"],"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/utils.json"],"files":[{"path":"react/src/components/navigation-menu/NavigationMenu.tsx","content":"\"use client\";\n\nimport { forwardRef, useCallback, useEffect, useInsertionEffect, useRef, useState } from \"react\";\nimport type { ReactNode } from \"react\";\n\nimport { cn } from \"../../utils\";\nimport { NAVIGATION_MENU_KEY } from \"./types\";\nimport type { NavigationMenuContext } from \"./types\";\n\nexport interface NavigationMenuProps {\n\t/** The open item's value, `\"\"` when every panel is closed. */\n\tvalue?: string;\n\t/** Fires whenever the open item changes, from any trigger — pointer, keyboard or dismissal. */\n\tonValueChange?: (value: string) => void;\n\t/** Accessible name for the `<nav>` landmark. */\n\tlabel?: string;\n\t/** Delay in ms before a hovered trigger opens its panel. */\n\topenDelay?: number;\n\t/** Delay in ms before a panel closes after the pointer leaves it and its trigger. */\n\tcloseDelay?: number;\n\t/** Typically a single `NavigationMenuList`. */\n\tchildren?: ReactNode;\n\t/** Additional CSS classes for the `<nav>`. */\n\tclassName?: string;\n}\n\n/**\n * Disclosure navigation: a row of triggers, at most one panel open at a time.\n *\n * The root element arrives through the ref channel rather than a `ref` prop,\n * per PORTING.md — the source declares `ref = $bindable(null)`.\n *\n * Rest props are not spread: the source reads only these props and has no\n * rest-props object, so the port carries no wider attribute surface than the\n * component it mirrors.\n */\nexport const NavigationMenu = forwardRef<HTMLElement, NavigationMenuProps>(function NavigationMenu(\n\t{\n\t\tvalue: valueProp,\n\t\tonValueChange,\n\t\tlabel = \"Main\",\n\t\topenDelay = 150,\n\t\tcloseDelay = 200,\n\t\tchildren,\n\t\tclassName,\n\t},\n\tforwardedRef\n) {\n\t// The React shape of the source's `value = $bindable(\"\")`: an internal copy\n\t// seeded from the prop, re-synced during render whenever the CALLER changes\n\t// the prop, and free to move on its own in between. That is what makes all\n\t// three call shapes work off one implementation — a caller driving `value`\n\t// from its own state, a caller who passes only `onValueChange`, and a\n\t// caller who passes neither and lets the trigger row run the whole thing.\n\t//\n\t// Re-synced in the render path, not an effect: an effect would paint one\n\t// frame of the stale value first, and the pattern React documents for\n\t// \"adjust state when a prop changes\" is exactly this.\n\tconst [value, setValueState] = useState(valueProp ?? \"\");\n\tconst [lastValueProp, setLastValueProp] = useState(valueProp);\n\tif (lastValueProp !== valueProp) {\n\t\tsetLastValueProp(valueProp);\n\t\tsetValueState(valueProp ?? \"\");\n\t}\n\n\t// The open value as it stands RIGHT NOW, ahead of the re-render `setValue`\n\t// schedules. The source's `value` is a `$state` assignment, visible to the\n\t// very next statement; React state is not, and every decision function\n\t// below branches on it. Two dismisses inside one tick are the case that\n\t// makes the difference observable: without this the second would pass\n\t// `setValue`'s equality guard and report `onValueChange(\"\")` twice.\n\t//\n\t// Written eagerly by `setValue` (which only ever runs from a handler or a\n\t// timer, never a render) and re-synced from the committed value in an\n\t// insertion effect, never during render — a render React throws away must\n\t// not be able to publish a value nothing committed.\n\tconst valueRef = useRef(value);\n\tuseInsertionEffect(() => {\n\t\tvalueRef.current = value;\n\t}, [value]);\n\n\tconst [listEl, setListEl] = useState<HTMLElement | null>(null);\n\n\t// Registration order of mounted triggers. It exists purely so something\n\t// rendered changes when a trigger mounts or unmounts; the actual\n\t// left-to-right *order* used below always comes from a live DOM query,\n\t// never from this array's own order, so a trigger that mounts out of\n\t// visual order still navigates correctly.\n\tconst [registeredOrder, setRegisteredOrder] = useState<string[]>([]);\n\tconst [focusedValueState, setFocusedValueState] = useState<string | null>(null);\n\n\t// State, exactly as the source keeps it — and the `$state` is load-bearing\n\t// even though nothing renders it. In the source, the panel effect reads it\n\t// through `consumeFocusRequest()` and is therefore SUBSCRIBED to it, so a\n\t// later `requestFocus()` write re-runs that effect and moves focus into the\n\t// panel. A ref here would be written by the keydown and observed by\n\t// nothing, which breaks the one case where no other input to the panel\n\t// effect moves: pressing Enter/Space/ArrowDown on a trigger whose panel is\n\t// ALREADY open. `open()` short-circuits on its equality guard and `focus()`\n\t// re-sets the same value, so the panel's `isOpen`, node and item value are\n\t// all unchanged, and the focus request would be silently dropped.\n\tconst [pendingFocusValue, setPendingFocusValue] = useState<string | null>(null);\n\n\tconst openTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\tconst closeTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n\tconst clearOpenTimer = useCallback(() => {\n\t\tif (openTimer.current !== undefined) {\n\t\t\tclearTimeout(openTimer.current);\n\t\t\topenTimer.current = undefined;\n\t\t}\n\t}, []);\n\n\tconst clearCloseTimer = useCallback(() => {\n\t\tif (closeTimer.current !== undefined) {\n\t\t\tclearTimeout(closeTimer.current);\n\t\t\tcloseTimer.current = undefined;\n\t\t}\n\t}, []);\n\n\tfunction setValue(next: string) {\n\t\tclearOpenTimer();\n\t\tclearCloseTimer();\n\t\tif (valueRef.current === next) return;\n\t\tvalueRef.current = next;\n\t\tsetValueState(next);\n\t\tonValueChange?.(next);\n\t}\n\n\t// Queried fresh on every call rather than cached, so a trigger that mounted\n\t// out of visual order — or one a keyed list reordered after mounting — is\n\t// correct on the very next arrow press.\n\tconst triggerButtons = useCallback((): HTMLElement[] => {\n\t\tif (!listEl) return [];\n\t\treturn Array.from(listEl.querySelectorAll<HTMLElement>(\"[data-ft-nav-trigger]\"));\n\t}, [listEl]);\n\n\t// The roving order: triggers a keyboard can actually land on. A consumer\n\t// can forward the native `disabled` attribute to a trigger, and a disabled\n\t// button cannot take focus, so it never holds the tab stop and arrows skip\n\t// it.\n\tconst navigableTriggers = useCallback(\n\t\t(): HTMLElement[] => triggerButtons().filter((el) => !(el as HTMLButtonElement).disabled),\n\t\t[triggerButtons]\n\t);\n\n\tconst getTriggerElement = useCallback(\n\t\t(itemValue: string): HTMLElement | null => {\n\t\t\treturn triggerButtons().find((el) => el.dataset.value === itemValue) ?? null;\n\t\t},\n\t\t[triggerButtons]\n\t);\n\n\tfunction open(itemValue: string) {\n\t\tsetValue(itemValue);\n\t}\n\n\tfunction close() {\n\t\tconst closing = valueRef.current;\n\t\tsetValue(\"\");\n\t\t// Escape and an outside click both route through this one function (see\n\t\t// NavigationMenuContent's single dismiss layer), so both return focus\n\t\t// to the trigger. Real browsers already send an outside click's own\n\t\t// focus to whatever the user actually clicked, as the *default* action\n\t\t// of the underlying mousedown — which runs after every listener,\n\t\t// including this one — so a click on another focusable element still\n\t\t// lands there; this only \"sticks\" for a click on non-focusable space,\n\t\t// where landing on the trigger beats losing focus to `<body>`. jsdom\n\t\t// does not implement that default action (see the component test\n\t\t// file), so this refocus is unconditionally visible there — it is the\n\t\t// correct outcome for Escape either way.\n\t\t//\n\t\t// It also runs BEFORE the panel is marked inert: the state update is\n\t\t// still queued at this point, so focus is already on the trigger by the\n\t\t// time the exit starts and a keyboard user is never stranded on\n\t\t// `<body>` for the length of the fade.\n\t\tif (closing) {\n\t\t\tgetTriggerElement(closing)?.focus();\n\t\t}\n\t}\n\n\tfunction toggle(itemValue: string) {\n\t\tif (valueRef.current === itemValue) {\n\t\t\tclose();\n\t\t} else {\n\t\t\topen(itemValue);\n\t\t}\n\t}\n\n\tfunction scheduleOpen(itemValue: string) {\n\t\tclearCloseTimer();\n\t\tif (valueRef.current === itemValue) return;\n\t\tif (valueRef.current !== \"\") {\n\t\t\t// Something else is already open: the pointer is travelling along\n\t\t\t// the row it already committed to, not arriving fresh, so switch\n\t\t\t// with no delay. Re-running the open delay here is the flicker\n\t\t\t// every hover-with-intent surface has to avoid past its first item.\n\t\t\topen(itemValue);\n\t\t\treturn;\n\t\t}\n\t\tclearOpenTimer();\n\t\topenTimer.current = setTimeout(() => setValue(itemValue), openDelay);\n\t}\n\n\tfunction scheduleClose() {\n\t\tclearOpenTimer();\n\t\tif (valueRef.current === \"\") return;\n\t\tclearCloseTimer();\n\t\t// No refocus here, unlike `close()` — this fires from the pointer\n\t\t// leaving, and forcing focus onto the trigger would yank it away from\n\t\t// wherever keyboard focus actually is.\n\t\tcloseTimer.current = setTimeout(() => setValue(\"\"), closeDelay);\n\t}\n\n\tfunction cancelClose() {\n\t\tclearCloseTimer();\n\t}\n\n\t// The request side is identity-stable — a trigger only ever calls it from a\n\t// keydown, and nothing depends on it by identity.\n\tconst requestFocus = useCallback((itemValue: string) => {\n\t\tsetPendingFocusValue(itemValue);\n\t}, []);\n\n\t// The consume side is re-created whenever the pending value moves, and that\n\t// identity change IS the subscription: it is listed in\n\t// `NavigationMenuContent`'s panel-focus effect dependencies, so a fresh\n\t// `requestFocus` re-runs that effect the way the source's `$state` read\n\t// re-runs its `$effect`. It is deliberately NOT stable across a pending\n\t// change — a permanently stable identity is what dropped the focus move on\n\t// an already-open panel.\n\t//\n\t// It cannot loop: consuming sets the pending value to `null`, which changes\n\t// the identity once more, and the re-run then finds nothing to consume and\n\t// writes nothing. A consume that matches nothing writes nothing either.\n\tconst consumeFocusRequest = useCallback(\n\t\t(itemValue: string): boolean => {\n\t\t\tif (pendingFocusValue !== itemValue) return false;\n\t\t\tsetPendingFocusValue(null);\n\t\t\treturn true;\n\t\t},\n\t\t[pendingFocusValue]\n\t);\n\n\tfunction collapseIfOpen(itemValue: string) {\n\t\tif (valueRef.current === itemValue) setValue(\"\");\n\t}\n\n\tconst focus = useCallback((itemValue: string) => {\n\t\tsetFocusedValueState(itemValue);\n\t}, []);\n\n\t// Identity-stable for the same reason as `requestFocus`: a trigger calls it\n\t// from its own mount effect and lists it in that effect's dependencies, so\n\t// a fresh function per root render would unregister and re-register\n\t// forever. The updaters also return the array unchanged when there is\n\t// nothing to do, so a duplicate register never schedules a render — the\n\t// React counterpart of the `untrack` the source wraps the body in.\n\tconst registerTrigger = useCallback((itemValue: string): (() => void) => {\n\t\tsetRegisteredOrder((order) => (order.includes(itemValue) ? order : [...order, itemValue]));\n\t\treturn () => {\n\t\t\tsetRegisteredOrder((order) => {\n\t\t\t\tconst i = order.indexOf(itemValue);\n\t\t\t\tif (i === -1) return order;\n\t\t\t\tconst next = order.slice();\n\t\t\t\tnext.splice(i, 1);\n\t\t\t\treturn next;\n\t\t\t});\n\t\t};\n\t}, []);\n\n\tconst setListRef = useCallback((element: HTMLElement | null) => {\n\t\tsetListEl(element);\n\t}, []);\n\n\tfunction goTo(button: HTMLElement) {\n\t\tconst nextValue = button.dataset.value;\n\t\tif (nextValue === undefined) return;\n\t\tsetFocusedValueState(nextValue);\n\t\tbutton.focus();\n\t\t// An already-open panel follows keyboard focus the same way it already\n\t\t// follows the pointer along the row (see `scheduleOpen`) — a panel left\n\t\t// open under a trigger that no longer has focus reads as broken, not as\n\t\t// \"still open\".\n\t\tif (valueRef.current !== \"\" && valueRef.current !== nextValue) {\n\t\t\topen(nextValue);\n\t\t}\n\t}\n\n\tfunction move(from: string, delta: number) {\n\t\tconst buttons = navigableTriggers();\n\t\tif (buttons.length === 0) return;\n\t\tconst fromIndex = buttons.findIndex((el) => el.dataset.value === from);\n\t\tconst base = fromIndex === -1 ? 0 : fromIndex;\n\t\tconst nextIndex = (((base + delta) % buttons.length) + buttons.length) % buttons.length;\n\t\tgoTo(buttons[nextIndex]!);\n\t}\n\n\tfunction moveToEdge(edge: \"first\" | \"last\") {\n\t\tconst buttons = navigableTriggers();\n\t\tif (buttons.length === 0) return;\n\t\tgoTo(edge === \"first\" ? buttons[0]! : buttons[buttons.length - 1]!);\n\t}\n\n\t// Recomputed on every render, so it settles the instant a trigger\n\t// registers, unregisters, or the roving position itself moves.\n\tlet focusedValue: string | null = null;\n\tif (focusedValueState !== null && registeredOrder.includes(focusedValueState)) {\n\t\tfocusedValue = focusedValueState;\n\t} else if (registeredOrder.length === 0) {\n\t\tfocusedValue = null;\n\t} else if (value !== \"\" && registeredOrder.includes(value)) {\n\t\tfocusedValue = value;\n\t} else {\n\t\t// Prefer live DOM order over registration order for the initial\n\t\t// fallback: a trigger that mounts later but renders first on screen\n\t\t// still becomes the first tab stop. A read, never a write — the render\n\t\t// stays pure, exactly as the source's getter is.\n\t\tfocusedValue = navigableTriggers()[0]?.dataset.value ?? registeredOrder[0]!;\n\t}\n\n\t// Rebuilt on every render rather than memoised, and that rebuild is what\n\t// re-renders the pieces below — the React counterpart of the source\n\t// context's live getters. A memo would have to be keyed on every function\n\t// here, each of which closes over `value` and is therefore fresh per\n\t// render anyway, so it would never hit. The three members something depends\n\t// on by identity — `registerTrigger`, `setListRef` and `requestFocus` — are\n\t// stable regardless; `consumeFocusRequest` moves with the pending focus\n\t// value on purpose, see above.\n\tconst context: NavigationMenuContext = {\n\t\tvalue,\n\t\topenDelay,\n\t\tcloseDelay,\n\t\tlistRef: listEl,\n\t\tfocusedValue,\n\t\tsetListRef,\n\t\tgetTriggerElement,\n\t\tregisterTrigger,\n\t\topen,\n\t\tclose,\n\t\ttoggle,\n\t\tscheduleOpen,\n\t\tscheduleClose,\n\t\tcancelClose,\n\t\trequestFocus,\n\t\tconsumeFocusRequest,\n\t\tcollapseIfOpen,\n\t\tfocus,\n\t\tmove,\n\t\tmoveToEdge,\n\t};\n\n\t// A pending hover-intent timer must never fire into an unmounted tree.\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tclearOpenTimer();\n\t\t\tclearCloseTimer();\n\t\t};\n\t}, [clearOpenTimer, clearCloseTimer]);\n\n\treturn (\n\t\t<NAVIGATION_MENU_KEY.Provider value={context}>\n\t\t\t<nav\n\t\t\t\tref={forwardedRef}\n\t\t\t\taria-label={label}\n\t\t\t\tclassName={cn(\"ft-navigation-menu relative\", className)}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</nav>\n\t\t</NAVIGATION_MENU_KEY.Provider>\n\t);\n});\n\nNavigationMenu.displayName = \"NavigationMenu\";\n","type":"registry:ui","target":"@ui/fancy/components/navigation-menu/NavigationMenu.tsx"},{"path":"react/src/components/navigation-menu/NavigationMenuContent.tsx","content":"\"use client\";\n\nimport { forwardRef } from \"react\";\nimport type { FocusEvent, ReactNode } 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 { useComposedRefs } from \"../../internals/dom/use-composed-refs\";\nimport { useElementRef } from \"../../internals/dom/use-element-ref\";\nimport { useIsomorphicLayoutEffect } from \"../../internals/dom/ssr\";\nimport { anchored, originFor } from \"../../internals/motion/anchored\";\nimport { usePresence } from \"../../internals/motion/presence\";\nimport { useNavigationMenuContext, useNavigationMenuItemContext } from \"./types\";\nimport \"./navigation-menu-content.css\";\n\nexport interface NavigationMenuContentProps {\n\t/** The panel's content — typically a feature tile plus a stack of `NavigationMenuLink`s. */\n\tchildren?: ReactNode;\n\t/** Additional CSS classes. */\n\tclassName?: string;\n}\n\nexport const NavigationMenuContent = forwardRef<HTMLDivElement, NavigationMenuContentProps>(\n\tfunction NavigationMenuContent({ children, className }, forwardedRef) {\n\t\tconst item = useNavigationMenuItemContext();\n\t\tconst root = useNavigationMenuContext();\n\n\t\tconst isOpen = root.value === item.value;\n\n\t\t// Convention C-1: the NODE, not a ref. The panel is created by\n\t\t// `presence.mounted`, so a `useRef` + `[]`-deps effect would still be\n\t\t// holding `null` when the dismiss layer and the positioner look for it,\n\t\t// and neither would ever arm — silently.\n\t\tconst [panel, setPanelNode] = useElementRef<HTMLDivElement>();\n\n\t\tconst presence = usePresence(isOpen);\n\n\t\t// The placement as ACTUALLY resolved. Anchored to the whole list rather\n\t\t// than to a single trigger — every panel drops out of the row,\n\t\t// start-aligned to it — and the growth origin follows whatever\n\t\t// `computePosition` settled on: it flips the requested `\"bottom\"` to\n\t\t// `\"top\"` for a nav sitting low in the viewport, and the resolved ALIGN\n\t\t// differs from the requested one whenever clamping slid the panel along\n\t\t// the cross axis, where an entrance grown from the requested corner\n\t\t// would expand from the far corner instead.\n\t\tconst { side: resolvedSide, align: resolvedAlign } = useAnchorPosition(panel, {\n\t\t\tanchor: root.listRef,\n\t\t\tside: \"bottom\",\n\t\t\talign: \"start\",\n\t\t\toffset: 6,\n\t\t});\n\n\t\t// `active: isOpen` disarms the dismiss layer the instant the panel stops\n\t\t// being the open one, so a second Escape during the fade is neither\n\t\t// answered again nor swallowed on its way to whatever sits underneath.\n\t\t//\n\t\t// The trigger is excluded so a pointerdown on it is a toggle rather\n\t\t// than an outside click that closes and immediately reopens.\n\t\tuseDismissable(panel, {\n\t\t\tonDismiss: root.close,\n\t\t\texclude: () => [root.getTriggerElement(item.value)],\n\t\t\tactive: isOpen,\n\t\t});\n\n\t\t// Only Enter/Space/ArrowDown on the trigger ever calls `requestFocus`\n\t\t// (see NavigationMenuTrigger) — a hover- or click-open leaves this a\n\t\t// no-op, and focus stays exactly where it already was.\n\t\t//\n\t\t// The node guard runs BEFORE the consume, where the source checks it\n\t\t// after: `useElementRef` publishes the panel one commit after the\n\t\t// branch mounts, so consuming on the pass where the node is still null\n\t\t// would spend the request on nothing and the focus move would be lost.\n\t\t// Same observable behaviour, one guard reordered.\n\t\t//\n\t\t// `consumeFocusRequest` in the dependency array is the subscription,\n\t\t// not ceremony: the root re-creates it whenever the pending focus value\n\t\t// moves, which is what re-runs this effect for a key press on a trigger\n\t\t// whose panel is ALREADY open — the one case where `isOpen`, `panel`\n\t\t// and `item.value` all stay put.\n\t\tconst { consumeFocusRequest } = root;\n\t\tuseIsomorphicLayoutEffect(() => {\n\t\t\tif (!isOpen) return;\n\t\t\tif (!panel) return;\n\t\t\tif (!consumeFocusRequest(item.value)) return;\n\t\t\tconst target = panel.querySelector<HTMLElement>(\"a[href], button:not([disabled])\") ?? panel;\n\t\t\ttarget.focus();\n\t\t}, [isOpen, panel, consumeFocusRequest, item.value]);\n\n\t\t// The disclosure equivalent of a trigger-side focusout handler: Tab is\n\t\t// never trapped in here (see the README — a modal focus trap would break\n\t\t// \"Tab moves through the panel's links naturally\"), so when focus leaves\n\t\t// this panel on its own, have the panel that no longer has focus close\n\t\t// itself rather than linger, invisible-to-the-eye-but-still-\"open\", off\n\t\t// in the portal.\n\t\tfunction handleFocusOut(event: FocusEvent<HTMLDivElement>) {\n\t\t\tconst next = event.relatedTarget as Node | null;\n\t\t\tif (next && panel?.contains(next)) return;\n\t\t\troot.collapseIfOpen(item.value);\n\t\t}\n\n\t\t// Convention C-2: composed ABOVE the conditional below. Calling this\n\t\t// inside the JSX branch would be a conditional hook and would throw the\n\t\t// first time `mounted` flips.\n\t\t//\n\t\t// ONE bidirectional leg, never a split in/out pair: Svelte reports\n\t\t// `direction: \"both\"` for a bidirectional directive and cannot tell an\n\t\t// arrival from a departure on its own, and a bidirectional leg gets the\n\t\t// in-flight counterpart's position handed to it, so a panel re-opened\n\t\t// inside its own fade continues from where it is instead of snapping.\n\t\t// That matters more here than on a click-only surface: this one closes\n\t\t// on a hover-intent timer, and a pointer that wanders back onto the bar\n\t\t// mid-close is ordinary rather than exceptional.\n\t\tconst panelRef = useComposedRefs(\n\t\t\tsetPanelNode,\n\t\t\tforwardedRef,\n\t\t\tpresence.register(anchored, (entering) => ({ side: resolvedSide, entering }))\n\t\t);\n\n\t\t/*\n\t\t\tNo `role` here on purpose, same reasoning as Popover's content: this\n\t\t\tis a disclosure panel, not a dialog, and it has no title to anchor\n\t\t\t`aria-labelledby` on other than the trigger it already points at.\n\t\t\tReachability comes from `aria-labelledby` + real DOM focus, not from\n\t\t\ta landmark role — and deliberately *not* from a focus trap: a\n\t\t\tNavigationMenu panel is not modal (see the README's \"why not\n\t\t\trole=menu\" section), so Tab must be free to walk out of it into the\n\t\t\trest of the page.\n\n\t\t\tThe motion lives in `internals/motion/anchored.js`, shared with every\n\t\t\tother floating surface. Two deliberate consequences: there are no\n\t\t\tpixels of `translateY` — travel a panel can only fake, since the\n\t\t\tpositioner owns `left`/`top` on this same element — and the rise\n\t\t\tgrows from the panel edge nearest the list rather than from its own\n\t\t\tcentre. Visibility never depends on any of it: `presence.mounted`\n\t\t\tgates the DOM, and under reduced motion the panel simply appears and\n\t\t\tdisappears.\n\n\t\t\t`data-state` is an ordinary React attribute (divergence D-2) carrying\n\t\t\t`surfaceState`'s TWO values — never `\"opening\"` (convention C-5).\n\t\t\t`inert` is not written by hand either: `usePresence` sets it on the\n\t\t\tregistered node for the whole exit, which keeps a panel on its way\n\t\t\tout from taking a click on one of its links.\n\n\t\t\t`data-align` reports the REQUESTED alignment, as the source does,\n\t\t\twhile the growth origin uses the RESOLVED one.\n\n\t\t\tFocus needs nothing here: `NavigationMenu`'s own `close()` refocuses\n\t\t\tthe trigger from a plain function outside the mount gate, so the\n\t\t\treturn lands at the dismiss instant rather than at unmount.\n\n\t\t\t`<Portal>` stays ABOVE the mounted gate, and the gate wraps its\n\t\t\tCHILDREN: `usePortalTarget` resolves its container in a layout\n\t\t\teffect, so a Portal that first mounts in the same commit as the\n\t\t\tsurface renders null on that pass — presence would then find no\n\t\t\tregistered legs and the entrance would be silently skipped.\n\t\t*/\n\t\treturn (\n\t\t\t<Portal>\n\t\t\t\t{presence.mounted ? (\n\t\t\t\t\t<div\n\t\t\t\t\t\tref={panelRef}\n\t\t\t\t\t\tid={item.contentId}\n\t\t\t\t\t\taria-labelledby={item.triggerId}\n\t\t\t\t\t\ttabIndex={-1}\n\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\"ft-navigation-menu-content bg-popover text-popover-foreground border-border grid w-[480px] grid-cols-2 gap-2 rounded-xl border p-3.5 shadow-2xl outline-none\",\n\t\t\t\t\t\t\tclassName\n\t\t\t\t\t\t)}\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\tonPointerEnter={root.cancelClose}\n\t\t\t\t\t\tonPointerLeave={root.scheduleClose}\n\t\t\t\t\t\tonBlur={handleFocusOut}\n\t\t\t\t\t>\n\t\t\t\t\t\t{children}\n\t\t\t\t\t</div>\n\t\t\t\t) : null}\n\t\t\t</Portal>\n\t\t);\n\t}\n);\n\nNavigationMenuContent.displayName = \"NavigationMenuContent\";\n","type":"registry:ui","target":"@ui/fancy/components/navigation-menu/NavigationMenuContent.tsx"},{"path":"react/src/components/navigation-menu/NavigationMenuItem.tsx","content":"\"use client\";\n\nimport { useMemo } from \"react\";\nimport type { ReactNode } from \"react\";\n\nimport { cn } from \"../../utils\";\nimport { useFancyId } from \"../../internals/use-id\";\nimport { NAVIGATION_MENU_ITEM_KEY } from \"./types\";\nimport type { NavigationMenuItemContext } from \"./types\";\n\nexport interface NavigationMenuItemProps {\n\t/** This item's value — what `NavigationMenu`'s `value` becomes while its panel is open. */\n\tvalue: string;\n\t/** A `NavigationMenuTrigger` + `NavigationMenuContent` pair. */\n\tchildren?: ReactNode;\n\t/** Additional CSS classes. */\n\tclassName?: string;\n}\n\n/**\n * One disclosure in the row: its trigger, its panel, and the identity that\n * ties the two together.\n *\n * The source exposes no ref here, so neither does this (PORTING.md's\n * per-component ref rule).\n */\nexport function NavigationMenuItem({ value, children, className }: NavigationMenuItemProps) {\n\t// Stable across SSR and hydration — `uid()` throws on the server, and the\n\t// trigger's `aria-controls` / the panel's `aria-labelledby` need to agree\n\t// with themselves from the first server-rendered paint (convention C-6).\n\tconst baseId = useFancyId();\n\n\t// Memoised on its two scalar inputs, so a re-render that changes neither\n\t// does not re-run the effects the trigger and the panel key on this\n\t// object's members.\n\tconst context = useMemo<NavigationMenuItemContext>(\n\t\t() => ({ value, triggerId: `${baseId}-trigger`, contentId: `${baseId}-content` }),\n\t\t[value, baseId]\n\t);\n\n\treturn (\n\t\t<NAVIGATION_MENU_ITEM_KEY.Provider value={context}>\n\t\t\t<li className={cn(\"ft-navigation-menu-item\", className)}>{children}</li>\n\t\t</NAVIGATION_MENU_ITEM_KEY.Provider>\n\t);\n}\n\nNavigationMenuItem.displayName = \"NavigationMenuItem\";\n","type":"registry:ui","target":"@ui/fancy/components/navigation-menu/NavigationMenuItem.tsx"},{"path":"react/src/components/navigation-menu/NavigationMenuLink.tsx","content":"\"use client\";\n\nimport type { AnchorHTMLAttributes, ReactNode } from \"react\";\n\nimport { cn } from \"../../utils\";\nimport \"./navigation-menu-link.css\";\n\nexport interface NavigationMenuLinkProps extends Omit<\n\tAnchorHTMLAttributes<HTMLAnchorElement>,\n\t\"className\" | \"href\" | \"title\" | \"children\"\n> {\n\t/** Destination URL. */\n\thref: string;\n\t/** Marks this as the current page: sets `aria-current=\"page\"` and the current-row styling. */\n\tcurrent?: boolean;\n\t/** The row's title. Ignored when `children` is given. */\n\ttitle?: string;\n\t/** The row's supporting description, shown under the title. Ignored when `children` is given. */\n\tdescription?: string;\n\t/** Marks the destination as off-site: opens in a new tab with a safe `rel`, and adds an sr-only note. */\n\texternal?: boolean;\n\t/**\n\t * Full override for the row's content — e.g. the mockup's feature tile,\n\t * which pairs an icon with its title on one line. Takes over from\n\t * `title`/`description` entirely when given.\n\t */\n\tchildren?: ReactNode;\n\t/** Additional CSS classes. */\n\tclassName?: string;\n}\n\n/**\n * A row inside a panel. An ordinary anchor, deliberately — never\n * `role=\"menuitem\"`.\n *\n * The source exposes no ref here, so neither does this.\n */\nexport function NavigationMenuLink({\n\thref,\n\tcurrent = false,\n\ttitle,\n\tdescription,\n\texternal = false,\n\tchildren,\n\tclassName,\n\t...rest\n}: NavigationMenuLinkProps) {\n\tconst classes = cn(\n\t\t\"ft-navigation-menu-link flex flex-col gap-0.5 rounded-lg px-2.5 py-2 text-left no-underline transition-colors\",\n\t\t\"hover:bg-accent hover:text-accent-foreground focus-visible:outline-none\",\n\t\tcurrent && \"bg-accent text-accent-foreground\",\n\t\tclassName\n\t);\n\n\treturn (\n\t\t<a\n\t\t\t{...rest}\n\t\t\thref={href}\n\t\t\tclassName={classes}\n\t\t\taria-current={current ? \"page\" : rest[\"aria-current\"]}\n\t\t\ttarget={external ? \"_blank\" : rest.target}\n\t\t\trel={external ? \"noopener noreferrer\" : rest.rel}\n\t\t>\n\t\t\t{children ? (\n\t\t\t\tchildren\n\t\t\t) : (\n\t\t\t\t<>\n\t\t\t\t\t{title ? (\n\t\t\t\t\t\t<span className=\"ft-navigation-menu-link-title text-foreground block text-[12px] font-medium\">\n\t\t\t\t\t\t\t{title}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t) : null}\n\t\t\t\t\t{description ? (\n\t\t\t\t\t\t<span className=\"ft-navigation-menu-link-description text-muted-foreground text-[12px]\">\n\t\t\t\t\t\t\t{description}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t) : null}\n\t\t\t\t</>\n\t\t\t)}\n\t\t\t{external ? <span className=\"sr-only\"> (opens in a new tab)</span> : null}\n\t\t</a>\n\t);\n}\n\nNavigationMenuLink.displayName = \"NavigationMenuLink\";\n","type":"registry:ui","target":"@ui/fancy/components/navigation-menu/NavigationMenuLink.tsx"},{"path":"react/src/components/navigation-menu/NavigationMenuList.tsx","content":"\"use client\";\n\nimport { forwardRef, useEffect } from \"react\";\nimport type { ReactNode } from \"react\";\n\nimport { cn } from \"../../utils\";\nimport { useComposedRefs } from \"../../internals/dom/use-composed-refs\";\nimport { useElementRef } from \"../../internals/dom/use-element-ref\";\nimport { useNavigationMenuContext } from \"./types\";\n\nexport interface NavigationMenuListProps {\n\t/** The `NavigationMenuItem`s (and any plain link that isn't a disclosure). */\n\tchildren?: ReactNode;\n\t/** Additional CSS classes. */\n\tclassName?: string;\n}\n\nexport const NavigationMenuList = forwardRef<HTMLUListElement, NavigationMenuListProps>(\n\tfunction NavigationMenuList({ children, className }, forwardedRef) {\n\t\tconst { setListRef } = useNavigationMenuContext();\n\n\t\t// Convention C-1: the NODE, not a ref. The root keys work on this\n\t\t// element's existence — a panel anchors against it and the trigger row\n\t\t// is queried from it — so it has to publish itself the moment it\n\t\t// appears, not one `[]`-deps effect too late.\n\t\tconst [node, setNode] = useElementRef<HTMLUListElement>();\n\n\t\t// Convention C-2: composed at the top of the body, never inside the JSX.\n\t\tconst listRef = useComposedRefs(setNode, forwardedRef);\n\n\t\t// This is the element every panel anchors against (start-aligned to the\n\t\t// whole row, not to whichever trigger opened it — see\n\t\t// NavigationMenuContent) and the element trigger buttons are queried\n\t\t// from for roving-tabindex order. Handing the root a plain element\n\t\t// reference, not a render prop, keeps `NavigationMenuTrigger` /\n\t\t// `NavigationMenuContent` unaware that a `NavigationMenuList` even\n\t\t// exists.\n\t\tuseEffect(() => {\n\t\t\tsetListRef(node);\n\t\t\treturn () => setListRef(null);\n\t\t}, [setListRef, node]);\n\n\t\treturn (\n\t\t\t<ul\n\t\t\t\tref={listRef}\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"ft-navigation-menu-list m-0 flex list-none items-center gap-1 p-0\",\n\t\t\t\t\tclassName\n\t\t\t\t)}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t</ul>\n\t\t);\n\t}\n);\n\nNavigationMenuList.displayName = \"NavigationMenuList\";\n","type":"registry:ui","target":"@ui/fancy/components/navigation-menu/NavigationMenuList.tsx"},{"path":"react/src/components/navigation-menu/NavigationMenuTrigger.tsx","content":"\"use client\";\n\nimport { forwardRef, useEffect, useRef } from \"react\";\nimport type {\n\tComponentPropsWithoutRef,\n\tKeyboardEvent,\n\tMouseEvent,\n\tPointerEvent,\n\tReactNode,\n} from \"react\";\n\nimport { cn } from \"../../utils\";\nimport { useComposedRefs } from \"../../internals/dom/use-composed-refs\";\nimport { useNavigationMenuContext, useNavigationMenuItemContext } from \"./types\";\nimport \"./navigation-menu-trigger.css\";\n\ninterface NavigationMenuTriggerOwnProps {\n\t/** The trigger's label. */\n\tchildren?: ReactNode;\n\t/** Additional CSS classes. */\n\tclassName?: string;\n}\n\n/**\n * Native `<button>` attributes and listeners are forwarded to the trigger; the managed\n * ARIA, state and structural attributes stay the component's. A forwarded `disabled`\n * leaves the roving order and makes the trigger inert.\n */\nexport interface NavigationMenuTriggerProps\n\textends\n\t\tOmit<ComponentPropsWithoutRef<\"button\">, keyof NavigationMenuTriggerOwnProps>,\n\t\tNavigationMenuTriggerOwnProps {}\n\nexport const NavigationMenuTrigger = forwardRef<HTMLButtonElement, NavigationMenuTriggerProps>(\n\tfunction NavigationMenuTrigger(\n\t\t{ children, className, onClick, onPointerEnter, onPointerLeave, onKeyDown, ...rest },\n\t\tforwardedRef\n\t) {\n\t\tconst item = useNavigationMenuItemContext();\n\t\tconst root = useNavigationMenuContext();\n\n\t\t// A plain ref rather than `useElementRef`: the button is never\n\t\t// conditionally rendered — convention C-1's hazard — and the node is\n\t\t// only ever read from inside a click handler, by which time it is long\n\t\t// since attached. A ref keeps the handlers below free of an extra\n\t\t// render at mount.\n\t\tconst buttonRef = useRef<HTMLButtonElement | null>(null);\n\t\t// Convention C-2: composed at the top of the body.\n\t\tconst setButtonRef = useComposedRefs(buttonRef, forwardedRef);\n\n\t\tconst isOpen = root.value === item.value;\n\t\tconst tabIndexAttr = root.focusedValue === item.value ? 0 : -1;\n\n\t\t// A forwarded native `disabled` keeps the trigger out of the roving\n\t\t// order (a disabled button cannot take focus, so it must never hold the\n\t\t// tab stop) and makes it inert: none of its handlers open, move or call\n\t\t// the consumer's listener.\n\t\tconst disabled = Boolean(rest.disabled);\n\n\t\tconst { registerTrigger } = root;\n\t\tuseEffect(() => {\n\t\t\tif (disabled) return;\n\t\t\treturn registerTrigger(item.value);\n\t\t}, [registerTrigger, item.value, disabled]);\n\n\t\t// Click is immediate, no hover-intent delay — an explicit click is\n\t\t// already the user's decision, there is no \"did they mean it\" travel to\n\t\t// protect against the way there is for a pointer merely passing over\n\t\t// the row.\n\t\tfunction handleClick(event: MouseEvent<HTMLButtonElement>) {\n\t\t\tif (disabled) return;\n\t\t\troot.toggle(item.value);\n\t\t\troot.focus(item.value);\n\t\t\t// Deliberate, not incidental, the same reasoning as ToggleGroupItem:\n\t\t\t// a plain `<button>` is only guaranteed to take focus on click in\n\t\t\t// some browsers (macOS Safari notably does not, by default).\n\t\t\t// Without this, clicking a trigger would open its panel but leave\n\t\t\t// the roving tab stop — and DOM focus — wherever it last was.\n\t\t\tbuttonRef.current?.focus();\n\t\t\tonClick?.(event);\n\t\t}\n\n\t\tfunction handlePointerEnter(event: PointerEvent<HTMLButtonElement>) {\n\t\t\tif (disabled) return;\n\t\t\troot.scheduleOpen(item.value);\n\t\t\tonPointerEnter?.(event);\n\t\t}\n\n\t\tfunction handlePointerLeave(event: PointerEvent<HTMLButtonElement>) {\n\t\t\troot.scheduleClose();\n\t\t\t// Leaving still schedules the close (the trigger can turn disabled\n\t\t\t// while its panel is open); only the consumer listener is guarded.\n\t\t\tif (disabled) return;\n\t\t\tonPointerLeave?.(event);\n\t\t}\n\n\t\t// There is deliberately no focus handler opening the panel here. A\n\t\t// keyboard user tabbing *past* a trigger on their way elsewhere must\n\t\t// not open it — only Enter/Space/ArrowDown, an explicit request, does\n\t\t// that. It also happens to be exactly what keeps Escape well-behaved:\n\t\t// closing returns focus to this trigger (see `close()`), and if focus\n\t\t// opened the panel, that same programmatic refocus would reopen it\n\t\t// immediately — the pointer-and-keyboard fight the panel's own test\n\t\t// file pins down.\n\t\tfunction handleKeydown(event: KeyboardEvent<HTMLButtonElement>) {\n\t\t\tif (disabled) return;\n\t\t\thandleNavigationKey(event);\n\t\t\tonKeyDown?.(event);\n\t\t}\n\n\t\tfunction handleNavigationKey(event: KeyboardEvent<HTMLButtonElement>) {\n\t\t\tswitch (event.key) {\n\t\t\t\tcase \"Enter\":\n\t\t\t\tcase \" \":\n\t\t\t\tcase \"ArrowDown\":\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\troot.open(item.value);\n\t\t\t\t\troot.focus(item.value);\n\t\t\t\t\troot.requestFocus(item.value);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"ArrowRight\":\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\troot.move(item.value, 1);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"ArrowLeft\":\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\troot.move(item.value, -1);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"Home\":\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\troot.moveToEdge(\"first\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"End\":\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\troot.moveToEdge(\"last\");\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tconst classes = cn(\n\t\t\t\"ft-navigation-menu-trigger text-muted-foreground inline-flex cursor-pointer items-center gap-1 rounded-[8px] px-[14px] py-[8px] text-[13px] font-medium transition-colors\",\n\t\t\t\"hover:bg-accent hover:text-accent-foreground focus-visible:outline-none\",\n\t\t\tisOpen && \"bg-accent text-accent-foreground\",\n\t\t\tclassName\n\t\t);\n\n\t\treturn (\n\t\t\t<button\n\t\t\t\t{...rest}\n\t\t\t\tref={setButtonRef}\n\t\t\t\ttype=\"button\"\n\t\t\t\tid={item.triggerId}\n\t\t\t\tdata-ft-nav-trigger=\"\"\n\t\t\t\tdata-value={item.value}\n\t\t\t\tclassName={classes}\n\t\t\t\taria-expanded={isOpen}\n\t\t\t\taria-controls={isOpen ? item.contentId : undefined}\n\t\t\t\ttabIndex={tabIndexAttr}\n\t\t\t\tonClick={handleClick}\n\t\t\t\tonPointerEnter={handlePointerEnter}\n\t\t\t\tonPointerLeave={handlePointerLeave}\n\t\t\t\tonKeyDown={handleKeydown}\n\t\t\t>\n\t\t\t\t{children}\n\t\t\t\t<span className=\"ft-navigation-menu-caret\" aria-hidden=\"true\">\n\t\t\t\t\t▾\n\t\t\t\t</span>\n\t\t\t</button>\n\t\t);\n\t}\n);\n\nNavigationMenuTrigger.displayName = \"NavigationMenuTrigger\";\n","type":"registry:ui","target":"@ui/fancy/components/navigation-menu/NavigationMenuTrigger.tsx"},{"path":"react/src/components/navigation-menu/index.ts","content":"\"use client\";\n\nexport { NavigationMenu } from \"./NavigationMenu\";\nexport type { NavigationMenuProps } from \"./NavigationMenu\";\nexport { NavigationMenuList } from \"./NavigationMenuList\";\nexport type { NavigationMenuListProps } from \"./NavigationMenuList\";\nexport { NavigationMenuItem } from \"./NavigationMenuItem\";\nexport type { NavigationMenuItemProps } from \"./NavigationMenuItem\";\nexport { NavigationMenuTrigger } from \"./NavigationMenuTrigger\";\nexport type { NavigationMenuTriggerProps } from \"./NavigationMenuTrigger\";\nexport { NavigationMenuContent } from \"./NavigationMenuContent\";\nexport type { NavigationMenuContentProps } from \"./NavigationMenuContent\";\nexport { NavigationMenuLink } from \"./NavigationMenuLink\";\nexport type { NavigationMenuLinkProps } from \"./NavigationMenuLink\";\nexport { NAVIGATION_MENU_KEY, NAVIGATION_MENU_ITEM_KEY } from \"./types\";\nexport type { NavigationMenuContext, NavigationMenuItemContext } from \"./types\";\n","type":"registry:ui","target":"@ui/fancy/components/navigation-menu/index.ts"},{"path":"react/src/components/navigation-menu/navigation-menu-content.css","content":"/*\n * `--ft-nav-accent` lives here, not on `NavigationMenu`'s own `<nav>`: the\n * panel is rendered into `document.body` by the portal, which severs the DOM\n * ancestry a CSS custom property would otherwise inherit through — a\n * declaration up on `<nav>` would simply never reach anything rendered in\n * here. This is the one read/declare site for the whole compound; a\n * `NavigationMenuLink` used as the mockup's feature tile\n * (`className=\"ft-navigation-menu-feature\"`, see that component's own CSS)\n * reads it back through ordinary inheritance, since it always renders as a\n * descendant of this element even inside the portal.\n */\n.ft-navigation-menu-content {\n\t--ft-nav-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","type":"registry:ui","target":"@ui/fancy/components/navigation-menu/navigation-menu-content.css"},{"path":"react/src/components/navigation-menu/navigation-menu-link.css","content":"/*\n * Optional utility for the mockup's feature tile — a `NavigationMenuLink`\n * rendered as the panel's highlighted first column. Not a boolean prop: the\n * mockup only ever shows one per panel, so a prop would exist for a single\n * call site, where `className=\"ft-navigation-menu-feature\"` composes with\n * everything else the `className` prop already merges in. Reads\n * `--ft-nav-accent`, which `NavigationMenuContent` declares — this class only\n * ever makes visual sense on a link rendered inside a content panel, so the\n * property is always present by the time it's read here.\n *\n * The source's `<style>` block was compiler-scoped to this component's own\n * markup, where the class could only ever land on the root anchor. Plain CSS\n * is global, so the port anchors the rule on that root class as well — the\n * same match, without a generic name leaking into every consumer's page.\n */\n.ft-navigation-menu-link.ft-navigation-menu-feature {\n\tbackground: linear-gradient(\n\t\t135deg,\n\t\tcolor-mix(in oklch, var(--ft-nav-accent) 12%, transparent),\n\t\tcolor-mix(in oklch, oklch(0.8001 0.1345 225.49) 6%, transparent)\n\t);\n}\n","type":"registry:ui","target":"@ui/fancy/components/navigation-menu/navigation-menu-link.css"},{"path":"react/src/components/navigation-menu/navigation-menu-trigger.css","content":"/*\n * Reduced motion keeps the whole rotation, not just its transition, out of\n * the base rule set — the caret is decorative (open/closed state is already\n * carried by `aria-expanded`), so under reduced motion it simply stays put\n * rather than only losing the animated easing between states.\n *\n * The source's `<style>` block was compiler-scoped to this component's own\n * markup, where `.ft-navigation-menu-caret` could only ever match the caret\n * inside a trigger. Plain CSS is global, so the descendant selector is\n * written out: it is the same match, and it keeps a generic class name from\n * leaking into every consumer's page.\n */\n@media (prefers-reduced-motion: no-preference) {\n\t.ft-navigation-menu-trigger .ft-navigation-menu-caret {\n\t\tdisplay: inline-block;\n\t\ttransition: transform 150ms ease;\n\t}\n\n\t.ft-navigation-menu-trigger[aria-expanded=\"true\"] .ft-navigation-menu-caret {\n\t\ttransform: rotate(180deg);\n\t}\n}\n","type":"registry:ui","target":"@ui/fancy/components/navigation-menu/navigation-menu-trigger.css"},{"path":"react/src/components/navigation-menu/types.ts","content":"\"use client\";\n\n/**\n * The contract between the `NavigationMenu` root and the compound pieces\n * nested inside it (`NavigationMenuList` down to `NavigationMenuLink`).\n *\n * Two separate contexts, not one. The root context below is shared by every\n * descendant and owns the single source of truth: which item (if any) is\n * open, the hover-intent timers, and the roving-tabindex position across the\n * trigger row. The item context is re-provided by each `NavigationMenuItem`\n * and only ever holds that one item's own identity — its `value` and the id\n * pair that links its trigger to its panel via `aria-controls` /\n * `aria-labelledby`. A `NavigationMenuTrigger` or `NavigationMenuContent`\n * reads both: the item context to know who it is, the root context to know\n * (and change) what is open.\n *\n * The source publishes both contracts under a `unique symbol` context key;\n * React's own context object plays that role here, so the exported names are\n * kept and the values are `React.Context` objects rather than symbols:\n *\n * ```tsx\n * const root = useContext(NAVIGATION_MENU_KEY);\n * ```\n */\n\nimport { createContext, useContext } from \"react\";\n\n/** What the root publishes. Every compound piece reads it; only the root writes it. */\nexport interface NavigationMenuContext {\n\t/** The currently open item's value, or `\"\"` when every panel is closed. */\n\treadonly value: string;\n\t/** Delay in ms before a hovered trigger opens its panel. */\n\treadonly openDelay: number;\n\t/** Delay in ms before a panel closes after the pointer leaves it and its trigger. */\n\treadonly closeDelay: number;\n\t/** The trigger row's element, once mounted. Every panel anchors against this, not against its own trigger. */\n\treadonly listRef: HTMLElement | null;\n\t/** The value that currently carries `tabindex=\"0\"` on its trigger, or `null` before anything has registered. */\n\treadonly focusedValue: string | null;\n\t/** Registers the trigger row element that panels anchor against. */\n\tsetListRef(element: HTMLElement | null): void;\n\t/** Looks up a trigger's own button element by its item value, once registered. */\n\tgetTriggerElement(itemValue: string): HTMLElement | null;\n\t/** Joins the roving-focus order. Returns the matching unregister function; call it on destroy. */\n\tregisterTrigger(itemValue: string): () => void;\n\t/** Opens `itemValue` immediately, closing whatever else was open. */\n\topen(itemValue: string): void;\n\t/** Closes the open panel immediately and returns focus to its trigger. */\n\tclose(): void;\n\t/** Opens `itemValue` if it isn't already open, otherwise closes it. Used by a trigger's click. */\n\ttoggle(itemValue: string): void;\n\t/**\n\t * Pointer-intent open: starts the open delay, unless something else is\n\t * already open, in which case it switches immediately with no delay — the\n\t * pointer is moving along an already-open row, not arriving fresh.\n\t */\n\tscheduleOpen(itemValue: string): void;\n\t/** Pointer-intent close: starts the close delay, giving the pointer time to travel into the panel. */\n\tscheduleClose(): void;\n\t/** Cancels a pending close — the pointer re-entered the trigger or the panel before the delay elapsed. */\n\tcancelClose(): void;\n\t/** Marks `itemValue` as owed a focus-into-panel move the next time its panel mounts. */\n\trequestFocus(itemValue: string): void;\n\t/**\n\t * Consumes the focus request for `itemValue`, if there is one. Returns\n\t * whether there was one.\n\t *\n\t * Its identity changes whenever the pending request changes, which is how a\n\t * panel effect that lists it as a dependency learns a new request arrived —\n\t * the React stand-in for the source reading a `$state` inside its `$effect`.\n\t */\n\tconsumeFocusRequest(itemValue: string): boolean;\n\t/** Closes `itemValue`'s panel without moving focus — used when focus tabs out of it on its own. */\n\tcollapseIfOpen(itemValue: string): void;\n\t/** Marks `itemValue` as the roving-focus position, without moving DOM focus. */\n\tfocus(itemValue: string): void;\n\t/**\n\t * Moves the roving-focus position, and DOM focus, `delta` steps from\n\t * `from` in DOM order, wrapping at both ends. If a panel is already open\n\t * it follows the new focus immediately — the same \"no re-delay\" rule\n\t * `scheduleOpen` applies to the pointer, applied to the keyboard too.\n\t */\n\tmove(from: string, delta: number): void;\n\t/** Moves the roving-focus position, and DOM focus, to the first or last trigger in DOM order. */\n\tmoveToEdge(edge: \"first\" | \"last\"): void;\n}\n\nexport const NAVIGATION_MENU_KEY = createContext<NavigationMenuContext | undefined>(undefined);\nNAVIGATION_MENU_KEY.displayName = \"NavigationMenuContext\";\n\n/** What a `NavigationMenuItem` publishes to its own `NavigationMenuTrigger` and `NavigationMenuContent`. */\nexport interface NavigationMenuItemContext {\n\t/** This item's own value, as given to `NavigationMenuItem`. */\n\treadonly value: string;\n\t/** The trigger button's id — what the panel's `aria-labelledby` points at. */\n\treadonly triggerId: string;\n\t/** The panel's id — what the trigger's `aria-controls` points at while its panel exists. */\n\treadonly contentId: string;\n}\n\nexport const NAVIGATION_MENU_ITEM_KEY = createContext<NavigationMenuItemContext | undefined>(\n\tundefined\n);\nNAVIGATION_MENU_ITEM_KEY.displayName = \"NavigationMenuItemContext\";\n\n/**\n * Reads the enclosing `NavigationMenu`'s root context.\n *\n * Every compound piece below the root is only ever rendered inside it — the\n * same assumption the source's own `getContext` calls make, which throw\n * nothing and simply hand back `undefined` — so a missing provider is a\n * wiring mistake worth a named error rather than a shape to degrade into.\n * Internal: not exported from `index.ts`.\n */\nexport function useNavigationMenuContext(): NavigationMenuContext {\n\tconst value = useContext(NAVIGATION_MENU_KEY);\n\tif (value === undefined) {\n\t\tthrow new Error(\n\t\t\t\"NavigationMenuContext is missing: this component must be rendered inside a <NavigationMenu>.\"\n\t\t);\n\t}\n\treturn value;\n}\n\n/**\n * Reads the enclosing `NavigationMenuItem`'s context. Internal: not exported\n * from `index.ts`.\n */\nexport function useNavigationMenuItemContext(): NavigationMenuItemContext {\n\tconst value = useContext(NAVIGATION_MENU_ITEM_KEY);\n\tif (value === undefined) {\n\t\tthrow new Error(\n\t\t\t\"NavigationMenuItemContext is missing: this component must be rendered inside a <NavigationMenuItem>.\"\n\t\t);\n\t}\n\treturn value;\n}\n","type":"registry:ui","target":"@ui/fancy/components/navigation-menu/types.ts"}],"meta":{"license":"MIT","package":"fancy-ui-react","version":"0.7.0","source":"https://fancy-ui.rama.app/docs/components/navigation-menu?framework=react"}}