<!-- GENERATED by scripts/build-llms.mjs from llms/retrieval.md — do not edit this file. -->

# `lr-node-palette`

- **Import** `import '@aceshooting/lyra-ui/components/lr-node-palette.js';` (stable tag alias; registers the tag)
- **Class** `LyraNodePalette`, also available unregistered from `@aceshooting/lyra-ui/components/retrieval/node-palette/node-palette.class.js`
- **Family** `components/retrieval/` — see `llms/index.md` for its siblings
- **Status** `stable` since `4.0.0` — see the maturity and deprecation policy in `llms/shared.md`
- **Release history** [CHANGELOG.md](../../CHANGELOG.md); family-wide breaking-change summaries: [llms-full.txt](../../llms-full.txt)
- **Deprecations** none
- **Optional peers** none
- **Themeable via** 12 parts, 5 custom properties — see this component's own `@csspart`/`@cssprop` list below
- **Library-wide behavior** (events, form association, `locale`/`strings`, tokens, TS types): `llms/shared.md`

---

## `lr-node-palette`

The searchable, categorized node library for workflow editors: drag an item onto a canvas, or place
it by keyboard. Never creates nodes or touches a canvas's data itself — the drop/place handshake ends
at `lr-node-add`/`lr-palette-place`; the host mutates `nodes`. Fully decoupled from
`lr-flow-canvas` (no `for` resolution, unlike the other three companions) — it only needs to agree
with a `droppable` canvas on the `FLOW_PALETTE_MIME_TYPE` drag payload shape.

For `lr-palette-place`, create the node and assign the updated array to the target canvas's public
`nodes` property. Updating a captured local array alone does not update an already rendered canvas.

**Properties:**

- `items: LyraPaletteItem[] = []` (attribute: false) — `LyraPaletteItem { type: string; label: string;
description?: string; category?: string; keywords?: string[]; icon?: unknown; disabled?: boolean }`;
  `type` is the `FlowNode.type` a placement/drop creates, `category` groups items under
  first-appearance-ordered headings, `disabled` renders an item visible but not draggable/placeable.
  A non-array value is treated as empty. Rows require nonblank string `type` and `label` values;
  malformed optional `description`, `category`, `keywords`, or `disabled` values omit only that row,
  preserving later valid entries. At most 10,000 positions are examined through own data
  descriptors; accessor-backed rows are omitted without reserving later valid rows. The frozen
  projection drives display while `lr-select.detail.item` retains the original admitted identity
- `label?: string` — accessible name for the search field/listbox; omission uses the localized
  palette label. An omitted, empty, or whitespace-only value uses that fallback without changing
  the raw property readback
- `reorderable: boolean = false` (reflected) — opts into Ctrl/Cmd+ArrowUp/ArrowDown keyboard
  reordering of the catalog. Unset, no `lr-reorder` is ever emitted and Ctrl/Cmd+Arrow keeps
  behaving exactly like a plain Arrow press
- `accessibleLabel: string | null = null` (attribute `aria-label`) — as a JS-only property while
  the host attribute is absent, overrides the listbox name. Authored host `aria-label` instead
  names the component as a whole (including explicit-empty/dynamic values) and is not cloned onto
  the listbox, which retains the distinct `label`/localized name

**Events:** `lr-palette-place` (`detail: { type }`, a pointer click or Enter/Space — the
click/keyboard alternative to dragging), `lr-select` (`detail: { item }`, emitted alongside
`lr-palette-place` on both gestures, carrying the full item), `lr-reorder`
(`detail: { type, category, fromIndex, toIndex }`, only while `reorderable`), `focus`/`blur`
(realm-correct native `FocusEvent`s relayed exactly once from the internal search field, preserving
`relatedTarget` while bubbling and crossing the shadow boundary).

`lr-reorder` is a _request_, the same host-applies-the-mutation contract `lr-tree`'s identical
`reorderable`/`lr-reorder` pair already uses: Ctrl/Cmd+ArrowUp/ArrowDown on the focused item asks to
move it past its neighbour **inside its own category group**, so a reorder can never turn into a
recategorization, and nothing is emitted at a group boundary. `category` is `null` for the
uncategorized bucket; `fromIndex`/`toIndex` index into `items` itself, so applying the move is a
plain splice-and-reassign. The palette never reorders `items` on its own, so nothing consults
`defaultPrevented` and the event is not cancelable. The new position is announced only once the
re-rendered group order confirms the host applied it.

**Slots:** `header` (content above the search field, e.g. a heading or tabs), `footer` (content
below the list).

**CSS parts:** `base`, `search-field` (wrapper around the search input and its clear button),
`search`, `search-clear` (clears the query; rendered only while the field has text), `list` (the
listbox), `group-header`, `item`, `item-icon`, `item-label`, `item-description`, `empty`
(no-results message), `live-region` (result-count announcement).

**Themeable custom properties:** `--lr-node-palette-search-min-height` (default
`var(--lr-icon-button-size)`), `--lr-node-palette-search-font-size` (default `inherit`),
`--lr-node-palette-search-padding-inline` (default `var(--lr-space-s)`),
`--lr-node-palette-search-padding-block` (default `var(--lr-space-xs)`) and
`--lr-node-palette-search-radius` (default `var(--lr-radius)`) size the built-in search field; point
the height at a `--lr-form-control-height-*` tier to match it to a themed search field. The height
hook can only raise the field — the shared tappable-target minimum stays underneath it, so no tier
can shrink it past the WCAG floor. The trailing inline gutter is reserved for the overlaid
`search-clear` button and is not a knob. Everything else is shared tokens.

**Optional peer deps:** none.

```html
<lr-node-palette id="palette"></lr-node-palette>
<lr-flow-canvas id="canvas" droppable style="height:480px"></lr-flow-canvas>
<script>
  document.getElementById("palette").items = [
    { type: "http-request", label: "HTTP Request", category: "Actions" },
    { type: "transform", label: "Transform", category: "Actions" },
  ];
  document.getElementById("canvas").addEventListener("lr-node-add", (e) => {
    console.log("drop payload type:", e.detail.type, e.detail.position);
  });
</script>
```

**Known gotchas:**

- A drag from this palette carries `FLOW_PALETTE_MIME_TYPE` as its `DataTransfer` type — only a
  `droppable` `lr-flow-canvas` (or a host reimplementing the same MIME type) accepts it.
- Clicking or activating an item by keyboard fires `lr-palette-place`/`lr-select` immediately, no
  drag required — the host decides where the new node lands (there's no pointer position to derive
  one from on a keyboard placement).

---
