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

# `lr-push-to-talk`

- **Import** `import '@aceshooting/lyra-ui/components/lr-push-to-talk.js';` (stable tag alias; registers the tag)
- **Class** `LyraPushToTalk`, also available unregistered from `@aceshooting/lyra-ui/components/conversation/push-to-talk/push-to-talk.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** 5 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-push-to-talk`

A mic capture button owning the full `getUserMedia` + `MediaRecorder` lifecycle: permission request,
recording, optional chunked streaming, teardown. The one place in this library that touches the
microphone — no SDK, no LiveKit/ElevenLabs import, native browser APIs only. `mode="hold"` (the
default) is a press-and-hold gesture; `mode="toggle"` is click-to-start/click-to-stop with
`aria-pressed`. Escape cancels the in-progress take in either mode.

For example, `<lr-push-to-talk><span slot="microphone-icon">MIC</span></lr-push-to-talk>` replaces
the idle microphone glyph. Use `recording-icon` separately for the recording state.

While recording, the optional elapsed timer uses the effective locale's decimal digits, suppresses
grouping, and pads its seconds field to two locale-aware digits.

**Properties:** `mode: PushToTalkMode = 'hold'` (`'hold' | 'toggle'`, reflected; invalid writes
normalize to `hold`), `timesliceMs: number = 0` (attribute
`timeslice-ms`) — `> 0` passes a timeslice to `MediaRecorder.start()` and emits `lr-record-chunk` per
slice, `mimeType: string = ''` (attribute `mime-type`) — a `MediaRecorder` MIME type, `deviceId:
string = ''` (attribute `device-id`) — a specific input device, `audioConstraints?:
PushToTalkAudioConstraints` (attribute: false) — merged into the `getUserMedia` audio constraints;
it deliberately excludes `deviceId`, whose single authority is the dedicated property,
`levelEvents: boolean = false` (attribute `level-events`) — opt in to `lr-level`, `maxDurationMs:
number = 0` (attribute `max-duration-ms`) — auto-stop cap, `0` disables it, `showTimer: boolean =
true` (attribute `show-timer`), `disabled: boolean = false` (reflected), plus two getter-only
properties: `state: PushToTalkState` (`'idle' | 'requesting' | 'denied' | 'recording' | 'error'`,
mirrored to `data-state`) and `stream: MediaStream | null` (the live capture stream, assignable straight onto
`lr-audio-visualizer.stream`).

A host `aria-label` names the internal trigger by attribute presence; an explicitly empty value is
preserved instead of being replaced by the generated Hold/Start/Stop label.

`levelEvents`, `maxDurationMs`, and `showTimer` stay reactive during an active recording: changing
them starts or stops their audio-analysis, deadline, or elapsed-time work immediately. A changed
maximum remains measured from the original recording start rather than granting a fresh duration;
setting it to `0` removes the deadline.

**Methods:** `start()`, `stop()`, and `cancel()` drive the capture lifecycle imperatively (mirroring
the pointer/keyboard gestures).

After permission is requested, only a still-active control in the same document may begin
recording; otherwise any stream returned later is immediately stopped. In hold mode, a later held
press in that document can take over one pending permission request, and a held press while
stopping begins after the prior take ends. Only one capture runs at a time.

**Slots:** `microphone-icon` replaces the default mic glyph. `recording-icon` replaces the default
recording-state pulse glyph. Both are
decorative inside the named trigger: their flattened content is inert and hidden from accessibility
APIs, so do not place a second interactive control there.

**Events:** `lr-record-start` (`detail: { stream: MediaStream }`), `lr-record-chunk` (`detail: { blob:
Blob }`, only when `timeslice-ms > 0`), `lr-record-stop` (`detail: { blob: Blob; durationMs: number
}`), `lr-record-cancel` (`detail: null`), `lr-record-error`
(`detail: { error: DOMException | Error }`, covering permission, recorder construction/start, and
recorder runtime failures), `lr-level`
(`detail: { level: number }` — 0–1 amplitude, opt-in via `level-events`), and `lr-record-state-change`
(`detail: { state: 'idle' | 'requesting' | 'denied' | 'recording' | 'error' }`).

**CSS parts:** `trigger` (the capture button), `icon`, `pulse` (rendered only while recording),
`timer` (the localized `M:SS` elapsed-time readout, only while recording and `show-timer`), and `status`
(visible status text for the `requesting`/`denied`/`error`/unsupported states).

**Themeable custom properties:** `--lr-push-to-talk-size` (default `var(--lr-size-3rem)`) — the
trigger button's preferred inline and block size; the shared `--lr-icon-button-size` remains its
minimum hit-area floor even when this value is smaller. `--lr-push-to-talk-recording-color` (default
`var(--lr-color-danger)`) remains the established aggregate fallback for the recording trigger's
border and foreground and the pulse-ring border. Retune those independently with
`--lr-push-to-talk-trigger-recording-border-color`,
`--lr-push-to-talk-trigger-recording-color`, and
`--lr-push-to-talk-pulse-recording-border-color`, each defaulting through
`var(--lr-push-to-talk-recording-color, var(--lr-color-danger))`. All four are inline `var()`
fallbacks at the point of use rather than `:host` declarations, so each can be set on the element or
on an ancestor without repainting every other danger-toned surface. `::part(trigger)[data-state='recording']`
is invalid CSS (Shadow Parts forbids an attribute selector after `::part()`), which is why these
recording-state hooks exist.

**Additional API surface:**

- `blur()` — Forwards host blur to the internal push-to-talk control.
- `focus()` — Forwards host focus to the internal push-to-talk control.
- `click()` — Programmatically starts or stops a take, mirroring a real pointer/keyboard
  activation: in `mode="toggle"` it forwards to the trigger's native `click()`; otherwise it calls
  `stop()` while `state` is `recording` or `start()` otherwise. A no-op while `disabled` or
  unsupported.
