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;
}