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

# `lr-dock-panel`

- **Import** `import '@aceshooting/lyra-ui/components/lr-dock-panel.js';` (stable tag alias; registers the tag)
- **Class** `LyraDockPanel`, also available unregistered from `@aceshooting/lyra-ui/components/layout/dock-panel/dock-panel.class.js`
- **Family** `components/layout/` — 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** none
- **Themeable via** 4 parts, 5 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-dock-panel`

A single panel docked to one edge of whatever contains it, resizable by dragging its inner edge.
First-party invention (no `wa-*`/`sl-*` counterpart). Unlike `lr-multi-split` (which owns and lays out N
sibling panels, and requires restructuring a layout so every panel becomes its direct child), this is
one self-contained element you drop next to your existing content — typically as an absolutely-
positioned child of a `position: relative` parent, or as a flex item alongside a main-content sibling.
It deliberately imposes no `position`/`inset` of its own: it only manages its own size along the
resize axis (`inline-size` for `start`/`end`, `block-size` for `top`/`bottom`) and fills 100% of the
cross axis, leaving where it sits in the page entirely up to the consumer's own layout. `lr-multi-split`
stays the right primitive for the multi-sibling-panel case; this is the primitive for the single-edge-
docked case.

**Properties:**

- `edge: 'start' | 'end' | 'top' | 'bottom' = 'end'` (reflected) — which edge of the panel's own
  container it's docked to. `start`/`end` are logical-inline (mirror left/right depending on writing
  direction); `top`/`bottom` are block-direction and unaffected by RTL.
- `extent: string = '280px'` — the current docked size along the resize axis, as a CSS length.
- `minExtent: string = '160px'` (attribute `min-extent`) — minimum resize bound, as a CSS length.
- `maxExtent: string = ''` (attribute `max-extent`) — maximum resize bound. Empty means "no explicit
  cap": the live extent of the containing element is used instead (falling back to the viewport if
  there's no parent, e.g. not yet connected). An explicit maximum is still capped to that live
  containing extent, and an effective minimum above the maximum is reduced to the maximum, so the
  separator always exposes `min <= now <= max`.
- `collapsible: boolean = false` (reflected)
- `collapsed: boolean = false` (reflected)
- `resizable: boolean = true` (reflected) — when `false`, no drag handle renders at all and the panel
  is a fixed size. Its string-aware converter accepts `resizable="false"` as false despite the
  true default. A Lit property binding (`.resizable=${false}`) also disables it; a false
  boolean-attribute binding (`?resizable=${false}`) only removes the attribute and cannot override
  a true-defaulting property.

**Renamed in 8.0.0: `size`/`min-size`/`max-size` are now `extent`/`min-extent`/`max-extent`**, and
the then-current resize detail key moved with them (`{ size }` → `{ extent }`). Everywhere else in the library
`size` names a tier on the shared six-step ladder; here it was an arbitrary CSS length, which is the
collision the rename resolves. It is a clean rename with no alias, and it fails quietly in both
directions: `size="320px"` is now an unknown attribute the browser ignores, so the panel silently
renders at the `280px` default, and `event.detail.size` reads `undefined`.

**Exported types:** `LyraDockPanelEdge = 'start' | 'end' | 'top' | 'bottom'`, readonly
`LyraDockPanelResizeDetail = { extent: string }`, readonly
`LyraDockPanelCollapseChangeDetail = { collapsed: boolean }`, and `LyraDockPanelEventMap`.
The former dock-specific `parseLengthPx()` export is removed; dock length resolution is now a
private adapter over the library's canonical CSS-length resolver, with container/viewport units
resolved in the host's owner realm.

**Events:**

- `lr-resize-request` (cancelable; `detail: { extent }` is the proposed `px` CSS length string),
  fired before a discrete keyboard step commits and before a pointer drag's final settle commits.
  Call `preventDefault()` to reject it: a keyboard step simply does not apply, and a drag's final
  settle snaps the panel back to the size it had before that drag gesture began. Not fired for a
  continuous pointer drag's own intermediate ticks — checking a cancelable event on every
  `pointermove` would make a live drag visibly stutter — only its final settle on release.
- `lr-resize-input` — frozen `detail: { extent }` (a `px` CSS length string), fired for each genuine
  pointer or keyboard value transition. Fully clamped/no-op attempts emit nothing.
- `lr-resize-change` — a fresh frozen detail snapshot, fired exactly once on genuine `pointerup`
  after at least one value transition and the drag's `lr-resize-request` was not prevented, and
  after each genuine keyboard step whose own `lr-resize-request` was not prevented. `pointercancel`,
  lost capture, disconnect/adoption, live policy/geometry mutation, no-op attempts, and a prevented
  `lr-resize-request` all emit nothing.
- `lr-collapse-request` (cancelable; `detail: { collapsed }` is the state proposed by the built-in
  collapse toggle. Call `preventDefault()` to leave `collapsed` unchanged. Not fired when a
  consumer assigns `collapsed` directly), `lr-collapse-change` (non-cancelable; `detail: {
collapsed }` is the accepted built-in-toggle state. Not fired when a consumer assigns `collapsed`
  directly). Both details are fresh readonly/frozen snapshots.

The Lyra-original v9 event migration is mechanical: listen for `lr-resize-input` for live layout
feedback and `lr-resize-change` for persistence/telemetry instead of the removed `lr-resize` name.
Type imports likewise move from `DockPanel*` to `LyraDockPanel*`.

**Slots:** default — the panel's own content.

**CSS parts:** `base` (the panel root), `content` (wraps the default slot; hidden while `collapsed`),
`handle` (the draggable resize edge; only rendered when `resizable` and not `collapsed`),
`collapse-toggle` (only rendered when `collapsible`)

**Themeable custom properties:** `--lr-dock-panel-collapsed-size` (default
`var(--lr-icon-button-size)`) — the persistent "rail" width/height the panel holds at while
`collapsed`, rather than collapsing to zero (a zero-size collapsed panel would have nowhere to host
the re-expand toggle); component-specific since collapse never zeroes the box. The `collapse-toggle`
button and the resize `handle` used to share the bare `--lr-color-brand`/`--lr-color-brand-quiet`
tokens for their hover/pressed feedback even though they're unrelated visual purposes (button
affordance vs. drag affordance); each now has its own scoped override, all still defaulting to the
exact same colors as before: `--lr-dock-panel-collapse-toggle-hover-bg` (default
`var(--lr-color-brand-quiet)`) and `--lr-dock-panel-collapse-toggle-hover-color` (default
`var(--lr-color-brand)`) theme the toggle's hover state and are reused verbatim (color) or through
`color-mix()` (background) for its pressed state; `--lr-dock-panel-handle-hover-color` (default
`var(--lr-color-brand)`) themes the handle's hover/keyboard-focus state, and
`--lr-dock-panel-handle-active-color` (default a `color-mix()` of the hover color) themes it while
being dragged. Plus shared tokens `--lr-color-surface`, `--lr-color-border`, `--lr-color-brand`,
`--lr-color-brand-quiet`, `--lr-color-text`, `--lr-radius`, `--lr-space-xs`,
`--lr-focus-ring-width/-color/-offset`, `--lr-transition-fast`, `--lr-icon-button-size`.

**Optional peer deps:** none.

```html
<div style="position: relative; block-size: 100vh;">
  <lr-dock-panel edge="end" extent="320px" min-extent="200px" max-extent="480px" collapsible>
    <div>Sidebar content — a chat thread list, an inspector, anything.</div>
  </lr-dock-panel>
