import type { JSX, ValidComponent } from "@solidjs/web"; /** * `` — the text input. */ import { dynamic } from "@solidjs/web"; import { createMemo, omit } from "solid-js"; import type { ComboboxSide } from "../../combobox/positioner/positioner-context"; import { type ComboboxRootChangeEventDetails, useComboboxRootContext, } from "../../combobox/root/combobox-root-context"; import { createChangeEventDetails, REASONS, } from "../../internals/change-event-details"; import { composeRefs, userRef } from "../../internals/compose-refs"; import { mergeRootProps } from "../../internals/merge-root-props"; import { resolveClassStyle } from "../../internals/resolve-class-style"; import type { BaseUIComponentProps } from "../../internals/types"; import { useAutocompleteRootContext } from "../root/autocomplete-root-context"; export interface AutocompleteInputState { open: boolean; disabled: boolean; readOnly: boolean; popupSide: ComboboxSide | null; listEmpty: boolean; } export interface AutocompleteInputProps extends BaseUIComponentProps<"input", AutocompleteInputState> { as?: ValidComponent; disabled?: boolean | undefined; } export function AutocompleteInput(props: AutocompleteInputProps): JSX.Element { const root = useComboboxRootContext(); const autocomplete = useAutocompleteRootContext(); const disabled = (): boolean => root.disabled() || (props.disabled ?? false); const readOnly = (): boolean => root.readOnly(); const listEmpty = createMemo( () => root.flatFilteredItems().length === 0, ); const setInputRef = (el: HTMLInputElement | null): void => { root.setInputElement(el); }; const state = createMemo(() => ({ open: root.open(), disabled: disabled(), readOnly: readOnly(), popupSide: root.popupSide(), listEmpty: listEmpty(), })); const ariaAutocomplete = (): "list" | "both" | "inline" | "none" => { return autocomplete.mode(); }; function onInput(event: InputEvent): void { const value = (event.currentTarget as HTMLInputElement).value; const details = createChangeEventDetails( REASONS.inputChange, event, ) as ComboboxRootChangeEventDetails; root.setInputValue(value, details); if (!readOnly() && !disabled()) { if (!root.open() && (value !== "" || root.openOnInputClick())) { root.setOpen(true, details); } } if (!root.autoHighlightMode()) { root.setIndices({ activeIndex: null, type: "keyboard" }); } } function onKeyDown(event: KeyboardEvent): void { if (disabled() || readOnly()) return; root.keyboardActiveRef.current = true; if (event.key === "Enter" && root.open()) { const ai = root.activeIndex(); if (ai == null) return; event.preventDefault(); const li = root.listRef.current[ai]; if (li) li.click(); return; } if (event.key === "Escape" && !root.open() && root.inputValue() !== "") { const details = createChangeEventDetails( REASONS.escapeKey, event, ) as ComboboxRootChangeEventDetails; root.setInputValue("", details); } } function onPointerMove(): void { root.keyboardActiveRef.current = false; } function onPointerDown(): void { root.keyboardActiveRef.current = false; } const internal = { type: "text", autocomplete: "off" as const, spellcheck: false, role: "combobox", "aria-haspopup": "listbox" as const, get "aria-expanded"() { return root.open() ? ("true" as const) : ("false" as const); }, get "aria-controls"() { return root.open() ? (root.listElement()?.id ?? "") : ""; }, get "aria-autocomplete"() { return ariaAutocomplete(); }, get value() { return root.inputValue(); }, get disabled() { return disabled(); }, get readOnly() { return readOnly(); }, get required() { return root.required(); }, onInput, onKeyDown, onPointerMove, onPointerDown, // inputStateAttributesMapping projection. get "data-popup-open"() { return state().open ? "" : undefined; }, get "data-disabled"() { return state().disabled ? "" : undefined; }, get "data-readonly"() { return state().readOnly ? "" : undefined; }, get "data-list-empty"() { return state().listEmpty ? "" : undefined; }, get "data-popup-side"() { return state().popupSide ?? undefined; }, }; const userClassStyle = resolveClassStyle(props, () => state()); const userProps = omit(props, "as", "class", "style", "attrs", "disabled"); let merged: Record = mergeRootProps( root.interactions.getReferenceProps(), internal, ); merged = mergeRootProps(merged, userClassStyle); merged = mergeRootProps(merged, userProps); const Tag = dynamic(() => props.as ?? "input"); return ; }