LiveInteractionContracts.Components.Select (live_interaction_contracts v1.0.0)

Copy Markdown View Source

Headless, unstyled, patch-safe select — popover (openness) + cursor (aria-activedescendant) + server-owned selection projected into a hidden form input.

Ownership (the decision that keeps this green — see CONTRACT_SELECT.md)

The playbook's provisional table guessed select would be amber ("value is form state"). This component resolves that by construction: the selected value never lives in browser editing state at all. Selection is a lossless client→server event (on_value_change); the server re-renders the trigger label, the hidden <input value=...>, and aria-selected — all server-owned constants, patch-safe by construction. Axis A: green (open = top layer; cursor = one-frame client; value = server). Axis B: protected-reflection (aria-expanded, aria-activedescendant, data-highlighted).

Usage

<.select id="fruit" name="fruit" value={@fruit} on_value_change="fruit_changed">
  <:trigger>{@labels[@fruit] || "Pick a fruit"}</:trigger>
  <:option :for={{id, label} <- @options} id={id}>{label}</:option>
</.select>

The caller renders the trigger content (it knows the label for the current value); the component wires everything else from @id.

Summary

Functions

select(assigns)

Attributes

  • id (:string) (required) - stable id; all wiring is derived from it.

  • value (:string) - server-owned selected option id. Defaults to nil.

  • name (:string) - if set, renders a hidden <input name=... value=...> for form integration. Defaults to nil.

  • on_value_change (:string) - LiveView event pushed with %{id: option_id} when the user selects (lossless). Defaults to nil.

  • mode (:string) - "auto" = native light-dismiss; "manual" = explicit dismiss only. Defaults to "auto". Must be one of "auto", or "manual".

  • 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). Do not combine with a custom style on the trigger or listbox element.

    Defaults to nil. Must be one of nil, "bottom", or "top".

  • Global attributes are accepted. extra attrs on the listbox element.

Slots

  • trigger (required) - trigger content; render the current label here.
  • item (required) - Accepts attributes:
    • id (:string) (required) - stable logical id for this option.