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 { UseInteractionsReturn } from "../internals/floating-ui/hooks/use-interactions"; import type { MutableRef } from "../internals/floating-ui/types"; import type { TransitionStatus } from "../internals/use-transition-status/use-transition-status"; import type { PopoverStore } from "./store/popover-store"; /** * Change-reason union for `Popover.Root`. Mirrors Base UI React's * `PopoverRootChangeEventReason`. The Solid port omits reasons that depend on * behaviors not yet ported (hover, focus-based open, imperative-action via * `actionsRef`); those are listed as a comment on ``. */ export type PopoverRootChangeEventReason = | typeof REASONS.triggerPress | typeof REASONS.outsidePress | typeof REASONS.escapeKey | typeof REASONS.closePress | typeof REASONS.focusOut | typeof REASONS.imperativeAction | typeof REASONS.none; export type PopoverRootChangeEventDetails = BaseUIChangeEventDetails; /** * Modal surface kind. Mirrors Base UI React's `modal` prop union. `true` * traps focus AND locks scroll / blocks outside pointer input via the * internal backdrop; `'trap-focus'` traps focus only; `false` does neither. */ export type PopoverModalKind = boolean | "trap-focus"; /** * Shared state / handles surfaced through the Popover root context. Every * reactive field is exposed as an `Accessor` so consumers don't need to * guard reactive reads. */ export interface PopoverRootContextValue { /** Reactive open state (true once the popover is requested open). */ open: Accessor; /** Whether the popover DOM is currently mounted (open OR animating out). */ mounted: Accessor; /** Enter/exit transition phase, or undefined when settled. */ transitionStatus: Accessor; /** * Modal surface kind. See {@link PopoverModalKind}. */ modal: Accessor; /** * The title element id, if any `` is mounted. Flows into * the popup's `aria-labelledby`. Seeded with `""` at mount so Solid's * JSX spread can subscribe to the key. */ titleId: Accessor; setTitleId: (id: string | undefined) => void; /** * The description element id, if any `` is mounted. * Flows into the popup's `aria-describedby`. Same `""`-seed rule as * `titleId`. */ descriptionId: Accessor; setDescriptionId: (id: string | undefined) => void; /** * The shared `FloatingRootStore`. Positioner + popup read reactive * signals off of it (open, reference/floating elements, etc). */ floatingRootContext: FloatingRootStore; /** * Prop-getter bundle produced by `useInteractions` on the root. Trigger, * Popup, and other parts feed these into their prop-merge pipelines. */ interactions: UseInteractionsReturn; /** * Imperative setOpen, invoked by the Close button and by interaction * hooks (dismiss, role) via `floatingRootContext.setOpen`. Respects * `details.cancel()` for the uncontrolled branch. */ setOpen: (next: boolean, details: PopoverRootChangeEventDetails) => void; /** * Ref slot for the popup element. The popup's ref callback writes * `.current` and the positioner uses it for the close-animation * synchronization (mirrors React's `store.context.popupRef`). */ popupRef: MutableRef; /** * Ref slot for the active trigger element. Matches React's * `activeTriggerElement` — positioner reads it to anchor the floating * element; popup reads it to restore focus on close. */ triggerElement: Accessor; setTriggerElement: (element: HTMLElement | null) => void; /** * The `PopoverStore` instance owned by this root. Triggers register * into its `context.triggerElements` map; the handle (if any) also * points to this store. */ store: PopoverStore; /** * Accessor for the currently-selected trigger id. Set by the * implicit-active-trigger hook, by an explicit `triggerId` prop on * the root, or by `` when it becomes active. */ activeTriggerId: Accessor; setActiveTriggerId: (id: string | null) => void; /** * Accessor for the current payload (if any). Set by the active * trigger via its `payload` prop; read by * `` children render-function. */ payload: Accessor; setPayload: (payload: unknown) => void; } /** * Context for the Popover compound components. `null` is the default so * `usePopoverRootContext(true)` can be called from above a provider (e.g. * the `` wrapper's `FloatingTree` gate) without triggering * Solid 2's `ContextNotFoundError`. */ export const PopoverRootContext = createContext( null, ); /** * Read the current `` context. Throws unless `optional` is * `true`. Matches Base UI React's `usePopoverRootContext` overload shape. */ export function usePopoverRootContext(): PopoverRootContextValue; export function usePopoverRootContext( optional: true, ): PopoverRootContextValue | null; export function usePopoverRootContext( optional?: boolean, ): PopoverRootContextValue | null { const context = useContext(PopoverRootContext); if (context == null && !optional) { throw new Error( "Base UI: PopoverRootContext is missing. Popover parts must be placed within .", ); } return context; }