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

# `lr-code-block-core`

- **Import** `import '@aceshooting/lyra-ui/components/lr-code-block-core.js';` (stable tag alias; registers the tag)
- **Class** `LyraCodeBlockCore`, also available unregistered from `@aceshooting/lyra-ui/components/conversation/code-block/code-block-core.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** `shiki` — see `llms/peers.md`
- **Themeable via** 15 parts, 9 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-code-block-core`

A build-lean sibling of `<lr-code-block>` above, for a consumer whose `languages` map already
covers every language it will ever render. Where `<lr-code-block>` unconditionally calls
`loadShikiHighlighter()` — the default ~200-language dynamic-import table loader, whose bundled
lookup table a bundler can't statically narrow away even when a consumer never actually uses it —
this component's own module never imports or calls that function at all. It only ever calls
`loadShikiHighlighterCore(languages)` (shiki's "fine-grained bundle" recipe: `createHighlighterCore()`
plus an explicit oniguruma engine, seeded with _only_ the grammars in `languages`), so a consumer
importing this entry point instead of `code-block.js` gets a build genuinely free of shiki's full
language table.

Removing `code`, `language`, or `highlight-lines` treats that input as absent: source becomes empty,
an absent language selects plain text, and attribute-based emphasis clears. Removal preserves Lit's
`null` property readback; an explicitly empty attribute stays an empty string, and a later value
resumes normal rendering. Highlighted gutter labels and locale-formatted line numbers follow live
`.strings` and inherited or explicit locale changes. Unrelated updates retain the existing
highlighted markup.

A `language` value absent from `languages` always renders the plain `<pre><code>` fallback — there is
no default/full-table highlighter here to fall back to, unlike `<lr-code-block>`'s dynamic-import
path for an unmapped language. That fallback is the _default_ rendering path, not a degraded one,
same as `<lr-code-block>`'s own plain-text fallback. Everything else — `code`/`language`/
`filename`/`copyable`/`collapsible`/`collapsed`/`maxHeight`, the copy button, the collapse header
toggle, the loading-skeleton behavior while the fine-grained highlighter resolves — matches
`<lr-code-block>` exactly, including its CSS parts, themeable custom properties, and stylesheet
(this component reuses `code-block.styles.ts` directly).

**Properties:**

- `code: string = ''` — the raw source text.
- `language: string = ''` — a shiki-recognized language id or alias; when unset, or when it isn't a
  key in `languages`, the code renders as plain unhighlighted text — this component has no
  default/full-table highlighter to fall back to.
- `filename: string = ''` — shown in the header, when set.
- `accessibleLabel: string | null = null` (attribute `aria-label`) — names the internal focusable code-body
  region; otherwise a localized filename/language description is generated.
- `collapsible: boolean = false` (reflected) — shows the collapse/expand chevron button.
- `collapsed: boolean = false` (reflected) — only has a visible effect while `collapsible` is also
  true.
- `copyable: boolean = true` (reflected) — shows the copy-to-clipboard button. Literal HTML
  `copyable="false"` or a `.copyable=${false}` property binding disables it; a
  `?copyable=${false}` boolean-attribute binding cannot override the true default.
- `maxHeight: string = ''` (attribute `max-height`) — a CSS length (e.g. `"20rem"`); once set, the
  code scrolls internally past this height instead of growing the page.
- `lineNumbers: boolean = false` (attribute `line-numbers`, reflected) — displays one-based line
  numbers for highlighted and plain output.
- `highlightLines: string = ''` (attribute `highlight-lines`) — comma-separated 1-based inclusive
  line ranges (e.g. `"3-5,7"`) to visually emphasize. Declarative sugar over `highlights` — merges
  with, and renders identically to, any `line-range` entries there. Malformed segments are ignored
  while valid segments still apply.
- `activatableLines: boolean = false` (attribute `activatable-lines`) — turns the
  (`lineNumbers`-gated) gutter into a roving-tabindex group of buttons emitting `lr-line-activate`.
  Has no effect while `lineNumbers` is unset. If controlled `code` shrinks while a line owns
  focus, focus follows the clamped surviving line; moving focus elsewhere during that update wins.
- `highlights: readonly LyraHighlight[] = []` (attribute: false) — host-supplied highlights to paint over the
  code. Only `line-range` anchors are meaningful here — every other `LyraAnchor` kind is ignored.
- `activeHighlightId: string | null = null` (attribute `active-highlight-id`) — the `highlights`
  entry, if any, currently treated as active (`data-active` on its lines).
- `anchorKinds: LyraAnchor['kind'][] = ['line-range']` — readonly, for the shared anchor-target
  contract, identical to `<lr-code-block>`.
