{#- chirp-ui: Combobox / autocomplete Typeahead text input over a filtered role="listbox" of suggestions — the WAI-ARIA combobox pattern with aria-activedescendant. Typing filters the options client-side; ArrowDown/ArrowUp rove the visible options, Enter selects the active one, Escape closes, click-outside closes. Single-select (default): selecting fills the visible input with the option label, writes the option value to a hidden input (submitted under `name`), and dispatches `chirpui:combobox-selected` {value, label}. Multi-select (`multiple=true`): selecting adds a removable pill, keeps the list open, and clears the query; each selected value submits as a repeated hidden `name` input. Backspace on the empty input removes the last pill; the remove button removes a specific one; already-selected options drop out of the list. Multi-select requires JavaScript (the pills/values are Alpine-managed); v1 starts empty (no pre-selected values). Requires Alpine.js. In single-select, options are server-rendered so the field degrades to a plain text input with JS off (the hidden value still submits). Options: list of {label, value?} dicts (value defaults to label). Usage: from "chirpui/combobox.html" import combobox {{ combobox(name="country", label="Country", placeholder="Search countries…", value="us", options=[{"label": "Canada", "value": "ca"}, {"label": "United States", "value": "us"}]) }} {{ combobox(name="tags", label="Tags", multiple=true, placeholder="Add tags…", options=[...]) }} -#} {% def combobox(name, options, label=none, value="", placeholder="", multiple=false, id=none, cls="") %} {% set _cid = id or ("chirpui-combobox-" ~ name) %} {% set _sel = [o.get("label", o.get("value", "")) for o in options if not multiple and value and (o.get("value", o.get("label", "")) | string) == (value | string)] %} {% set _selected_label = (_sel[0] if _sel else "") %}