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

# `lr-drop-zone`

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

A drag-and-drop region wrapper with no file input of its own. Wrap it around an arbitrary region —
a chat composer, a whole conversation viewport, a panel far larger than any single control — to
make that entire region a file-drop target: it owns the drag-session state, renders a themeable
drag-over overlay, applies `accept`/size/count limits, and emits the same `lr-files` event shape
`lr-file-input` does. It never renders a native file picker, a selected-file list, or any focusable
control of its own; wrap `lr-file-input` itself (or any other focusable content) inside it when the
region also needs a click-to-browse affordance. The drag-session mechanics are the exact ones
`lr-file-input` uses, shared through `internal/drop-session-controller.ts` rather than
reimplemented.

**Properties:**

- `disabled: boolean = false` (reflected) — disables drag/drop handling entirely; the wrapped
  content keeps its own interactivity
- `multiple: boolean = true` (reflected) — accepts more than one file per drop and enables
  recursive folder-drop traversal, same contract as `lr-file-input`'s `multiple`. Defaults to `true`
  (unlike `lr-file-input`'s `false`): a region wrapper's typical use expects more than one file, and
  there is no native single-file picker here to keep in sync.
- `accept: string = ''` — identical native-`accept`-style parsing to `lr-file-input`'s `accept`, via
  the same shared `matchesAccept()`
- `maxFileSize: number = 0` (attribute `max-file-size` — bytes; `0` disables the check), `maxFiles:
  number = 0` (attribute `max-files`), `maxTotalSize: number = 0` (attribute `max-total-size`) —
  identical contract and invalid-override fail-safe fallback to `lr-file-input`'s own three limits.
  Since this component retains nothing of its own between drops, `maxFiles`/`maxTotalSize` would
  otherwise always cover only the current drop — `heldFileCount`/`heldTotalSize` below are what let
  a cumulative cap span separate drops.
- `heldFileCount: number = 0` (attribute `held-file-count`) and `heldTotalSize: number = 0`
  (attribute `held-total-size`) — externally held baseline added to the running count/byte-total
  `maxFiles`/`maxTotalSize` evaluate against, identical contract to `lr-file-input`'s own
  `heldFileCount`/`heldTotalSize`: `0` (the default) means "nothing held" and reproduces prior
  behavior exactly, and a negative, `NaN`, or `Infinity` override is normalized to `0` via
  `finiteCount` rather than corrupting every later comparison.
- `readonly dragging: boolean` — `true` during an active drag session

**Events:** `lr-files` (`detail: LyraDropZoneFilesDetail`, with fresh frozen readonly `files` and
`rejected` arrays and frozen rejected-file records, plus `remainingFiles`/`remainingTotalSize`
reporting the allowance still left under `maxFiles`/`maxTotalSize` after this drop (`null` while
that limit is unset), fired on drop; immutable `File` objects retain identity) — typed as
`LyraDropZoneFilesEvent`, so `event.target`/`event.currentTarget` are `LyraDropZone` without a
cast. `LyraDropZoneRejectedFile = { readonly file: File; readonly reason: 'type' | 'count' | 'size'
| 'directory' | 'read' | 'limit' | 'maxFiles' | 'maxTotalSize' }`, the same reason vocabulary as
`lr-file-input`'s `LyraFileInputRejectedFile`.

**Slots:** the default slot is the wrapped region, rendered as ordinary light DOM; `overlay`
overrides the localized accept/reject overlay text.

**CSS parts:** `base` (wraps the default slot and the overlay), `overlay` (the drag-over overlay,
layered above the slotted content via `position: absolute; inset: 0`, hidden outside an active drag
session), `overlay-icon`, `overlay-text`, `status` (a visually-hidden, `aria-hidden` mirror of the
drag state and counts — announcements go through the shared light-DOM regions, same as
`lr-file-input`), `rejection` (a visible region listing each currently-rejected file, rendered only
while one exists).

**CSS custom states:** `dragging`, matching `lr-file-input`'s.

**Themeable custom properties:** `--lr-drop-zone-radius` (default `var(--lr-radius)`),
`--lr-drop-zone-overlay-border-color` (default `var(--lr-color-brand)`, the dashed overlay border in
its neutral drag state, before an accept/reject verdict) and `--lr-drop-zone-overlay-bg` (default
`color-mix(in srgb, var(--lr-color-brand) 8%, transparent)`, that same neutral state's fill),
`--lr-drop-zone-overlay-font-size` (default `var(--lr-font-size-md-sm)`),
`--lr-drop-zone-overlay-icon-size` (default `var(--lr-font-size-xl)`),
`--lr-drop-zone-overlay-gap` (default `var(--lr-space-xs)`), and the drag accept/reject highlight —
`--lr-drop-zone-accept-border-color`/`--lr-drop-zone-accept-bg` (defaults `var(--lr-color-success)`/
`color-mix(in srgb, var(--lr-color-success) 12%, transparent)`) and
`--lr-drop-zone-reject-border-color`/`--lr-drop-zone-reject-bg` (defaults `var(--lr-color-danger)`/
`color-mix(in srgb, var(--lr-color-danger) 12%, transparent)`) — independently overridable on the
element or any ancestor, mirroring `lr-file-input`'s equivalent hooks.

**Optional peer deps:** none.

```html
<lr-drop-zone id="chat-surface" accept="image/*,.pdf" max-files="5">
  <div class="chat-panel">
    <!-- an existing chat composer / viewport, unrelated to this component -->
  </div>
</lr-drop-zone>
<script>
  document.querySelector("#chat-surface").addEventListener("lr-files", (e) => {
    console.log("accepted:", e.detail.files, "rejected:", e.detail.rejected);
  });
</script>
```

Composing `lr-file-input` inside the wrapped region gives that region both a click-to-browse picker
and a drop target covering the whole surrounding panel:

```html
<lr-drop-zone>
  <div class="chat-panel">
    <lr-file-input></lr-file-input>
  </div>
</lr-drop-zone>
```

**Known gotchas:**

- No paste-from-clipboard handling (unlike `lr-file-input`'s `paste`) — this component is drag/drop
  only.
- Dragged folders are traversed recursively while `multiple` (the default), with the same
  10,000-entry budget and `'read'`/`'limit'` failure reasons as `lr-file-input`. While not
  `multiple`, a dropped folder is rejected outright with reason `'directory'`.

---
