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

# `lr-page-rail`

- **Import** `import '@aceshooting/lyra-ui/components/lr-page-rail.js';` (stable tag alias; registers the tag)
- **Class** `LyraPageRail`, also available unregistered from `@aceshooting/lyra-ui/components/viewers/page-rail/page-rail.class.js`
- **Family** `components/viewers/` — 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)
- **Deprecations** none
- **Optional peers** none
- **Themeable via** 15 parts, 7 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-page-rail`

A virtualized vertical thumbnail rail for page-addressed documents, with per-page highlight heat
markers. Two modes: **wired** (`viewer`/`for` supply a live page source, e.g. `<lr-pdf-viewer>` or
`<lr-pptx-viewer>` —
thumbnails render lazily as rows materialize, and the rail tracks page/count from the viewer's own
events) and **mediated** (`page-count`/`page` are host-bound directly, rows render a placeholder
glyph — still a fully functional pager). In wired mode the viewer's `page` is the single source of
truth.

**Properties:** `viewer: PageThumbnailSource | null = null` (attribute: false) — the wired viewer.
`for: string = ''` — an id selector alternative to setting `viewer` directly. `pageCount: number = 0`
(attribute `page-count`) and `page: number = 1` (reflected) — mediated-mode page state.
`highlights: readonly LyraHighlight[] = []` (attribute: false) — drives the per-page heat markers;
IDs are trimmed and required to be nonempty, with the first record retained when IDs repeat.
`thumbWidth: number = 96` (attribute `thumb-width`) and `label: string = ''`. A wired
`PageThumbnailSource` provides its one-based `page`, optionally exposes the atomic
`pageViewerSnapshot`/`lr-page-viewer-state-change` protocol, and supplies at least one lazy preview
method: the original `renderPageThumbnail(page, canvas, options?)` for bitmap/canvas sources, or
`renderPageThumbnailToContainer(page, container, options?)` for renderer-owned DOM/SVG previews.
The latter resolves to a `PageThumbnailRenderHandle`; the rail disposes it whenever its row,
viewer, allocation width, status, or document identity changes.

**Events:** `lr-page-select` — a page row was activated (click, or Enter/Space on a focused row).
`detail: { page }`. In wired mode the rail also sets `viewer.page` itself.

If `pageCount` shrinks past the currently focused row, focus moves to the absolute last remaining
page instead of using the rendered window's local index or being lost with the virtualized row.
Rapid consecutive shrinks supersede an in-flight repair, so focus lands on the latest count. The
numeric type-ahead buffer is cleared on detach. Alt/Ctrl/Meta-modified digits are left to browser or
application shortcuts and never enter the buffer.

**CSS parts:** `base` (the rail), `pages` (the embedded `<lr-virtual-list>`), `page` (one page
button), `page-current` (the button for the current `page`), `thumbnail` (the thumbnail canvas/DOM
preview wrapper), `thumbnail-target` (the canvas or renderer-owned DOM preview target), `page-number`
(the visible page number), `heat` (the heat-marker cluster), `heat-dot` (one
heat marker), `heat-dot-accent`, `heat-dot-success`, `heat-dot-warning`, `heat-dot-danger` and
`heat-dot-neutral` (the tone-specific name on each marker), and `heat-dot-overflow` (the `+n`
marker).

Page rows are rendered into the embedded `<lr-virtual-list>`'s own shadow root and forwarded with
`exportparts`, so `lr-page-rail::part(page)` and the rest reach them from a consuming stylesheet.
State variants each carry a second name in the element's part list rather than a state attribute,
because `::part()` cannot be followed by an attribute selector: the current row is
`part="page page-current"` and a danger marker is `part="heat-dot heat-dot-danger"`, and `::part()`
matches with `part~=` semantics, so both names select the same element.

Loading thumbnail skeletons are aria-hidden and non-announcing. Each page button already carries
the localized page name, so thumbnail work does not create one live region per virtualized row.

**Themeable custom properties:** `--lr-page-rail-height` (default `var(--lr-size-24rem)`) — block
size of the virtualized rail. `--lr-page-rail-current-bg` (default `var(--lr-color-brand-quiet)`) —
background of the `page-current` button, kept while the row is hovered so the current page stays
identifiable under the pointer. Each heat-dot tone has its own matching cssprop, all defaulting to
the same shared tone token the marker used before: `--lr-page-rail-heat-accent-color` (default
`var(--lr-color-brand)`, the base `heat-dot` rule shared by the default "accent" tone),
`--lr-page-rail-heat-success-color` (default `var(--lr-color-success)`),
`--lr-page-rail-heat-warning-color` (default `var(--lr-color-warning)`),
`--lr-page-rail-heat-danger-color` (default `var(--lr-color-danger)`), and
`--lr-page-rail-heat-neutral-color` (default `var(--lr-color-text-quiet)`).
