LiveInteractionContracts. Components. Combobox
(live_interaction_contracts v1.0.0)
Copy Markdown
View Source
Headless, unstyled, patch-safe combobox — the composition case:
popover listbox (openness in the top layer) × Cursor (aria-activedescendant
on the input) × Channel (versioned async query/results with the server-side
monotonic guard, LiveInteractionContracts.Channel).
Ownership
- Openness: browser (top layer; the hook only calls show/hidePopover on user gestures — typing, Escape, selection).
- Cursor: client, one-frame; projected as
aria-activedescendanton the input +data-highlightedon options; live-DOM lookups; deterministic collapse and logical-id rebind after patches. - Results: server-rendered slot, stamped
data-version— and the server MUST route result renders throughChannel.accept/2(that is what makes staleness prevention construction-grade; the client'sdata-staleflag is telemetry only). - Selection: server-owned — Enter/click push a lossless
on_value_change. - IME: no query emission during composition; exactly one at
compositionend(real-device conformance remains an honest unknown — BACKLOG).
Usage
<.combobox id="fruit" version={@channel.rendered_version}
on_input_change="query" on_value_change="select">
<:option :for={r <- @results} id={r}>{r}</:option>
</.combobox>
def handle_event("query", %{"q" => q, "v" => v}, socket) do
channel = Channel.note_query(socket.assigns.channel, v)
results = search(q) # or async; on completion:
case Channel.accept(channel, v) do
{:ok, channel} -> {:noreply, assign(socket, channel: channel, results: results)}
{:stale, channel} -> {:noreply, assign(socket, channel: channel)}
end
end
Summary
Functions
Attributes
id(:string) (required) - stable id; all wiring is derived from it.version(:integer) (required) - the Channel's rendered_version — stamps data-version on the listbox.on_input_change(:string) (required) - LiveView event pushed with %{q, v} on input (v = client-monotonic stamp).on_value_change(:string) - LiveView event pushed with %{id: option_id} on Enter/click (lossless). Defaults tonil.placement(:string) - optional zero-JS anchored positioning (CSS Anchor Positioning; see reports/POSITIONING_SPIKE_REPORT.md). Server-owned static style attrs; flips at the viewport edge; follows the anchor through patches/scroll as plain layout. Older engines show it unanchored (documented version floor; no JS fallback).Defaults to
nil. Must be one ofnil,"bottom", or"top".Global attributes are accepted. extra attrs on the listbox element.
Slots
item(required) - Accepts attributes:id(:string) (required) - stable logical id for this option.