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()}
)}
>
);
}