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

# `lr-sequence-playback`

- **Import** `import '@aceshooting/lyra-ui/components/lr-sequence-playback.js';` (stable tag alias; registers the tag)
- **Class** `LyraSequencePlayback`, also available unregistered from `@aceshooting/lyra-ui/components/media/sequence-playback/sequence-playback.class.js`
- **Family** `components/media/` — see `llms/index.md` for its siblings
- **Status** `stable` since `9.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** 3 parts, 3 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-sequence-playback`

Steps a current index through `[0, itemCount)` on a fixed interval — explicit discrete-sequence
playback for time-series scrubbing, without implying native audio/video playback.

When fewer than two items are available, the disabled play button retains its resting border and
background under hover and press. Enabled playback controls retain their token-driven pointer
feedback.

**Properties:**

- `itemCount: number = 0` (attribute `item-count`)
- `currentIndex: number = 0` (attribute `current-index`)
- `intervalMs: number = 900` (attribute `interval-ms`)
- `playing: boolean = false` (reflected)
- `loop: boolean = true`
- `hidden: boolean = false` (reflected; re-declared over the native IDL property so Lit's
  change-tracking sees it and auto-pauses on `hidden = true`)

**Methods:** `play()`, `pause()`, `toggle()`, `next()`, `previous()`,
`goTo(currentIndex: number)` — all idempotent/clamped; `itemCount <= 1` is a no-op degenerate case.
`focus(options?)`, `blur()`, and
`click()` forward to the play button.

**Events:** `lr-play`, `lr-pause` (no detail), `lr-sequence-step`
(`detail: LyraSequencePlaybackStepDetail { currentIndex }`, fired on every tick and manual step);
internal `focus`/`blur` are relayed exactly once as owner-realm native `FocusEvent`s (bubbling and
composed, preserving `relatedTarget`).

**Class and event types:** `LyraSequencePlayback`, `LyraSequencePlaybackEventMap`, and
`LyraSequencePlaybackStepDetail`. The former generic `LyraPlayback`, `<lr-playback>`, `length`,
`index`, and `lr-step` names are removed in v9 rather than retained as ambiguous aliases.

**Slots:** none.

**CSS parts:** `base`, `play-button`, `slider`

The `slider` carries supplemental localized `aria-valuetext` — `Step {index} of {total}` (key
`playbackStepPosition`, both numbers formatted with the component's effective locale). Browsers may
ignore that override on a native range, so the interoperable semantic contract is itself one-based
(`min=1`, `max=itemCount`, `value=currentIndex+1`): assistive technologies receive the correct
ordinal even when they expose the native numeric value.

**Themeable custom properties:** `--lr-sequence-playback-icon-size` (default
`calc(var(--lr-icon-button-size) * 0.35)` — the play/pause glyph's size; applied as the button's
`font-size`, and the inline SVG renders at `1em`).
`--lr-sequence-playback-play-button-active-bg` (default
`color-mix(in oklab, var(--lr-color-surface), var(--lr-color-mix-partner) var(--lr-color-mix-active))`) —
the pressed play/pause background; and `--lr-sequence-playback-play-button-active-border-color` (default
`var(--lr-color-brand)`) — its pressed border. Both are inline `var()` fallbacks, so a value set on
the element or an ancestor inherits without being shadowed by a host default. Plus shared tokens `--lr-space-s`,
`--lr-color-border`, `--lr-color-surface`, `--lr-color-text`, `--lr-color-brand`,
`--lr-icon-button-size` (the play button's box), `--lr-opacity-disabled` (play button/slider
dimming at `itemCount <= 1`), `--lr-focus-ring-*`.

**Optional peer deps:** none.

```html
<lr-sequence-playback item-count="24" interval-ms="500"></lr-sequence-playback>
<script>
  const playback = document.querySelector("lr-sequence-playback");
  playback.addEventListener("lr-sequence-step", (event) => {
    renderFrame(event.detail.currentIndex);
  });
</script>
```

**Known gotchas:**

- `currentIndex` is re-clamped into `[0, itemCount)` as soon as `itemCount` shrinks (in
  `willUpdate()`, not waiting for the next `tick()`/`goTo()`/`next()`/`previous()` call) — setting
  `el.itemCount = 2` while `el.currentIndex = 7` immediately pulls `currentIndex` back to `1`, and
  playback auto-pauses if `itemCount` drops
  to `<= 1` while playing (the play button and slider would otherwise both become disabled with no
  way to stop it — both are `?disabled` whenever `itemCount <= 1`, not just the button).
- `intervalMs` is live-reactive mid-playback: ticking is a self-rescheduling `setTimeout` (not one
  long-lived `setInterval`), so `intervalMs` is re-read fresh before every tick — changing
  `interval-ms` while `playing` takes effect on the very next step instead of only after a
  pause/play cycle.
- `itemCount` and `currentIndex` are normalized to finite non-negative integer counts, with
  `currentIndex` clamped into `[0, itemCount)`; fractional, negative, `NaN`, infinite, and oversized
  values cannot poison end conditions or the slider.
- `interval-ms` is clamped to the 16ms floor and the browser's finite timer ceiling: a non-finite or
  lower value ticks at 16ms, while an oversized value uses the timer ceiling.
- Initial `playing` and `item-count` attributes are resolved together on the first update, so
  playback starts consistently regardless of their source order; an invalid final `itemCount <= 1`
  clears the reflected `playing` state.
- No _visible_ "N of M" position label beside the range input; the native one-based range still
  exposes the current ordinal and total bounds, with localized `aria-valuetext` as a supplemental
  enhancement where the platform honors it.
- Calling `play()`/`pause()` programmatically (not via the button) gives no `aria-live`
  announcement of the Play/Pause state change.

---
