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

# `lr-pan-zoom`

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

Scrollable inspection surface for slotted DOM or one image, with bounded zoom and native-scroll
panning. This is the compatibility destination for Lyra's former `lr-zoomable-frame` behavior:
existing consumers that inspect DOM/images should rename the tag to `lr-pan-zoom` (and the class
import to `LyraPanZoom`); `lr-zoomable-frame` now means the mapped iframe component above.

**Properties:**

- `zoom: number = 1` (reflected), `minZoom: number = 0.5`, `maxZoom: number = 4`, and
  `zoomStep: number = 0.25` — bounded, finite zoom configuration
- `src: string = ''` and `alt: string = ''` — optional safe image source. A rejected URL is treated
  as absent and the default slot renders; no empty or unsafe `<img>` replaces that fallback.
- `fit: LyraImageFit = 'actual'` (reflected) — base sizing policy for an image supplied through
  `src`, using the same `contain`/`width`/`actual` vocabulary as `<lr-image-viewer>`. `actual`
  preserves the historical natural-size layout; the other modes resolve against the viewport and
  update with its allocation, including when an image loads after the frame first renders.
- `accessibleLabel: string | null` (attribute `aria-label`) — a declarative host label remains on
  the host while the focusable viewport receives the localized inspection-surface purpose name.
  A property-only value names the viewport and updates reactively without creating a host attribute. This avoids cloning one author label onto both the outer component and nested `role="group"`. An explicitly empty direct property remains empty; a present host attribute, including an empty one, leaves the viewport's localized purpose name intact.

**Methods:** `zoomIn()`, `zoomOut()`, and `resetZoom()` update zoom and emit `lr-zoom-change`
(`detail: { zoom }`). `resetZoom()` preserves pan; `resetView()` also scrolls the viewport to the
origin. Reset reaches 100% exactly whenever it is within `minZoom`/`maxZoom`; it is not quantized to
the nearest `zoomStep`. The viewport accepts `+`/`=`, `-`/`_`, and `0`, without consuming keys from
a slotted editor. The three zoom buttons are independently tabbable inside a labelled `group`; the
container does not claim toolbar arrow-key navigation.

**Slots:** default — inspected content, ignored while `src` renders an image.

`focus(options?)`, `blur()`, and `click()` forward to the scrollable `viewport`, which is the
component's own keyboard target — a bare host `.focus()` would otherwise be a silent no-op.

**Events:** `lr-zoom-change` (`detail: { zoom }`); internal `focus`/`blur` from the viewport are
relayed exactly once as owner-realm native `FocusEvent`s (bubbling and composed, preserving
`relatedTarget`).

**CSS parts:** `base`, `viewport`, `content`, `controls`, `zoom-out`, `zoom-in`, and `reset`. The
`reset` button's visible text is the live zoom percentage, locale-formatted and recomputed from
`zoom` on every render (not a fixed "100%"). Its accessible name includes both the localized reset
action and that visible percentage, so the visible label is contained in the computed name.

**Themeable custom properties:** `--lr-pan-zoom-min-block-size` (default `var(--lr-size-10rem)`)
and the read-only `--lr-pan-zoom-zoom`. The former `--lr-zoomable-frame-min-block-size` and
`--lr-zoomable-frame-zoom` compatibility names were removed in v9; migrate them to the two
`--lr-pan-zoom-*` names. Scaling
uses layout-participating CSS `zoom`, not a paint-only transform, so the viewport's native scroll
range reaches the entire painted footprint at both logical edges in LTR and RTL.

```js
import "@aceshooting/lyra-ui/components/media/pan-zoom/pan-zoom.js";
```

```html
<lr-pan-zoom
  src="map-preview.png"
  alt="Map preview"
  aria-label="Map preview"
></lr-pan-zoom>
```

---
