PhoenixKitWeb.Components.Core.SearchPicker (phoenix_kit v1.7.200)

Copy Markdown View Source

A generic instant typeahead for "search a source, pick an entry" fields — extracted from phoenix_kit_crm's involved-parties picker.

The dropdown opens, spins, and renders ENTIRELY client-side (the SearchPicker hook in phoenix_kit.js); the server only executes the actual search and returns rows via push_event. Nothing visual waits on a round-trip.

Contract

The consumer (LiveView or LiveComponent) implements:

  • a search event (search_event, default "picker_search") receiving %{"q" => query, "limit" => limit} and answering with push_event(socket, results_event, %{q: query, results: rows, has_more: bool}) where each row is %{kind:, uuid:, label:, sublabel?, icon?} (icon = a heroicon class, defaults to hero-user);
  • a pick event (pick_event, default "picker_pick") receiving %{"kind" =>, "uuid" =>, "label" =>} — stage your chip, then confirm with push_event(socket, staged_event, %{}) so the hook clears the input;
  • optionally a free-text event (text_event) receiving %{"name" => text} — the "Add … as text" row renders only when this attr is set.

Multiple pickers in one view

push_event replies broadcast to EVERY hook listening on the event name, so two pickers sharing names would cross-populate (and a staged confirm would clear both). Give each picker distinct event names — or keep shared names and echo the id the hook sends with every push (%{"id" => input_id}) back in the results/staged payloads; when present, each instance drops payloads addressed to another id.

Single-select mode

mode="single" turns the picker into a suggestion box for ONE value: a pick sets the input's value client-side and fires a synthetic input event so a surrounding form's phx-change (and any server-side value→id mapping) sees it. No pick/staged events, no chips — give the input a form name and it submits like any field.

Example (multi, inside a LiveComponent)

<.search_picker
  id="party-search"
  dropdown_id="party-dropdown"
  text_event="stage_text"
  placeholder={gettext("Type a name…")}
/>

Events auto-route to the closest LiveComponent/LiveView containing the input — no target needed when the picker renders inside the component that handles its events.

The chips themselves stay consumer-rendered — their look and remove semantics differ per feature.

Summary

Functions

Renders the picker input + its client-rendered dropdown container.

Functions

search_picker(assigns)

Renders the picker input + its client-rendered dropdown container.

Attributes

  • id — input DOM id (required; must be page-unique)
  • dropdown_id — dropdown DOM id (required)
  • target — CSS selector of an element inside the LiveComponent that should receive the pushed events. ONLY needed when the picker renders OUTSIDE that component's DOM tree; omitted, events auto-route to the closest LiveComponent/LiveView containing the input (the usual case). The selector must actually match an element or every push is dropped with a console error.
  • mode"multi" (default) or "single" (see moduledoc)
  • search_on_focus — when true, clicking/focusing the EMPTY input already opens the dropdown in browse mode (the hook searches with an empty query, so the server should answer it with a first page — the workspace picker rule: offer options before any typing). Off by default; without it the dropdown only opens while typing, which also means a multi-mode picker won't reopen after a pick clears the input. (Previously only reachable as a raw data-search-on-focus rest attr — still honored for existing call sites.)
  • direction"down" (default) or "up". Use "up" when the input sits near the BOTTOM of a scrollable container (e.g. the last field of a modal): a downward dropdown there extends the container's scroll area instead of floating, so the suggestions end up below the fold — users read that as "no suggestions".
  • name/value — form field wiring for single mode
  • search_event / results_event / pick_event / text_event / staged_event — event-name overrides (defaults in moduledoc; text_event nil = no free-text row)
  • placeholder, class — input presentation
  • searching_label etc. — translated strings for the client-rendered rows

Attributes

  • id (:string) (required)
  • dropdown_id (:string) (required)
  • target (:any) - Defaults to nil.
  • mode (:string) - Defaults to "multi". Must be one of "multi", or "single".
  • search_on_focus (:boolean) - Defaults to false.
  • direction (:string) - Defaults to "down". Must be one of "down", or "up".
  • name (:string) - Defaults to nil.
  • value (:string) - Defaults to nil.
  • search_event (:string) - Defaults to "picker_search".
  • results_event (:string) - Defaults to "picker_results".
  • pick_event (:string) - Defaults to "picker_pick".
  • text_event (:string) - Defaults to nil.
  • staged_event (:string) - Defaults to "picker_staged".
  • placeholder (:string) - Defaults to nil.
  • class (:any) - Defaults to "input input-bordered w-full".
  • searching_label (:string) - Defaults to "Searching…".
  • add_prefix_label (:string) - Defaults to "Add".
  • add_suffix_label (:string) - Defaults to "as text".
  • adding_label (:string) - Defaults to "Adding…".
  • more_label (:string) - Defaults to "Load more".
  • loading_more_label (:string) - Defaults to "Loading…".
  • no_matches_label (:string) - Defaults to "No matches".
  • Global attributes are accepted.