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.