import { type Accessor, createContext, useContext } from "solid-js"; import type { BaseUIChangeEventDetails, BaseUIGenericEventDetails, 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 { ItemEqualityComparer } from "../internals/item-equality"; import type { Group } from "../internals/resolve-value-label"; import type { ComboboxSide } from "../positioner/positioner-context"; export type ComboboxRootChangeEventReason = | typeof REASONS.triggerPress | typeof REASONS.outsidePress | typeof REASONS.escapeKey | typeof REASONS.focusOut | typeof REASONS.listNavigation | typeof REASONS.inputChange | typeof REASONS.inputClear | typeof REASONS.itemPress | typeof REASONS.clearPress | typeof REASONS.inputPress | typeof REASONS.chipRemovePress | typeof REASONS.none; export type ComboboxRootChangeEventDetails = BaseUIChangeEventDetails; export type ComboboxRootHighlightEventReason = "keyboard" | "pointer" | "none"; export type ComboboxRootHighlightEventDetails = BaseUIGenericEventDetails< ComboboxRootHighlightEventReason, { index: number } >; /** * Shared state exposed through the root context. Accessors throughout so * consumers don't need to guard reactive reads. */ export interface ComboboxRootContextValue { // Identity id: Accessor; labelId: Accessor; setLabelId: (id: string | undefined) => void; // Open state + transitions open: Accessor; mounted: Accessor; transitionStatus: Accessor; // Input value + selection inputValue: Accessor; setInputValue: ( next: string, details: ComboboxRootChangeEventDetails, ) => void; /** * In single-select + `"none"` modes this is the single selected value (or * `null`); in multi-select mode it's an array of selected values. */ selectedValue: Accessor; setSelectedValue: ( next: Value | Value[] | null, details: ComboboxRootChangeEventDetails, ) => void; /** Selection mode — drives chip/array vs. scalar semantics. */ selectionMode: Accessor<"none" | "single" | "multiple">; // Active/selected indices activeIndex: Accessor; selectedIndex: Accessor; setIndices: (options: { activeIndex?: number | null | undefined; selectedIndex?: number | null | undefined; type?: ComboboxRootHighlightEventReason | undefined; }) => void; // Control / flags disabled: Accessor; readOnly: Accessor; required: Accessor; autoHighlightMode: Accessor; highlightItemOnHover: Accessor; modal: Accessor; loopFocus: Accessor; openOnInputClick: Accessor; /** * Whether list items are presented as a grid. See `grid` prop on * ``. */ grid: Accessor; /** * Whether items are externally virtualized. See `virtualized` prop on * ``. */ virtualized: Accessor; itemToStringLabel: Accessor<((item: Value) => string) | undefined>; itemToStringValue: Accessor<((item: Value) => string) | undefined>; isItemEqualToValue: Accessor>; // Items / filtering items: Accessor[] | undefined>; filteredItems: Accessor[]>; flatFilteredItems: Accessor; query: Accessor; // Static-children (no `items` prop) filtering. When consumers render // `<*.Item>` children directly (the cmdk "Command" pattern) instead of // passing `items`, there is no `flatFilteredItems` to drive empty-state / // per-item visibility. These helpers let filtering be driven by the // registered children instead: // • `matchesQuery(value)` — whether a single item's value matches the // current query (honoring `filter` / `filter={null}`, and the // autocomplete `mode` static-item contract). Returns `true` when the // query is empty or filtering is disabled. // • `setItemVisible(token, visible)` — each item reports its live // visibility keyed by a per-item token. // • `visibleItemCount` — reactive count of currently-visible static // children, read by `<*.Empty>` / `<*.List>`. // All three are inert in items-mode (visibility + empty derive from // `flatFilteredItems`). matchesQuery: (value: Value) => boolean; setItemVisible: (token: symbol, visible: boolean) => void; visibleItemCount: Accessor; // Refs shared with parts listRef: MutableRef>; labelsRef: MutableRef>; popupRef: MutableRef; inputRef: MutableRef; emptyRef: MutableRef; clearRef: MutableRef; valuesRef: MutableRef; allValuesRef: MutableRef; /** Host ref for ``. Used by dismiss outside-press filter. */ chipsContainerRef: MutableRef; // Elements (reactive) triggerElement: Accessor; setTriggerElement: (el: HTMLElement | null) => void; inputElement: Accessor; setInputElement: (el: HTMLInputElement | null) => void; listElement: Accessor; setListElement: (el: HTMLElement | null) => void; positionerElement: Accessor; setPositionerElement: (el: HTMLElement | null) => void; popupSide: Accessor; setPopupSide: (side: ComboboxSide | null) => void; // Imperative actions + setOpen setOpen: (next: boolean, details: ComboboxRootChangeEventDetails) => void; handleSelection: ( event: MouseEvent | PointerEvent | KeyboardEvent, value?: Value, ) => void; forceMount: () => void; // Floating/interactions floatingRootContext: FloatingRootStore; interactions: UseInteractionsReturn; // keyboard/pointer modality ref keyboardActiveRef: MutableRef; } export const ComboboxRootContext = createContext | null>(null); export function useComboboxRootContext< Value = unknown, >(): ComboboxRootContextValue; export function useComboboxRootContext( optional: true, ): ComboboxRootContextValue | null; export function useComboboxRootContext( optional?: boolean, ): ComboboxRootContextValue | null { const ctx = useContext(ComboboxRootContext); if (ctx == null && !optional) { throw new Error( "Base UI: ComboboxRootContext is missing. Combobox parts must be placed within .", ); } return ctx as ComboboxRootContextValue | null; }