/** * Context plumbing for `` → `` and * ``. Mirrors React's * `packages/react/src/context-menu/root/ContextMenuRootContext.ts`. * * The ContextMenu compound is a thin wrapper around ``: it * captures a `(clientX, clientY)` point on `contextmenu` (or long press), * threads a virtual anchor into the inner menu's floating-root context, * and calls `setOpen(true, …)` via an `actionsRef` populated by * `` once it detects the outer context-menu. * * Null-sentinel context (solidify P5) — `` * requires the provider, but `` optionally reads it to detect * the context-menu interop path and tolerates `null`. */ import { type Accessor, createContext, useContext } from "solid-js"; import type { MutableRef } from "../../internals/floating-ui/types"; import type { MenuRootChangeEventDetails } from "../../menu/root/menu-root-context"; /** * A virtual anchor element Floating UI can position against. Mirrors React's * `ContextMenuRootContext['anchor']` shape. */ export interface ContextMenuAnchor { getBoundingClientRect: () => DOMRect; contextElement?: Element; } export interface ContextMenuRootContextValue { /** Current anchor element (virtual or real — usually virtual). */ anchor: Accessor; /** Setter for the anchor — called by `` on contextmenu/long-press. */ setAnchor: (anchor: ContextMenuAnchor) => void; /** * Populated by `` once it detects the context-menu parent. * The trigger calls `setOpen` to open the menu after updating the anchor. */ actionsRef: MutableRef<{ setOpen: ( nextOpen: boolean, eventDetails: MenuRootChangeEventDetails, ) => void; } | null>; /** Populated by `` from the inner positioner element. */ positionerRef: MutableRef; /** Populated when `` mounts. */ backdropRef: MutableRef; /** Populated when an internal (modal-gating) backdrop mounts. */ internalBackdropRef: MutableRef; /** * Whether the trigger's pending mouseup can still open the menu * (long-press window). Shared with `` for outside-press dismissal. */ allowMouseUpTriggerRef: MutableRef; /** Point captured at contextmenu — retained across the open for inset positioning. */ initialCursorPointRef: MutableRef<{ x: number; y: number } | null>; /** Unique id for this context-menu root. Used by menu popup to tag `data-rootownerid`. */ rootId: string | undefined; /** * Whether this `` is itself nested inside another * ``. Matches React's `'nested-context-menu'` * parent-type gate (inner menu's `` reads this to route * dismissal / open-behavior like a regular submenu). */ nestedInsideContextMenu: boolean; } export const ContextMenuRootContext = createContext(null); export function useContextMenuRootContext(): ContextMenuRootContextValue; export function useContextMenuRootContext( optional: true, ): ContextMenuRootContextValue | null; export function useContextMenuRootContext( optional?: boolean, ): ContextMenuRootContextValue | null { const ctx = useContext(ContextMenuRootContext); if (ctx == null && !optional) { throw new Error( "Base UI: ContextMenuRootContext is missing. ContextMenu parts must be placed within .", ); } return ctx; }