import type { JSX } from "@solidjs/web"; import { type Accessor, createContext, createMemo, useContext } from "solid-js"; import { createPopupCore } from "../internals/create-popup-core"; import { type MaybeAccessor, readOption } from "../internals/maybe-accessor"; import { SNAPSHOT_KEYS, type Snapshottable, snapshotKeys, } from "../internals/observability/snapshot"; /** * Why the preview card's open state changed. * * - `"trigger-hover"` — pointer entered / left the trigger. * - `"trigger-focus"` — keyboard focus moved to / from the trigger. * - `"escape-key"` — Escape dismissed the card. * - `"outside-press"` — pointer pressed outside. * - `"imperative"` — `pc.setOpen()` called programmatically. */ export type PreviewCardChangeReason = | "trigger-hover" | "trigger-focus" | "escape-key" | "outside-press" | "imperative"; /** * Options for {@link createPreviewCard}. */ export interface CreatePreviewCardOptions { /** * Open state. Consumer-owned signal. */ open: MaybeAccessor; /** * Whether interaction is suppressed. * @default false */ disabled?: MaybeAccessor; /** * Pre-change callback fired via `runWithOwner(null, ...)`. */ onOpenChange?: ( open: boolean, event: Event, reason: PreviewCardChangeReason, ) => void; /** * Delay before opening (ms). * @default 600 */ delay?: MaybeAccessor; /** * Delay before closing on hover-out (ms). * @default 300 */ closeDelay?: MaybeAccessor; } /** * Public API returned by {@link createPreviewCard}. */ export interface PreviewCardApi { /** Reactive accessor for the current open state. */ open: Accessor; /** Whether interaction is suppressed. */ disabled: Accessor; /** Open delay in ms. */ delay: Accessor; /** Close delay in ms. */ closeDelay: Accessor; /** Imperative open setter. */ setOpen: ( next: boolean, event?: Event, reason?: PreviewCardChangeReason, ) => void; /** Flip open (no-op when disabled). */ toggle: (event?: Event, reason?: PreviewCardChangeReason) => void; /** Context provider. */ Provider: (props: { children: JSX.Element }) => JSX.Element; } const PreviewCardContext = createContext(null); /** * Read the nearest {@link PreviewCardApi}. Throws unless `optional` is true. */ export function usePreviewCard(): PreviewCardApi; export function usePreviewCard(optional: true): PreviewCardApi | null; export function usePreviewCard(optional?: boolean): PreviewCardApi | null { const ctx = useContext(PreviewCardContext); if (ctx == null && !optional) { throw new Error( "Base UI: usePreviewCard() must be called inside a .", ); } return ctx; } /** * Headless primitive for a preview-card compound. Hover-triggered with open * and close delays. DOM machinery lives in ``. */ export function createPreviewCard( options: CreatePreviewCardOptions, ): PreviewCardApi { const core = createPopupCore({ open: options.open, disabled: options.disabled, onOpenChange: options.onOpenChange, }); const delay = createMemo(() => readOption(options.delay) ?? 600, { name: "previewCard.delay", }); const closeDelay = createMemo( () => readOption(options.closeDelay) ?? 300, { name: "previewCard.closeDelay" }, ); const api: PreviewCardApi = { open: core.open, disabled: core.disabled, delay, closeDelay, setOpen: core.setOpen, toggle: core.toggle, Provider: (props) => ( {props.children} ), }; (api as PreviewCardApi & Snapshottable)[SNAPSHOT_KEYS] = PREVIEW_CARD_SNAPSHOT_KEYS; return api; } const PREVIEW_CARD_SNAPSHOT_KEYS = snapshotKeys( "open", "disabled", "delay", "closeDelay", );