</div>
<script type="module">
  const panel = document.querySelector("lr-dock-panel");
  panel.addEventListener("lr-resize-input", (e) => updateLayoutPreview(e.detail.extent));
  panel.addEventListener("lr-resize-change", (e) => persistExtent(e.detail.extent));
  panel.addEventListener("lr-collapse-change", (e) => console.log(e.detail.collapsed));
</script>
```

Pointer-drag-resize admits only a primary pointer using its primary button, then mirrors
`lr-multi-split`'s pointer-capture technique (`pointerdown` captures the pointer on the handle;
`pointermove` computes a new size; `pointerup`/`pointercancel`/`lostpointercapture` all release it,
since a drag can end without a clean `pointerup`) but reasons in raw pixels throughout rather than
percent. A genuine interaction — pointer movement or a keyboard step
(<kbd>ArrowLeft</kbd>/<kbd>ArrowRight</kbd> for the inline axis, <kbd>ArrowUp</kbd>/<kbd>ArrowDown</kbd>
for the block axis, 16px per step) — commits `extent` as a rounded `px` string regardless of what
unit `extent`/`min-extent`/`max-extent` were originally expressed in. Passive container/bounds
reconciliation emits neither resize event and preserves an in-range authored relative unit.

**Known gotchas:**

- `collapsed` doesn't zero the panel's box — it shrinks to the persistent rail size
  (`--lr-dock-panel-collapsed-size`). `extent` itself is left untouched while collapsed, so
  re-expanding restores what it was unless the current bounds require a valid clamp.
- Parent or flex allocation shrink, direct out-of-range property writes, and live min/max changes
  reconcile atomically. A later container grow does not silently restore an extent that was clamped
  during shrink.
- `handle` only renders while `resizable && !collapsed`; `collapse-toggle` only renders while
  `collapsible` — a panel with both `false` renders neither control, just fixed-size slotted content.
  `resizable` and `collapsed` interact: dragging is disabled whenever `collapsed` is `true`, even if
  `resizable` is also `true`.
- The collapse-toggle's chevron rotates to point toward the panel's pinned edge when expanded (the
  direction clicking it will shrink toward) and away from it when collapsed — this is folded through
  both `edge` and, for `start`/`end`, current RTL-ness, so the same markup visually flips correctly
  under `dir="rtl"` with no extra author work.

---
