# `Flicker.Components.Select`
[🔗](https://github.com/team-alembic/flicker/blob/v0.1.0/lib/flicker/components/select.ex#L1)

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](https://github.com/team-alembic/flicker/blob/main/docs/specs/spec-010-windowed-search.md),
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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
