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

# `lr-popup`

- **Import** `import '@aceshooting/lyra-ui/components/lr-popup.js';` (stable tag alias; registers the tag)
- **Class** `LyraPopup`, also available unregistered from `@aceshooting/lyra-ui/components/overlays/popup/popup.class.js`
- **Family** `components/overlays/` — 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** 4 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-popup`

The low-level anchored-positioning primitive `lr-popover`, `lr-dropdown` and `lr-tooltip` are built
on. Mirrors `wa-popup` / `sl-popup`. **New in 8.0.0** — the positioning logic already existed as an
internal module, but a migrating consumer had no public element to rename `wa-popup`/`sl-popup` to
and had to reimplement it by hand.

It positions its default slot against an anchor and keeps the two aligned through scroll, resize and
layout change — and does nothing else. **No dismiss behaviour, no focus management, no ARIA
relationship, no trigger semantics.** Those are policy, and each of the three overlays above layers
its own. Reach for `lr-popup` when you need a floating surface the library does not already ship (an
anchored inline editor, a custom autocomplete list); if you find yourself adding light dismiss and
focus return on top of it, use `lr-popover` instead.

**Anchoring**, in precedence order: legacy `virtualAnchor` (an arbitrary rect — a canvas hit, chart
datum, or selection range), mapped `anchor` (an `Element`, same-root id string, or Floating UI
virtual element), `for` (a same-root id), then the first element assigned to the `anchor` slot. A
disconnected element or dangling id falls through to the next source. Id insertion, removal,
replacement and transfer, plus direct and forwarded slot changes, are tracked live.

**Properties:**

- `active: boolean = false` (reflected) — requests positioning and paint. It remains the caller's
  intent when an anchor is temporarily unavailable; the popup and optional hover bridge stay
  hidden and non-interactive until the currently resolved anchor has completed placement.
- `anchor: Element | string | VirtualAnchor | null = null`, `for: string = ''` (reflected), and
  `virtualAnchor` (property only) — the non-slot anchors, in the precedence order above. For a
  plain virtual rect, omitted `width`/`height` default to zero, negative dimensions clamp to zero,
  and any non-finite coordinate or dimension makes that highest-priority rect inert so it cannot
  corrupt layout or paint stale popup chrome.
- `placement: Placement = 'top'` (reflected) — the full Floating UI vocabulary, mirrored
  under RTL. The shared positioner's physical coordinates remain authoritative in either
  direction, so RTL never stretches a fixed-width popup against an opposite logical inset.
- `strategy: 'absolute' | 'fixed' = 'absolute'` (reflected) — the CSS positioning scheme. `fixed`
  normally positions relative to the viewport, but ancestors using transforms, filters or
  containment can establish a different containing block, and ancestor clipping may still apply.
  `absolute` positions relative to its containing block and scrolls with its containing content.
- `distance: number = 0` — offset from the anchor along the placement axis, in px
- `skidding: number = 0` — offset along the anchor's edge, in px
- `flip: boolean = false` (not reflected), with `flipFallbackPlacements: string = ''` (attribute
  `flip-fallback-placements` — a
  space-delimited placement list `flip` tries in order instead of just the opposite side;
  unrecognized entries are dropped rather than forwarded), `flipFallbackStrategy: 'best-fit' |
'initial' | 'initial-placement' = 'best-fit'` (attribute `flip-fallback-strategy` — what `flip` settles on
  when no candidate fits: the least-overflowing one, or `placement` as written),
  `flipBoundary: PlaceBoundary | null = null` (property only — element(s) to measure overflow
  against instead of the popup's clipping ancestors) and `flipPadding: number = 0` (attribute
  `flip-padding`)
- `boundary: 'viewport' | 'scroll' = 'viewport'` (not reflected) — shared overflow boundary for flip,
  shift, and auto-size; each middleware-specific boundary below overrides it independently
- `shift: boolean = false` (not reflected), with
  `shiftBoundary: PlaceBoundary | null = null` (property only) and
  `shiftPadding: number = 0` (attribute `shift-padding`)
- `padding: number = 0` — boundary padding kept clear by `shift` and by the available-size
  measurement
- `autoSize: 'horizontal' | 'vertical' | 'both' | null = null` (attribute `auto-size`), with
  `autoSizeBoundary: PlaceBoundary | null = null` (property only) and
  `autoSizePadding: number = 0` (attribute `auto-size-padding`). The popup is _always_ capped by the
  available space it publishes as `--lr-positioner-available-inline-size` /
  `--lr-positioner-available-block-size`; `auto-size` re-measures the named axes against
  `auto-size-boundary`/`auto-size-padding` instead of the shared `padding`, so it narrows or widens
  that cap rather than introducing one. An unrecognized value is inert rather than half-applied.
- `sync: 'width' | 'height' | 'both' | null = null` — copies the anchor's inline size, block size,
  or both onto the popup. An unrecognized value is inert, for the same reason.
- `hoverBridge: boolean = false` (attribute `hover-bridge`, not reflected) — renders an invisible quad
  across the `distance` gap, so a pointer travelling between anchor and popup never leaves both at
  once. Purely geometric: this element owns no hover policy of its own, the component built on top
  reads the hover.
- `arrow: boolean = false` (not reflected), `arrowPlacement: 'anchor'|'start'|'end'|'center' = 'anchor'`
  (attribute `arrow-placement`) and `arrowPadding: number = 10` (attribute `arrow-padding`) — the
  shared arrow trio described at the top of this family

The aligned v8 defaults above are source-contract exact. To preserve the previous Lyra-shaped
geometry explicitly, use `placement="bottom-start" strategy="fixed" distance="4" flip shift`;
origin-aware migration rewrites emit those tokens instead of relying on changed defaults.

`popup: HTMLElement` is the positioned, shadow-owned popup node. Its setter exists for the writable
WA/SL public TypeScript contract, but assignments are intentionally ignored: replacing that node
would disconnect positioning, animation, and the documented CSS parts. Read it to animate or style
the live internal node.

**Methods:** `reposition()` — recompute now. Rarely needed, since the popup already tracks scroll,
resize, layout and live DOM-anchor identity changes; useful after moving a virtual anchor
imperatively.

**Events:** `lr-reposition` — `detail: { placement }`, the placement actually used after `flip`.

**Slots:** `anchor` (the element to position against), default (the floating content).

**CSS parts:** `anchor`, `popup`, `arrow`, and `hover-bridge` (the invisible quad, rendered only
while `hover-bridge` is set). `popup` carries the **resolved side** as a second part token
(`top`/`bottom`/`left`/`right`), so `::part(popup bottom)` styles one side —
`::part(popup)[data-side]` would silently never match. `arrow` carries its own resolved side the
same way (`arrow-top`, `arrow-bottom`, `arrow-left`, `arrow-right`).

**Themeable custom properties:** mapped `--arrow-size`, `--arrow-color`, `--popup-border-width`,
`--show-duration`, and `--hide-duration`; retained `--lr-popup-arrow-size` is the arrow-size
fallback. Read-only `--auto-size-available-width` / `--auto-size-available-height` mirror the
positioner's available dimensions. Shared tokens cover stacking, raised surface, and border.

The popup also receives `--lr-positioner-available-inline-size` / `--lr-positioner-available-block-size`
from the shared positioner and caps its dimensions to the measured available space.

```html
<lr-popup active arrow placement="top" distance="8">
  <button slot="anchor">Anchor</button>
  <div class="panel">Positioned content</div>
</lr-popup>
```

---
