import type { JSX, ValidComponent } from "@solidjs/web"; import { dynamic } from "@solidjs/web"; import { createMemo, omit } from "solid-js"; import { composeRefs, userRef } from "../../internals/compose-refs"; import { CompositeList } from "../../internals/composite/composite-list"; import { mergeRootProps } from "../../internals/merge-root-props"; import { resolveClassStyle } from "../../internals/resolve-class-style"; import type { BaseUIComponentProps } from "../../internals/types"; import { useComboboxRootContext } from "../root/combobox-root-context"; export interface ComboboxListState { empty: boolean; } export interface ComboboxListProps extends BaseUIComponentProps<"div", ComboboxListState> { as?: ValidComponent; } /** * A list container for the items. Renders a `
` element with * `role="listbox"` and wires `` so items auto-register * their DOM order + labels for typeahead. */ export function ComboboxList(props: ComboboxListProps): JSX.Element { const root = useComboboxRootContext(); const state = createMemo(() => ({ empty: root.flatFilteredItems().length === 0, })); const setListRef = (el: HTMLElement | null): void => { root.setListElement(el); }; function onKeyDown(event: KeyboardEvent): void { if (root.disabled() || root.readOnly()) return; if (event.key === "Enter") { const ai = root.activeIndex(); if (ai == null) return; event.preventDefault(); const li = root.listRef.current[ai]; if (li) li.click(); } } const internal = { role: "listbox" as const, tabIndex: -1, get id() { return root.floatingRootContext.useState("floatingId") ?? undefined; }, onKeyDown, onKeyDownCapture(): void { root.keyboardActiveRef.current = true; }, onPointerMoveCapture(): void { root.keyboardActiveRef.current = false; }, get "data-empty"() { return state().empty ? "" : undefined; }, }; const userClassStyle = resolveClassStyle(props, () => state()); const userProps = omit(props, "as", "class", "style", "attrs"); let merged: Record = mergeRootProps( root.interactions.getFloatingProps(), internal, ); merged = mergeRootProps(merged, userClassStyle); merged = mergeRootProps(merged, userProps); const Tag = dynamic(() => props.as ?? "div"); const element = (): JSX.Element => ( ); const hasItems = (): boolean => root.items() !== undefined; // In static-children mode the `dynamic()` element (and therefore the // `` children it renders) MUST be created *inside* the // `` provider so the items resolve the real // `CompositeListContext` and register their DOM order. If the element were // built in this component's own owner (e.g. `const element = `) // the item children would execute under ComboboxList's owner — outside the // provider — and silently fall back to the no-op context, so they would // never register, `index()` would stay `-1`, `listRef` would stay empty, // and keyboard navigation would be dead. Mirror ``, // which inlines its `dynamic()` element inside `` for the same // reason. (items-mode has no item-registration dependency, so the plain // element is fine there.) return ( <> {hasItems() ? ( element() ) : ( {element()} )} ); }