# `PhoenixKitCatalogue.Web.Components.ItemPicker`
[🔗](https://github.com/BeamLabEU/phoenix_kit_catalogue/blob/0.8.0/lib/phoenix_kit_catalogue/web/components/item_picker.ex#L1)

Combobox LiveComponent for picking a single item from the catalogue
via server-side search.

Drop one into any LiveView — typically many, one per row in a picker
table. Each instance owns its own search state; the parent LV only
reacts to two messages:

    {:item_picker_select, id, %Item{}}  # user chose an item
    {:item_picker_clear,  id}           # user cleared the selection

### API

    <.item_picker
      id="row-42-picker"
      category_uuids={[category.uuid]}
      selected_item={@chosen_item}
      excluded_uuids={@already_used_uuids}
      locale="en"
    />

Attrs:

  * `:id` (required) — unique DOM/component id. The `:item_picker_*`
    messages echo this back so a parent with N pickers knows which
    fired.
  * `:category_uuids` — scope search to these categories. `nil` or
    `[]` means "all categories + uncategorized" (matches
    `Catalogue.search_items/2`).
  * `:catalogue_uuids` — scope search to these catalogues. Composes
    with `:category_uuids` (AND).
  * `:include_descendants` — when `true` (default), `:category_uuids`
    is expanded through the V103 tree; pass `false` for literal
    set semantics.
  * `:only` — `:uncategorized_only` restricts results to items without
    a category; `:categorized_only` restricts to items in some
    category; `nil` (default) is unrestricted. Forwards to
    `Catalogue.search_items/2`'s `:only` opt.
  * `:selected_item` — the `%Item{}` currently chosen (or `nil`).
    Drives the input text and the `aria-selected` / primary-border
    styling in the dropdown.
  * `:excluded_uuids` — items in this list are rendered dim +
    `aria-disabled` and cannot be clicked. Use for "already picked in
    another row" state.
  * `:locale` (required) — locale string for translated display
    names (`"en"`, `"es"`, etc.). Resolved via
    `Catalogue.get_translation/2`.
  * `:placeholder` — input placeholder. Defaults to "Search items…".
  * `:empty_query_limit` — how many items to show when the query is
    empty (the "just focused" state). Defaults to `10`.
  * `:page_size` — max results fetched per query. Defaults to `20`.
    When the unbounded count exceeds this the dropdown shows a
    "Type to refine…" sentinel row so the user knows there's more.
  * `:disabled` — disables the input and hides the clear button.
  * `:format_price` — 1-arity function taking an `%Item{}` (with
    `:catalogue` preloaded — the search always does this) and
    returning a display string or `nil`. Defaults to a Decimal
    stringifier of `item_pricing(item).final_price`. Return `nil` to
    omit the price column entirely.
  * `:show_unit` — when `true`, renders the item's measurement unit
    (via `:format_unit`) as a small muted label next to the price in
    each dropdown row. Defaults to `false` (no unit) so existing
    consumers are unaffected.
  * `:format_unit` — 1-arity function taking the item's unit string and
    returning a display label (`""` to omit). Only used when
    `:show_unit` is `true`. Defaults to a built-in mapping of common
    abbreviations (`piece`→`pc`, `set`→`set`, `pair`→`pair`,
    `sheet`→`sheet`, `m2`→`m²`, `running_meter`→`rm`; unknown strings
    pass through). Supply your own to use a different unit vocabulary.
  * `:highlight_selected` — when `true` (default), the input gets the
    `input-primary` border while an item is selected. Pass `false` to
    suppress that highlight. Default preserves existing behaviour.
  * `:initial_query` — optional seed string for the search input. When
    provided (and nothing is selected and the user hasn't typed), the
    input is prefilled with this string and the dropdown opens with
    matching results on first render. Fires once; subsequent updates
    leave the query alone. Defaults to `nil` (no seeding).

### Keyboard / a11y

Handled client-side by the colocated `ItemPicker` hook:

  * ArrowDown / ArrowUp cycle through enabled options (announced via
    `aria-activedescendant`; DOM focus stays on the input).
  * Home / End jump to first / last enabled option.
  * Enter activates the focused option (simulates a click so the
    normal `select` event fires).
  * Escape closes the dropdown and keeps focus on the input.
  * Clicking outside the picker closes it (`phx-click-away`).

The dropdown is absolutely positioned and elevated with `z-50`; the
parent container must allow overflow (`overflow: visible` or just
don't set `overflow: hidden` on an ancestor that clips it).

---

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