Flicker.Components.Select (Flicker v0.1.0)

Copy Markdown View Source

The internal Phoenix.LiveComponent behind Flicker.select/1.

This module is not part of the public API — hosts never reference it directly, its assigns and events are internal, and its shape can change between minor releases without notice. Flicker.select/1 is the only supported entry point.

Owns all search state: the typed query, the loaded results, the open/ closed listbox, the resolved selection, and (in form-field mode) the hidden inputs and _unused_<field> recovery marker (ADR-005, ported verbatim per the extraction notes). Every render is gated on connected?/1 — interactive controls are disabled until the socket has joined, so typing before the LiveSocket connects can never be lost.

multiple (Spec 002) switches the value model: selected becomes a list of Flicker.Result structs (kept as Results, not bare values, so chips render without refetching) instead of a single Result or nil. Options already in selected are excluded from search results; fetch/2 still runs exactly once, resolving every preselected value in one call (ADR-003) regardless of how many are set.

group (Spec 008): when any Flicker.Result in results carries a :group, the listbox renders a contiguous, non-interactive theme.group_header row before the first result of each new group — ordering is whatever the provider returned, never re-sorted here. A groupless result list (every :group is nil, the default) renders exactly as it always has; Flicker.select/1 never sets :group itself, it only renders it when a provider does — Flicker.palette/1 is the only caller that has any special interest in it, and even that interest lives entirely in provider data, not a branch here.

navigate_on_select (Spec 008, internal-only assign — not part of Flicker.select/1's public attrs) additionally issues a push_navigate/2 to result.meta.href on selection, when set. This is Flicker.palette/1's navigate-on-select convention; it is plain assign-driven behaviour, not a branch on "am I a palette" — Flicker.select/1 simply never sets the assign, so its own selections never navigate.

facets (Spec 003) drives the same Flicker.CursorContext/ Flicker.FacetSuggest machinery Flicker.Components.Search does: while the cursor sits in facet-key or facet-value position, the listbox shows key/value suggestions (tagged meta.flicker_facet: true) instead of record results, and picking one edits the typed text rather than selecting a record. Once the cursor is back in free-text position, the listbox reverts to ordinary record search — run against the free-text portion Flicker.Query.parse/2 extracts, not the raw typed string (so a completed status:active doesn't leak into the ilike match). The parsed facet filter also ANDs into the provider's own record query (not just the autocomplete UX) — see run_faceted_search/2's :text clause.

The .Nav colocated hook reports the input's real selectionStart on every keyup/click/select (Spec 003's cursor-tracking follow-up, now closed) — the cursor assign carries it (nil before the hook's first event, or after this component sets the query text itself, falling back to end-of-text per Flicker.FacetSuggest.classify/3), so a cursor moved back into an already-typed facet token classifies right there, not "at the end".

paginate (Spec 010, default false) switches the "keep typing to narrow" limit + 1 probe into windowed infinite scroll: reaching the tail of the listbox (via the .Nav hook's IntersectionObserver sentinel, or ArrowDown on the last option) fetches the next limit-sized window (offset: window * limit) and appends it to results instead of replacing them. A new query, or a facet-context change, resets window to 0, discards any in-flight window fetch, and scrolls the listbox back to the top — windowing only ever accumulates for the current query. Capped by max_windows (default 10): past the cap — or as soon as core detects a provider ignoring :offset (a window identical to the one before it) — the list is marked complete and the tail renders the same "keep typing to narrow" hint windowing otherwise replaces, rather than a bespoke dead-end state. paginate: false (the default) is byte-identical to pre-Spec-010 behaviour — none of this machinery engages.