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

# `lr-spreadsheet-viewer`

- **Import** `import '@aceshooting/lyra-ui/components/lr-spreadsheet-viewer.js';` (stable tag alias; registers the tag)
- **Class** `LyraSpreadsheetViewer`, also available unregistered from `@aceshooting/lyra-ui/components/viewers/spreadsheet-viewer/spreadsheet-viewer.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** `xlsx` — see `llms/peers.md`
- **Themeable via** 13 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-spreadsheet-viewer`

Fetches and renders `.xlsx` and `.xls` workbooks with the optional `xlsx` (SheetJS) peer. Multiple
worksheets render through a `<lr-tab-group>` switcher, and body rows use `<lr-virtual-list>`.

Adopts `DocumentAnchorTarget`: a `cell-range` anchor addresses one sheet's raw grid, 1-based, with
its header row included, resolving the target sheet from the anchor's own `sheet` field (falling
back to a `Sheet!`-prefixed `range`, then the active sheet); `scrollToAnchor()` switches
`<lr-tab-group>`'s active tab first when needed, then scrolls the addressed row/column into view.
`highlights` paint as a focusable `part="cell-highlight"`. A jump whose workbook is replaced by a
concurrent `src` reassignment mid-flight reports `found: false` rather than a phantom success —
matching `lr-csv-viewer`/`lr-dataset-viewer` (it wrongly reported `found: true` before 9.0.0).

**Properties:** `src: string = ''` and `name: string = ''`. `maxHeight: string = ''` (attribute
`max-height`) is a CSS length that caps the scrollable body — setting it writes
`--lr-spreadsheet-viewer-max-height` inline on `[part="base"]`; invalid CSS `max-height` values are
ignored. `anchorKinds: readonly LyraAnchorKind[] = ['cell-range']` (this viewer's supported
`LyraAnchor.kind` values for the shared anchor-target contract).

**Methods:** `search(query)` resolves the match count across every sheet's stringified cell values,
ordered sheet then row then column, switching tabs as navigation crosses sheets (empty/whitespace
query behaves like `clearSearch()`); `searchNext()`/`searchPrevious()` advance/step back through
matches (wrapping, resolving `false` when there are none); `clearSearch()` clears the query,
matches, and painted marks.

**Events:** `lr-render-error` with `detail.error` when fetching or parsing fails.
`lr-highlight-activate` (`detail: { highlightId }`) — a `highlights` cell was clicked or activated via
Enter/Space. `lr-anchor-result` (`detail: { found }`) — fired after an `anchor` assignment or a
`scrollToAnchor()` call. `lr-search-change` (`detail: { query, matchCount, matchCountExact, activeIndex }`) — from
search/navigation/clear, canonical source reset, and effective-locale re-evaluation. `lr-text-select` is not part of this
grid viewer's event contract; its registry capabilities advertise `textSelect: false`.

**CSS parts:** `base`, `body` (the scrollable wrapper around the fetched-state content, capped by
`max-height`), `tabs`, `sheet`, `header-row`, `data-row`, `cell`, `cell-highlight` (a
structural cell covered by a `highlights` entry), `cell-highlight-action` (the native button
filling a highlighted cell; focusable and emits `lr-highlight-activate`; its complete accessible
name uses the localized `cellHighlightWithLabel` message with independent `{value}` and `{label}`
placeholders), `rows`, `spinner`, `error`, and `anchor-live-region` (an aria-hidden, non-live shadow
mirror of the latest anchor-jump message; the spoken copy is appended to the shared document-level
polite sink only while the viewer and its composed ancestors are exposed to the accessibility tree).
`data-row`, `cell`, `cell-highlight`, and `cell-highlight-action` are rendered inside the internal
`<lr-virtual-list>` and forwarded via
`exportparts`, so `lr-spreadsheet-viewer::part(cell)` reaches them from a consumer stylesheet.
The spinner always includes visible localized loading text alongside its decorative ring; the text
remains understandable without CSS or animation and the ring stops under reduced motion.

**Themeable custom properties:** `--lr-spreadsheet-viewer-highlight-color` (default
`var(--lr-color-brand)`) — the outline color of a `cell-highlight` cell. The cell matching
`activeHighlightId` receives a private warning-color default because a `[data-active]` selector
can't be chained onto the `::part(cell-highlight)` the cell reaches this component's stylesheet
through. An inherited or direct public value remains authoritative across that boundary.
`--lr-spreadsheet-viewer-highlight-outline-offset` (default
`calc(-1 * var(--lr-border-width-medium))`) — the outline offset of a highlighted cell.
`--lr-spreadsheet-viewer-max-height` (default `none`) — maximum block size of `[part="body"]`
before it scrolls internally; also settable via the `maxHeight` property, which writes this token
inline on `[part="base"]`.

**Optional peer dependency:** install `xlsx` with `pnpm add https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz`. The official CDN matches the
`.xlsx` and `.xls` MIME types and filename extensions.

Importing `spreadsheet-viewer.js` (this component's own registration entry) loads
`<lr-spreadsheet-viewer>`'s class module immediately. A granular consumer (not importing the
`all.js` compatibility bundle) who wants that deferred too can instead import
`@aceshooting/lyra-ui/components/viewers/spreadsheet-viewer/spreadsheet-viewer-register.js`, which
installs the same registration lazily and exports `SPREADSHEET_VIEWER_TAG`
(`'lr-spreadsheet-viewer'`) as a stable reference to the tag it eventually registers.

Remote resources are capped at 25 MB, each parsed sheet at 10,000 rows and 1,000 columns, and each
workbook at 256 sheets and 1,000,000 aggregate expanded cells. Row limits are per sheet, not
cumulative across a workbook. Exceeding any ceiling surfaces the localized
`documentPreviewResourceTooLarge` message instead of the workbook.
