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

# `lr-message-actions`

- **Import** `import '@aceshooting/lyra-ui/components/lr-message-actions.js';` (stable tag alias; registers the tag)
- **Class** `LyraMessageActions`, also available unregistered from `@aceshooting/lyra-ui/components/conversation/message-actions/message-actions.class.js`
- **Family** `components/conversation/` — 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** 7 parts, 0 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-message-actions`

The per-message action toolbar for `lr-chat-message`'s `actions` slot: opt-in built-ins (copy /
regenerate / edit / feedback) that emit intent events, plus a default slot for custom controls (e.g.
a slotted `lr-branch-picker`). `role="toolbar"` with WAI-ARIA APG roving-tabindex; ArrowLeft/
ArrowRight (RTL-aware) plus Home/End move focus across every stop — built-ins and slotted controls
alike. Composite controls expose ordered logical actions through the exported
`LyraToolbarActionProvider` protocol, so implementation nodes stay private while the toolbar can
focus and set each logical tab stop. Providers announce order/availability changes with
`lr-toolbar-actions-change`; plain authored controls remain observed in light DOM.
Disabled, hidden, `aria-hidden`, `aria-disabled`, inert, or no-longer-actionable controls (including
controls beneath an unavailable ancestor) are excluded before the usable roving fallback is chosen.
Those states and `tabindex` are observed live, not only at mount/slot assignment; former stops are
cleared immediately. Slotted custom elements contribute their actual composed action targets rather
than their host merely because it has a `focus()` method; multiple nested actions (for example both
feedback thumbs or branch-picker buttons) remain distinct stops. If the focused action is removed or
becomes unavailable, focus moves to the nearest survivor or the stable toolbar, without overriding a
newer external focus move. Keyboard movement starts from the action that actually received the event,
even after a controlled state write changed the remembered stop.

ArrowLeft/ArrowRight/Home/End from a slotted feedback comment editor remain native editing keys.
Roving navigation still operates on the actual toolbar and thumb actions.

**Properties:** `controls: MessageActionControl[] = []` (attribute: false) —
`MessageActionControl = 'copy' | 'regenerate' | 'edit' | 'feedback'` (exported here); which built-ins
render, in that order. Duplicate names are omitted first-wins before rendering, roving focus, or
intent events, so each built-in can occur at most once. `copyText: string = ''`
(attribute `copy-text`) — required for the `copy` built-in to render at all. `feedbackRating:
MessageFeedbackValue = null` (attribute `feedback-rating`) — forwarded to the embedded, thumbs-only
`lr-message-feedback` (its `detail`/`detailFor` are never forwarded, so its detail panel never
opens). `feedbackPending: boolean` (read-only, nonreflecting) — true only while the current built-in
feedback control awaits settlement; it has no `feedback-pending` attribute or change event.
`revealOnInteraction: boolean = false` (reflected, attribute `reveal-on-interaction`) — hides
the bar until the closest `lr-chat-message` ancestor is hovered, or the toolbar contains focus.
`label: string = ''` — accessible name override for the toolbar. `accessibleLabel: string | null =
null` (attribute `aria-label`) — overrides the toolbar's computed accessible name, winning over
`label` and the localized default; attribute-reflects from a host-level `aria-label`.

**Methods:** `finalizePendingSubmit(submissionId: string): boolean` and
`revertPendingSubmit(submissionId: string): boolean` settle only the current built-in feedback
request. Both return `false` for blank, stale, mismatched, removed, or replaced requests.

**Events:** `lr-regenerate`/`lr-edit` — a built-in was activated, `detail: null`. `lr-copy` —
frozen `detail: { ok: true, text }`, emitted only after the embedded `lr-copy-button`'s clipboard
write fulfills (bubbles/composed already, not re-emitted). A failed write surfaces generic
`lr-error` (`detail: null`) plus `lr-copy-error` with frozen
`detail: { ok: false, text, reason, error }`; `reason` is `'unsupported' | 'denied' | 'failed'`.
`lr-feedback-change`/`lr-feedback-submit` — bubble unchanged from the embedded, thumbs-only
`lr-message-feedback`; the frozen submit detail includes its `submissionId`, which is the value to
pass to either settlement method. A colliding event from an
arbitrary slotted child is contained at that slot boundary rather than being mistaken for a
built-in action.

Composite toolbar providers must expose nonblank action ids that are unique within that provider;
invalid actions and later duplicates are omitted before roving focus ownership.

**Slots:** default — additional controls (e.g. `lr-copy-button`, `lr-icon-button`,
`lr-branch-picker`) appended after the built-ins; they participate in the toolbar's arrow-key
navigation.

**CSS parts:** `base` (the toolbar, `role="toolbar"`), `copy-button` (the embedded
`lr-copy-button`), `regenerate-button`, `edit-button`,
`regenerate-button__control` / `edit-button__control` (each built-in action's own native `<button>` —
as of 16.0.0 both are composed `<lr-icon-button>`s, so `--lr-icon-button-*` retunes them and the
toolbar's roving tab stop is leased on the native control rather than the host), and `feedback` (the
embedded `lr-message-feedback`).

The toolbar has no `size`/`compact` property: every built-in's hit area is `<lr-icon-button>`'s
shared `--lr-icon-button-size` floor (2.5rem/40px), same as everywhere else in the library. For a
dense action row, lower `--lr-theme-icon-button-size` (not `--lr-icon-button-size`, which every
`LyraElement` re-declares on its own `:host` and so never reaches a composed child) on this element
or an ancestor, or reach a built-in's composed native control directly through
`::part(regenerate-button__control)` / `::part(edit-button__control)`. A coarse-pointer/no-hover
media rule then floors the rendered hit area at 2.75rem/44px regardless of how far a dense-row
override lowered it, so the shrink is safe on a touch device.

**Migrating a pre-16.0.0 `::part()` rule.** This component's icon-only actions are composed
`<lr-icon-button>`s, so a part naming one of them now names the composed child's HOST, which paints
nothing. A `border`, `background` or `border-radius` set on it is silently dead — only `color` still
appears to work, because it inherits, which makes such a rule look half-alive rather than broken.
Set `--lr-icon-button-background`/`-color`/`-border`/`-radius` (and their `-hover`/`-active`
variants) on this element or an ancestor instead: the composed control reads those public tokens
ahead of any default this component supplies. For SIZE use `--lr-theme-icon-button-size`, not
`--lr-icon-button-size` — every `LyraElement` re-declares the latter on its own `:host`, so it never
reaches a composed child (see `llms/tokens.md`).
