import type { JSX } from "@solidjs/web"; /** * Context plumbing for `` → descendants. Mirrors * `packages/react/src/menu/root/MenuRootContext.ts` but threads reactive * accessors (Solid-native) instead of the React source's store + selector pair. * * `createContext(null)` + `== null` sentinel — menu * nests recursively via `` so parents must tolerate reads * from above a provider. */ import { type Accessor, createContext, useContext } from "solid-js"; import type { BaseUIChangeEventDetails, REASONS, } from "../../internals/change-event-details"; import type { FloatingRootStore } from "../../internals/floating-ui/components/floating-root-store"; import type { FloatingTreeStore } from "../../internals/floating-ui/components/floating-tree-store"; import type { UseInteractionsReturn } from "../../internals/floating-ui/hooks/use-interactions"; import type { MutableRef } from "../../internals/floating-ui/types"; import type { PropsLike } from "../../internals/merge-props-n"; import type { InteractionType } from "../../internals/use-enhanced-click-handler"; import type { TransitionStatus } from "../../internals/use-transition-status/use-transition-status"; import type { MenuStore } from "../store/menu-store"; /** * Change-reason union for `Menu.Root`. Mirrors React 1:1. */ export type MenuRootChangeEventReason = | typeof REASONS.triggerHover | typeof REASONS.triggerFocus | typeof REASONS.triggerPress | typeof REASONS.outsidePress | typeof REASONS.focusOut | typeof REASONS.listNavigation | typeof REASONS.escapeKey | typeof REASONS.itemPress | typeof REASONS.closePress | typeof REASONS.siblingOpen | typeof REASONS.cancelOpen | typeof REASONS.imperativeAction | typeof REASONS.none; export type MenuRootChangeEventDetails = BaseUIChangeEventDetails & { /** Keeps the popup mounted across a close for exit animations. */ preventUnmountOnClose?: () => void; }; export type MenuRootOrientation = "horizontal" | "vertical"; export type MenuInstantType = | "dismiss" | "click" | "group" | "trigger-change" | undefined; export interface MenuRootContextValue { /** Whether this root is nested inside another menu (i.e. a submenu). */ nested: Accessor; /** Parent menu context if nested, else null. */ parent: Accessor | null>; /** Reactive open state. */ open: Accessor; /** Whether the popup subtree is currently mounted (open OR animating out). */ mounted: Accessor; /** Enter/exit transition phase, or undefined when settled. */ transitionStatus: Accessor; /** Whether the menu is in modal mode (top-level only). */ modal: Accessor; /** Whether the menu is disabled. */ disabled: Accessor; /** Visual/keyboard orientation. @default "vertical" */ orientation: Accessor; /** Whether arrow keys loop focus at the ends. */ loopFocus: Accessor; /** Whether hovering items highlights them (drives `data-highlighted`). */ highlightItemOnHover: Accessor; /** Highlighted item index for keyboard navigation. */ activeIndex: Accessor; setActiveIndex: (index: number | null) => void; /** The current payload attached to the open trigger (when using `handle`). */ payload: Accessor; setPayload: (payload: Payload | undefined) => void; /** The trigger element that opened the menu. */ activeTriggerElement: Accessor; setActiveTriggerElement: (el: HTMLElement | null) => void; /** The trigger id that opened the menu (for imperative workflows). */ activeTriggerId: Accessor; setActiveTriggerId: (id: string | null) => void; /** Last open-change reason, useful for derived styling. */ lastOpenChangeReason: Accessor; /** Instant transition hint for skipping entry animations. */ instantType: Accessor; /** Setters. */ setOpen: (next: boolean, details: MenuRootChangeEventDetails) => void; /** Floating-UI root context for interaction hooks + positioning. */ floatingRootContext: FloatingRootStore; /** Interaction prop-getter bundle (click + dismiss + listnav + typeahead). */ interactions: UseInteractionsReturn; /** Parallel listRef + labelsRef used by listnav / typeahead. */ listRef: MutableRef>; labelsRef: MutableRef>; /** Whether typeahead is currently active (suppresses space → click). */ typingRef: MutableRef; /** * Delay (ms) before this menu closes when a sibling item of its trigger is * hovered. Set by ``'s `closeDelay`. */ closeDelayRef: MutableRef; /** Popup element ref (filled by ``). */ popupRef: MutableRef; /** Positioner element ref (filled by ``). */ positionerRef: MutableRef; /** Callback fired after open/close transitions settle. */ onOpenChangeComplete?: ((open: boolean) => void) | undefined; /** Optional floating-tree node id for this root. */ floatingNodeId: Accessor; /** Whether `closeParentOnEsc` is enabled for submenu escape bubbling. */ closeParentOnEsc: Accessor; /** * Context-menu root id when this menu is an inner menu of * ``. Threaded onto the popup as `data-rootownerid` * so nested-menu mouseups can be attributed back to their owning root. */ rootId: Accessor; /** * Parent type — mirrors React's `parent.type`. `undefined` for a * top-level menu, `"menu"` for a submenu, `"menubar"` when mounted * inside ``, `"context-menu"` when mounted inside * ``, `"nested-context-menu"` when the parent is * itself an inner context-menu. */ parentType: Accessor< "menu" | "menubar" | "context-menu" | "nested-context-menu" | undefined >; /** * Underlying `MenuStore` instance. Carries the trigger registry + * imperative setOpen slot. When the consumer passes a `handle` prop to * ``, the handle's store is adopted here. */ store: MenuStore; /** * Interaction type that opened the menu, or `null` when closed. Mirrors * React's `openMethod` state from `useOpenInteractionType`. */ openMethod: Accessor; /** * Extra props emitted by `useOpenInteractionType` that the trigger must * spread to capture the interaction type on open. */ openInteractionTypeProps: PropsLike; /** * Focus-guard refs used by ``'s `useTriggerFocusGuards` * integration. */ beforeContentFocusGuardRef: MutableRef; triggerFocusTargetRef: MutableRef; /** * The floating tree store (if any). Submenus read through to their * root to emit/subscribe cross-menu events. */ floatingTreeRoot: Accessor; /** Parent floating-tree node id (`null` for top-level roots). */ floatingParentNodeId: Accessor; } /** Wraps a payload child-render function. */ export type MenuPayloadChildRenderFunction = (details: { payload: Payload | undefined; }) => JSX.Element; export const MenuRootContext = createContext(null); export function useMenuRootContext(): MenuRootContextValue; export function useMenuRootContext(optional: true): MenuRootContextValue | null; export function useMenuRootContext( optional?: boolean, ): MenuRootContextValue | null { const ctx = useContext(MenuRootContext); if (ctx == null && !optional) { throw new Error( "Base UI: MenuRootContext is missing. Menu parts must be placed within .", ); } return ctx; }