- `languages: Record<string, ShikiLanguageSource> = {}` (attribute: false) — grammar definitions this
  instance can highlight, e.g. `{ json: jsonGrammar }` (import from `shiki/langs/<name>.mjs`), or a
  lazy loader per key, e.g. `{ bash: () => import('@shikijs/langs/bash') }` — called at most once
  per key, memoized, the first time a fenced block actually requests that language. Empty
  (the default) never highlights at all — every `language` renders the plain-text fallback, and so
  does a key whose lazy loader rejects.
  If `languages` changes while highlighting is loading, only results for the current map can update
  the displayed code. For a
  TypeScript annotation, use `import type { ShikiLanguageSource } from
'@aceshooting/lyra-ui/components/conversation/code-block/code-block-core.js'`; the type-only
  granular import emits no registration side effect.
- `copyAppearance: 'text' | 'icon' = 'text'` (attribute `copy-appearance`, reflected) — identical to
  `<lr-code-block>`'s own property: `'text'` is the labelled button, `'icon'` swaps the visible label
  for a compact glyph and promotes the same localized string to the control's accessible name

**Methods:** `scrollToAnchor(target)` — resolves a `line-range` anchor (or a `highlights` id string
resolving to one) by scrolling its start line into view within `[part="body"]`; resolves `false`
when the anchor isn't a `line-range`, the id isn't found, or the start line is out of bounds.
`refreshTheme(): void` re-reads the resolved theme for syntax highlighting.
Identical behavior to `<lr-code-block>`'s own method.

**Events:** the same fulfilled-only `lr-copy`, generic `lr-error`, full `lr-copy-error`, cancelable
`lr-toggle-request`, committed `lr-toggle`, and `lr-line-activate` contracts as `<lr-code-block>`;
`lr-text-select` (`detail: { text, anchor, rects }` — a text selection inside the code
body ended; `anchor` is a `line-range` anchor covering the selected lines).

**Slots:** `header-actions` — identical to `<lr-code-block>`'s own slot.

**CSS parts:** `base`, `header`, `filename`, `language`, `copy-button`, `copy-button-text`,
`copy-button-icon`, `copy-button__control`, `header-actions`, `toggle`, `body`, `pre`,
`code`, `line-highlight`, `line-button` — identical set to `<lr-code-block>`.

**Themeable custom properties:** identical to `<lr-code-block>` — `--lr-code-block-max-height`
(independently settable; an authored `max-height` attribute wins inline),
`--lr-code-block-font`, `--lr-code-block-tab-size` (default `2`, applied to `[part='pre']`),
`--lr-code-block-active-line-outline-color` (default `var(--lr-color-brand)`),
`--lr-code-block-highlighted-line-bg` (default `var(--lr-color-warning-quiet)`),
`--lr-code-block-language-bg` (default `var(--lr-color-brand-quiet)`), and
`--lr-code-block-language-color` (default `var(--lr-color-brand)`), plus the same shared
tokens, including the `--lr-theme-scrollbar-width`/`--lr-theme-scrollbar-gutter` hooks `body` honors —
this component reuses `<lr-code-block>`'s stylesheet, so both share exactly the same scroll
container. The last five are inline `var()` fallbacks at the point of use rather than `:host`
declarations, so a page-, container-, or theme-level value reaches them; see `<lr-code-block>` above
for the full rationale, including why `<lr-markdown>`/`<lr-markdown-core>` must declare the tab-size
fallback separately. `base` is a flex column and `body` grows to fill whatever block space a
definite-height host gives it too, identically to `<lr-code-block>` above.

**Optional peer deps:** `shiki` (specifically its `shiki/core`, `shiki/engine/oniguruma`, the binary
`shiki/onig.wasm` asset (the default; select `shiki/engine/javascript` instead via
`setShikiCoreEngine('javascript')`), and `shiki/themes/github-{light,dark}.mjs` subpaths — never
`shiki`'s main entry point, which is what carries the ~200-language table this component exists to
avoid). Building the
fine-grained highlighter is cached per `languages` object identity. A bounded weak cache also shares
recently used equivalent, deeply frozen plain grammar maps, including the detached snapshots owned
by separate component instances. Grammar contents and property/array order must agree completely;
mutable or unusual inputs keep identity-only caching. Highlighters returned by the public loader
can therefore be shared across equivalent frozen maps. Passing a stable module-level `languages`
constant also avoids repeated component snapshots.

```ts
import { html } from "lit";
import jsonGrammar from "shiki/langs/json.mjs";
import "@aceshooting/lyra-ui/components/conversation/code-block/code-block-core.js";

const languages = { json: jsonGrammar };
const view = html`<lr-code-block-core
  language="json"
  .languages=${languages}
  .code=${'{"ok": true}'}
></lr-code-block-core>`;
```

**Known gotchas:**

- there is no default highlighter and no dynamic-import fallback table — a `language` you haven't
  added to `languages` will never highlight, no matter how common that language is elsewhere. Reach
  for `<lr-code-block>` instead if you need to support an open-ended set of languages without
  pre-declaring each one.
- Pass a stable, module-level `languages` constant to avoid repeated snapshots on parent renders.
  Equivalent frozen maps can share a recently used highlighter, but that bounded weak reuse is an
  optimization: callers must not depend on distinct frozen map objects producing distinct cores.

---
