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

# `lr-notebook-viewer`

- **Import** `import '@aceshooting/lyra-ui/components/lr-notebook-viewer.js';` (stable tag alias; registers the tag)
- **Class** `LyraNotebookViewer`, also available unregistered from `@aceshooting/lyra-ui/components/viewers/notebook-viewer/notebook-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** `dompurify`, `katex`, `marked`, `shiki` — see `llms/peers.md`
- **Themeable via** 21 parts, 8 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-notebook-viewer`

Read-only Jupyter notebook (nbformat 4.x) renderer, composing existing components per cell.
Execution is a hard non-goal. Markdown cells render through `lr-markdown`, code cells through
`lr-code-block` (using the notebook's kernel language for syntax highlighting), and raw cells as
plain preformatted text. A code cell's `execute_result`/`display_data` outputs prefer, in order,
`image/png`, `image/jpeg`, `image/svg+xml` (sanitized), `text/html` (sanitized), `application/json`
(via `lr-json-viewer`), then `text/plain`. Stream/error outputs render as preformatted text (tinted
`danger` for stderr/tracebacks) with embedded ANSI SGR color/style escapes interpreted through the
same shared `internal/ansi.ts` parser `lr-terminal` uses — a traceback keeps its coloring instead of
showing raw `ESC[` sequences. Sanitizing raw HTML/SVG output markup lazy-loads the
optional peer `dompurify`; without it, the output renders a localized notice instead of raw markup.
Sanitized `text/html` output uses the passive-document profile: anchors, form controls, and custom
elements are unwrapped to ordinary text/children where safe, remote navigation/resource attributes
are removed, and only inline base64 raster image sources render. Sanitized `image/svg+xml` output
is likewise network-silent and non-interactive: animation is removed and only same-document
fragment references or inline base64 raster image references survive.
Cells are virtualized through `lr-virtual-list`. `node-path` anchors resolve `path[0]` as a cell
index; `fragment` anchors resolve a cell's own `id`. For cells with IDs, successful public anchor
navigation scrolls the matching virtual row into the notebook allocation and applies active-cell
paint using that same ID. Repeating a target scrolls back to it after manual scrolling.
`maxHeight` also bounds the child virtual viewport. No execution, no kernels, no editing, no
ipywidgets.

**Properties:** `src: string = ''` — URL to fetch and parse as a notebook; ignored while `notebook`
is present. `notebook?: Readonly<NotebookDoc> | string` (property only) — an already-parsed
notebook document, or its raw JSON text; presence wins over `src` (including an empty string) and
is parsed synchronously. Parsed document assignments are synchronously clone-owned and recursively
frozen; mutate a copy and reassign it to update the viewer, because later source-object mutation is
not observed.
Assigning `undefined` clears inline authority and immediately reloads the already configured `src`,
or exposes the idle state when no URL exists. `source: LyraNotebookViewerSource` is a readonly
discriminated snapshot (`{ kind: 'inline', value }`, `{ kind: 'url', url }`, or `null`).
`name: string = ''` —
accessible label, and matched against a `fragment` anchor's cell id. `outputCollapseLines: number =
40` (attribute `output-collapse-lines`) — a plain-text output longer than this many lines renders
collapsed behind a toggle; `0` disables collapsing. `maxHeight: string = ''` (attribute
`max-height`) — once set, the notebook scrolls internally past this height; invalid CSS
`max-height` values, declaration breaks, and `url()` are ignored. `anchorKinds: readonly
LyraAnchorKind[] = ['node-path', 'fragment']` (this viewer's supported `LyraAnchor.kind` values for
the shared anchor-target contract). The inherited carrier fields `highlights: readonly LyraHighlight[] = []`
(property only) and `activeHighlightId: string | null = null` (attribute `active-highlight-id`)
paint every highlight entry that resolves to a cell index (capped at 100 painted highlights), each
tinted by its tone and, when its `id` matches `activeHighlightId`, outlined. Use
`anchor: LyraAnchor | string | null = null` or `scrollToAnchor()` for notebook-cell navigation.

**Methods:** `search(query)` resolves the match count over cell sources and text outputs — a
matching cell counts as one match (empty/whitespace query behaves like `clearSearch()`);
queries are capped at 4,096 code units and one pass at 4,000,000 source/output code units, with
`matchCountExact: false` reporting a truncated lower bound;
`searchNext()`/`searchPrevious()` advance/step back through matches, scrolling to and marking the
target cell with the persistent active-cell paint, and each resolves `true` once the active match
moved or `false` when there are none — the same `Promise<boolean>` every other searchable viewer
resolves, so one find-in-page host can drive them all; `clearSearch()` clears the query and
matches.

**Events:** `lr-load` — `detail: { cellCount, language }`, fired once a notebook has been parsed
and validated (`language` from `metadata.language_info.name`/`kernelspec.language`, else `''`).
`lr-search-change` — `detail: { query, matchCount, matchCountExact, activeIndex }`, including
canonical source reset and effective-locale re-evaluation. `lr-render-error` —
`detail: { error }`, fetching, parsing, or validating the notebook failed. `lr-anchor-result` —
non-cancelable; `detail: { found: boolean }`, fired after an `anchor` assignment or a
`scrollToAnchor()` call is applied.

Neither `lr-highlight-activate` nor `lr-text-select` is part of `lr-notebook-viewer`'s event
contract: highlights are painted but not click-to-activate, and no selection binding is installed.
Use `anchor` plus `lr-anchor-result` for notebook cell navigation outcomes.

**CSS parts:** `base` (the root scroll container), `cell` (`data-cell-type="code|markdown|raw"`,
`data-active`), `cell-active` (added alongside `cell` on the cell currently targeted by an anchor
or the active search match),
`cell-highlighted` (added alongside `cell` on a cell matched by a `highlights` entry, always paired
with a tone-specific `cell-highlighted-<tone>` part below), `cell-highlighted-accent` (the default
tone), `cell-highlighted-success`, `cell-highlighted-warning`, `cell-highlighted-danger`,
`cell-highlighted-neutral`, `cell-highlight-active` (added alongside `cell-highlighted`/
`cell-highlighted-<tone>` when the matched highlight's `id` equals `activeHighlightId`),
`cell-gutter` (the `In [n]`/`Out [n]` label column), `cell-source`, `raw-source` (the horizontally
scrollable preformatted surface for a raw cell), `outputs`, `output`
(`data-output-type`, `data-stream`), `output-error` (added alongside `output` on a stderr stream or
an error output), `error-output-label` (the label introducing an error output's traceback),
`output-toggle`, `error`, `spinner`, 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). The
highlight/active state variants are separate part _names_ rather than attribute selectors, because
Shadow Parts forbids an attribute selector after `::part()`.
The document-level 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.

Every cell-level part above is rendered into the embedded `<lr-virtual-list>`'s own shadow root and
forwarded back out through `exportparts`, so `lr-notebook-viewer::part(cell)` and friends work from
a consumer stylesheet. The three state variants are separate part _names_ rather than attribute
selectors, because Shadow Parts forbids an attribute selector after `::part()` —
`::part(cell)[data-active]` is invalid CSS, so use `::part(cell-active)`. The `data-*` attributes
remain on the elements for scripting.

**Themeable custom properties:** `--lr-notebook-viewer-max-height` (default `none`).

`--lr-notebook-viewer-active-bg` (default `var(--lr-color-brand-quiet)`) is the background of the
cell currently targeted by an anchor or the active search match — the `cell-active` part. It is an
inline `var()` fallback at the point of use rather than a `:host` declaration, so it can be set on
the element or on any ancestor.

`--lr-notebook-viewer-highlight-accent-background` (default `var(--lr-color-brand-quiet)`),
`--lr-notebook-viewer-highlight-success-background` (default `var(--lr-color-success-quiet)`),
`--lr-notebook-viewer-highlight-warning-background` (default `var(--lr-color-warning-quiet)`),
`--lr-notebook-viewer-highlight-danger-background` (default `var(--lr-color-danger-quiet)`), and
`--lr-notebook-viewer-highlight-neutral-background` (default `var(--lr-color-surface-raised)`) are
each tone's highlighted-cell background. `--lr-notebook-viewer-highlight-active-outline` (default
`var(--lr-focus-ring-color)`) outlines the highlighted cell whose highlight `id` equals
`activeHighlightId`.

**Optional peer deps:** `marked`+`dompurify` (markdown cells, falls back to plain text per cell),
`shiki` (code cells, falls back to unhighlighted), `dompurify` (HTML/SVG outputs, falls back to
`text/plain`).

```html
<lr-notebook-viewer max-height="30rem"></lr-notebook-viewer>
<script type="module">
  const viewer = document.querySelector("lr-notebook-viewer");
  viewer.notebook = result;
  viewer.addEventListener("lr-load", (e) => console.log(e.detail.cellCount, "cells"));
</script>
```

A notebook major version outside 4.0–4.5, an invalid shape, or more than 2,000 cells renders a
localized error and fires `lr-render-error` instead of the notebook.

PNG/JPEG outputs use their `text/plain` representation as alt text, falling back to a localized
code-cell name. Sanitized SVG output is wrapped in a named `role="img"` with the same fallback.
