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-activedescendant on the input + data-highlighted on 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 through Channel.accept/2 (that is what makes staleness prevention construction-grade; the client's data-stale flag 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

combobox(assigns)

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 to nil.

  • 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 of nil, "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.