cursedbelt 5.16.0 → 5.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"Drawer.d.ts","sourceRoot":"","sources":["../../../src/react/components/Drawer.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AASvC,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AACrD,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAmB5C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,+DAA+D;IAC/D,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,mFAAmF;IACnF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,6FAA6F;IAC7F,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;IACpB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,MAAM,CAAC,EACrB,IAAI,EACJ,OAAO,EACP,KAAK,EACL,IAAW,EACX,IAAc,EACd,OAAO,EACP,MAAM,EACN,QAAQ,EACR,MAAM,EACN,UAAiB,GAClB,EAAE,WAAW,+BAqFb"}
1
+ {"version":3,"file":"Drawer.d.ts","sourceRoot":"","sources":["../../../src/react/components/Drawer.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AASvC,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AACrD,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAmB5C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,+DAA+D;IAC/D,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,mFAAmF;IACnF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,6FAA6F;IAC7F,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;IACpB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,MAAM,CAAC,EACrB,IAAI,EACJ,OAAO,EACP,KAAK,EACL,IAAW,EACX,IAAc,EACd,OAAO,EACP,MAAM,EACN,QAAQ,EACR,MAAM,EACN,UAAiB,GAClB,EAAE,WAAW,+BAsFb"}
@@ -4,7 +4,7 @@ import { useDiscardGuard } from '../hooks/useDiscardGuard.js';
4
4
  import { useSwipeToDismiss } from '../hooks/useSwipeToDismiss.js';
5
5
  import { cn } from '../lib/cn.js';
6
6
  import { FooterSlot } from './ActionBar.js';
7
- import { Dialog, DialogContent, DialogTitle } from './dialog.js';
7
+ import { Dialog, DialogContent, DialogTitle, focusPanelOnOpen } from './dialog.js';
8
8
  import { IconButton } from './IconButton.js';
9
9
  import { ScrollArea } from './ScrollArea.js';
10
10
  // `size` is the panel width on a left/right drawer, the panel height on a bottom drawer.
@@ -66,7 +66,7 @@ export function Drawer({ open, onClose, title, size = 'md', side = 'right', isDi
66
66
  ]), onInteractOutside: (e) => {
67
67
  e.preventDefault();
68
68
  guardedClose();
69
- }, onEscapeKeyDown: (e) => {
69
+ }, onOpenAutoFocus: focusPanelOnOpen, onEscapeKeyDown: (e) => {
70
70
  e.preventDefault();
71
71
  if (isConfirming)
72
72
  cancelDiscard();
@@ -1 +1 @@
1
- {"version":3,"file":"Drawer.js","sourceRoot":"","sources":["../../../src/react/components/Drawer.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Drawer.js","sourceRoot":"","sources":["../../../src/react/components/Drawer.tsx"],"names":[],"mappings":"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"}
@@ -102,6 +102,25 @@ export interface FileDropZoneProps {
102
102
  * The notice is inline and dismissible, never a modal or a toast: it belongs
103
103
  * beside the zone the files were aimed at, and it must not auto-dismiss, because
104
104
  * a message the user did not get to read is the same as no message.
105
+ *
106
+ * ── 🔴 A DROPPED FOLDER IS ITS FILES (2026-09-28) ─────────────────────────────
107
+ * `dataTransfer.files` lists a dropped folder as ONE `File` — the folder's own
108
+ * name, no extension, no bytes — so it failed every `accept` and reached the app
109
+ * as a single refusal. Found in roms: the owner double-clicked a `.7z` in Finder
110
+ * and dropped the folder it unpacked to (a PlayStation disc's `.cue` and thirty
111
+ * `.bin`s, one level down) and got "not a file this accepts" for the lot, when
112
+ * what he dropped was exactly the files the zone takes. Finder's "unpack, then
113
+ * drag the result" is the ordinary way a person hands over a set of files.
114
+ *
115
+ * So a folder is walked (`webkitGetAsEntry`, the only door every browser ships)
116
+ * and its files are judged one by one, as if they had been dropped loose. Two
117
+ * rules keep that honest rather than noisy:
118
+ *
119
+ * · inside a folder, a file `accept` turns away is SKIPPED, as an archive's
120
+ * readme is — the person dropped the folder, not its `.txt` — and hidden files
121
+ * (`.DS_Store`, AppleDouble `._x`) are never files anyone meant;
122
+ * · but a folder that yields NOTHING acceptable is itself reported as rejected,
123
+ * under its own name — the 2026-08-14 rule, one level up: a drop never vanishes.
105
124
  */
106
125
  export declare function FileDropZone({ onFiles, onRejected, onDrop: onDropResult, accept, multiple, disabled, children, hint, className, }: FileDropZoneProps): import("react").JSX.Element;
107
126
  //# sourceMappingURL=FileDropZone.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FileDropZone.d.ts","sourceRoot":"","sources":["../../../src/react/components/FileDropZone.tsx"],"names":[],"mappings":"AACA,OAAO,EAAkB,KAAK,SAAS,EAA2B,MAAM,OAAO,CAAC;AAKhF,MAAM,WAAW,iBAAiB;IAChC,yEAAyE;IACzE,OAAO,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACjC;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACrC;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAAC,QAAQ,EAAE,IAAI,EAAE,CAAA;KAAE,KAAK,IAAI,CAAC;IAClE,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAaD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AACH,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,UAAU,EACV,MAAM,EAAE,YAAY,EACpB,MAAM,EACN,QAAe,EACf,QAAgB,EAChB,QAAQ,EACR,IAAI,EACJ,SAAS,GACV,EAAE,iBAAiB,+BA4HnB"}
1
+ {"version":3,"file":"FileDropZone.d.ts","sourceRoot":"","sources":["../../../src/react/components/FileDropZone.tsx"],"names":[],"mappings":"AACA,OAAO,EAAkB,KAAK,SAAS,EAA2B,MAAM,OAAO,CAAC;AAKhF,MAAM,WAAW,iBAAiB;IAChC,yEAAyE;IACzE,OAAO,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACjC;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACrC;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAAC,QAAQ,EAAE,IAAI,EAAE,CAAA;KAAE,KAAK,IAAI,CAAC;IAClE,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA0FD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkFG;AACH,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,UAAU,EACV,MAAM,EAAE,YAAY,EACpB,MAAM,EACN,QAAe,EACf,QAAgB,EAChB,QAAQ,EACR,IAAI,EACJ,SAAS,GACV,EAAE,iBAAiB,+BAkInB"}
@@ -4,6 +4,72 @@ import { useId, useRef, useState } from 'react';
4
4
  import { cn } from '../lib/cn.js';
5
5
  import { Button } from './Button.js';
6
6
  import { Input } from './Input.js';
7
+ /** What a folder can hold before walking it stops — a drop, not a disk scan. */
8
+ const MAX_FOLDER_FILES = 5000;
9
+ const isHidden = (name) => name.startsWith('.');
10
+ /** Every file under `entry`, depth-first. A directory's reader answers in batches until empty. */
11
+ async function filesUnder(entry, out) {
12
+ if (out.length >= MAX_FOLDER_FILES || isHidden(entry.name))
13
+ return;
14
+ if (entry.isFile && entry.file) {
15
+ const read = entry.file.bind(entry);
16
+ out.push(await new Promise((ok, fail) => read(ok, fail)));
17
+ return;
18
+ }
19
+ if (!entry.isDirectory || !entry.createReader)
20
+ return;
21
+ const reader = entry.createReader();
22
+ while (out.length < MAX_FOLDER_FILES) {
23
+ const batch = await new Promise((ok, fail) => reader.readEntries(ok, fail));
24
+ if (batch.length === 0)
25
+ return;
26
+ for (const child of batch)
27
+ await filesUnder(child, out);
28
+ }
29
+ }
30
+ /**
31
+ * A drop's files, with every dropped FOLDER replaced by the accepted files inside it — see the
32
+ * FOLDER section above. `null` when nothing in the drop is a folder, so the ordinary path stays
33
+ * exactly `dataTransfer.files`.
34
+ *
35
+ * 🔴 The entries are taken SYNCHRONOUSLY, before the first `await`: a browser empties the
36
+ * `DataTransfer` once the drop event returns.
37
+ */
38
+ function folderAwareDrop(transfer, accept) {
39
+ const items = transfer.items ? [...transfer.items] : [];
40
+ const entries = items.map((item) => item.kind === 'file' && typeof item.webkitGetAsEntry === 'function'
41
+ ? item.webkitGetAsEntry()
42
+ : null);
43
+ if (!entries.some((entry) => entry?.isDirectory))
44
+ return null;
45
+ const looseFiles = [...transfer.files];
46
+ return (async () => {
47
+ const loose = [];
48
+ const fromFolders = [];
49
+ const emptyFolders = [];
50
+ for (const [i, entry] of entries.entries()) {
51
+ if (!entry?.isDirectory) {
52
+ const file = looseFiles[i] ?? (entry ? await new Promise((ok) => entry.file?.(ok, () => ok(null))) : null);
53
+ if (file)
54
+ loose.push(file);
55
+ continue;
56
+ }
57
+ const inside = [];
58
+ try {
59
+ await filesUnder(entry, inside);
60
+ }
61
+ catch {
62
+ // An unreadable folder yields what was read before the error — and, if that is nothing, a refusal below.
63
+ }
64
+ const kept = inside.filter((f) => matchesAccept(f, accept));
65
+ if (kept.length > 0)
66
+ fromFolders.push(...kept);
67
+ else
68
+ emptyFolders.push(new File([], entry.name));
69
+ }
70
+ return { loose, fromFolders, emptyFolders };
71
+ })();
72
+ }
7
73
  const matchesAccept = (file, accept) => {
8
74
  if (!accept)
9
75
  return true;
@@ -81,21 +147,40 @@ const matchesAccept = (file, accept) => {
81
147
  * The notice is inline and dismissible, never a modal or a toast: it belongs
82
148
  * beside the zone the files were aimed at, and it must not auto-dismiss, because
83
149
  * a message the user did not get to read is the same as no message.
150
+ *
151
+ * ── 🔴 A DROPPED FOLDER IS ITS FILES (2026-09-28) ─────────────────────────────
152
+ * `dataTransfer.files` lists a dropped folder as ONE `File` — the folder's own
153
+ * name, no extension, no bytes — so it failed every `accept` and reached the app
154
+ * as a single refusal. Found in roms: the owner double-clicked a `.7z` in Finder
155
+ * and dropped the folder it unpacked to (a PlayStation disc's `.cue` and thirty
156
+ * `.bin`s, one level down) and got "not a file this accepts" for the lot, when
157
+ * what he dropped was exactly the files the zone takes. Finder's "unpack, then
158
+ * drag the result" is the ordinary way a person hands over a set of files.
159
+ *
160
+ * So a folder is walked (`webkitGetAsEntry`, the only door every browser ships)
161
+ * and its files are judged one by one, as if they had been dropped loose. Two
162
+ * rules keep that honest rather than noisy:
163
+ *
164
+ * · inside a folder, a file `accept` turns away is SKIPPED, as an archive's
165
+ * readme is — the person dropped the folder, not its `.txt` — and hidden files
166
+ * (`.DS_Store`, AppleDouble `._x`) are never files anyone meant;
167
+ * · but a folder that yields NOTHING acceptable is itself reported as rejected,
168
+ * under its own name — the 2026-08-14 rule, one level up: a drop never vanishes.
84
169
  */
85
170
  export function FileDropZone({ onFiles, onRejected, onDrop: onDropResult, accept, multiple = true, disabled = false, children, hint, className, }) {
86
171
  const inputRef = useRef(null);
87
172
  const inputId = useId();
88
173
  const [dragOver, setDragOver] = useState(false);
89
174
  const [rejected, setRejected] = useState([]);
90
- const emit = (list) => {
175
+ const emit = (list, fromFolders = [], emptyFolders = []) => {
91
176
  if (!list)
92
177
  return;
93
178
  const all = [...list];
94
- if (all.length === 0)
179
+ if (all.length === 0 && fromFolders.length === 0 && emptyFolders.length === 0)
95
180
  return;
96
- const matched = all.filter((f) => matchesAccept(f, accept));
181
+ const matched = [...all.filter((f) => matchesAccept(f, accept)), ...fromFolders];
97
182
  const accepted = multiple ? matched : matched.slice(0, 1);
98
- const turnedAway = all.filter((f) => !matchesAccept(f, accept));
183
+ const turnedAway = [...all.filter((f) => !matchesAccept(f, accept)), ...emptyFolders];
99
184
  // One gesture, one call — the whole reason this overload exists.
100
185
  if (onDropResult) {
101
186
  setRejected([]);
@@ -117,8 +202,14 @@ export function FileDropZone({ onFiles, onRejected, onDrop: onDropResult, accept
117
202
  const onDrop = (e) => {
118
203
  e.preventDefault();
119
204
  setDragOver(false);
120
- if (!disabled)
205
+ if (disabled)
206
+ return;
207
+ const walking = folderAwareDrop(e.dataTransfer, accept);
208
+ if (!walking) {
121
209
  emit(e.dataTransfer.files);
210
+ return;
211
+ }
212
+ void walking.then(({ loose, fromFolders, emptyFolders }) => emit(loose, fromFolders, emptyFolders));
122
213
  };
123
214
  return (_jsxs("div", { onDragOver: (e) => {
124
215
  e.preventDefault(); // required by the spec for `drop` to fire
@@ -1 +1 @@
1
- {"version":3,"file":"FileDropZone.js","sourceRoot":"","sources":["../../../src/react/components/FileDropZone.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"FileDropZone.js","sourceRoot":"","sources":["../../../src/react/components/FileDropZone.tsx"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"Lightbox.d.ts","sourceRoot":"","sources":["../../../src/react/components/Lightbox.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAKvC,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,+EAA+E;IAC/E,QAAQ,EAAE,SAAS,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,aAAa,+BAiC3E"}
1
+ {"version":3,"file":"Lightbox.d.ts","sourceRoot":"","sources":["../../../src/react/components/Lightbox.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAKvC,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,+EAA+E;IAC/E,QAAQ,EAAE,SAAS,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,aAAa,+BAkC3E"}
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { X } from 'lucide-react';
3
3
  import { cn } from '../lib/cn.js';
4
- import { Dialog, DialogContent, DialogTitle } from './dialog.js';
4
+ import { Dialog, DialogContent, DialogTitle, focusPanelOnOpen } from './dialog.js';
5
5
  import { IconButton } from './IconButton.js';
6
6
  /**
7
7
  * Fullscreen media lightbox on Radix Dialog. This is just the overlay shell — the actual
@@ -14,6 +14,6 @@ export function Lightbox({ open, onClose, children, caption }) {
14
14
  return (_jsx(Dialog, { open: open, onOpenChange: (v) => {
15
15
  if (!v)
16
16
  onClose();
17
- }, children: _jsxs(DialogContent, { overlayClassName: 'bg-black/80', className: cn('fixed inset-4 flex flex-col items-center justify-center gap-3 p-4', 'rounded-xl border-none bg-black/95 shadow-none', 'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95', 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95'), onInteractOutside: () => onClose(), children: [_jsx(DialogTitle, { className: 'sr-only', children: caption ?? 'Media viewer' }), _jsx(IconButton, { label: 'Close', variant: 'ghost', onClick: onClose, className: 'absolute top-4 right-4 h-auto w-auto rounded-full bg-white/10 p-1.5 text-white hover:bg-white/20 hover:text-white focus-visible:ring-offset-black', children: _jsx(X, { className: 'size-5' }) }), _jsx("div", { className: 'flex min-h-0 min-w-0 flex-1 items-center justify-center', children: children }), caption ? _jsx("p", { className: 'text-center text-sm text-white/70', children: caption }) : null] }) }));
17
+ }, children: _jsxs(DialogContent, { overlayClassName: 'bg-black/80', className: cn('fixed inset-4 flex flex-col items-center justify-center gap-3 p-4', 'rounded-xl border-none bg-black/95 shadow-none', 'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95', 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95'), onInteractOutside: () => onClose(), onOpenAutoFocus: focusPanelOnOpen, children: [_jsx(DialogTitle, { className: 'sr-only', children: caption ?? 'Media viewer' }), _jsx(IconButton, { label: 'Close', variant: 'ghost', onClick: onClose, className: 'absolute top-4 right-4 h-auto w-auto rounded-full bg-white/10 p-1.5 text-white hover:bg-white/20 hover:text-white focus-visible:ring-offset-black', children: _jsx(X, { className: 'size-5' }) }), _jsx("div", { className: 'flex min-h-0 min-w-0 flex-1 items-center justify-center', children: children }), caption ? _jsx("p", { className: 'text-center text-sm text-white/70', children: caption }) : null] }) }));
18
18
  }
19
19
  //# sourceMappingURL=Lightbox.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Lightbox.js","sourceRoot":"","sources":["../../../src/react/components/Lightbox.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAC;AAEjC,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAU7C;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAiB;IAC1E,OAAO,CACL,KAAC,MAAM,IACL,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE;YAClB,IAAI,CAAC,CAAC;gBAAE,OAAO,EAAE,CAAC;QACpB,CAAC,YAED,MAAC,aAAa,IACZ,gBAAgB,EAAC,aAAa,EAC9B,SAAS,EAAE,EAAE,CACX,mEAAmE,EACnE,gDAAgD,EAChD,uFAAuF,EACvF,gGAAgG,CACjG,EACD,iBAAiB,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,aAGlC,KAAC,WAAW,IAAC,SAAS,EAAC,SAAS,YAAE,OAAO,IAAI,cAAc,GAAe,EAC1E,KAAC,UAAU,IACT,KAAK,EAAC,OAAO,EACb,OAAO,EAAC,OAAO,EACf,OAAO,EAAE,OAAO,EAChB,SAAS,EAAC,mJAAmJ,YAE7J,KAAC,CAAC,IAAC,SAAS,EAAC,QAAQ,GAAG,GACb,EACb,cAAK,SAAS,EAAC,yDAAyD,YAAE,QAAQ,GAAO,EACxF,OAAO,CAAC,CAAC,CAAC,YAAG,SAAS,EAAC,mCAAmC,YAAE,OAAO,GAAK,CAAC,CAAC,CAAC,IAAI,IAClE,GACT,CACV,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"Lightbox.js","sourceRoot":"","sources":["../../../src/react/components/Lightbox.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAC;AAEjC,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAU7C;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAiB;IAC1E,OAAO,CACL,KAAC,MAAM,IACL,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE;YAClB,IAAI,CAAC,CAAC;gBAAE,OAAO,EAAE,CAAC;QACpB,CAAC,YAED,MAAC,aAAa,IACZ,gBAAgB,EAAC,aAAa,EAC9B,SAAS,EAAE,EAAE,CACX,mEAAmE,EACnE,gDAAgD,EAChD,uFAAuF,EACvF,gGAAgG,CACjG,EACD,iBAAiB,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,EAClC,eAAe,EAAE,gBAAgB,aAGjC,KAAC,WAAW,IAAC,SAAS,EAAC,SAAS,YAAE,OAAO,IAAI,cAAc,GAAe,EAC1E,KAAC,UAAU,IACT,KAAK,EAAC,OAAO,EACb,OAAO,EAAC,OAAO,EACf,OAAO,EAAE,OAAO,EAChB,SAAS,EAAC,mJAAmJ,YAE7J,KAAC,CAAC,IAAC,SAAS,EAAC,QAAQ,GAAG,GACb,EACb,cAAK,SAAS,EAAC,yDAAyD,YAAE,QAAQ,GAAO,EACxF,OAAO,CAAC,CAAC,CAAC,YAAG,SAAS,EAAC,mCAAmC,YAAE,OAAO,GAAK,CAAC,CAAC,CAAC,IAAI,IAClE,GACT,CACV,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../../../src/react/components/Modal.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAQvC,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;AAa3D,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,QAAQ,EAAE,SAAS,CAAC;IACpB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,mFAAmF;IACnF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,6FAA6F;IAC7F,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;;;;;;;;;OAeG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,gFAAgF;IAChF,UAAU,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,KAAK,CAAC,EACpB,IAAI,EACJ,OAAO,EACP,IAAW,EACX,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,OAAO,EACP,OAAO,EACP,MAAM,EACN,UAAiB,EACjB,aAAoB,EACpB,KAAK,EACL,UAAiB,EACjB,SAAS,GACV,EAAE,UAAU,+BAyHZ"}
1
+ {"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../../../src/react/components/Modal.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAQvC,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;AAa3D,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,QAAQ,EAAE,SAAS,CAAC;IACpB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,mFAAmF;IACnF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,6FAA6F;IAC7F,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;;;;;;;;;OAeG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,gFAAgF;IAChF,UAAU,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,KAAK,CAAC,EACpB,IAAI,EACJ,OAAO,EACP,IAAW,EACX,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,OAAO,EACP,OAAO,EACP,MAAM,EACN,UAAiB,EACjB,aAAoB,EACpB,KAAK,EACL,UAAiB,EACjB,SAAS,GACV,EAAE,UAAU,+BA0HZ"}
@@ -3,7 +3,7 @@ import { X } from 'lucide-react';
3
3
  import { useDiscardGuard } from '../hooks/useDiscardGuard.js';
4
4
  import { cn } from '../lib/cn.js';
5
5
  import { FooterSlot } from './ActionBar.js';
6
- import { Dialog, DialogContent, DialogTitle } from './dialog.js';
6
+ import { Dialog, DialogContent, DialogTitle, focusPanelOnOpen } from './dialog.js';
7
7
  import { IconButton } from './IconButton.js';
8
8
  import { ScrollArea } from './ScrollArea.js';
9
9
  // ONE sizing scale replaces the ad-hoc `max-w-xl`/`max-w-2xl`/`max-h-[80vh]` classNames
@@ -61,7 +61,7 @@ export function Modal({ open, onClose, size = 'md', title, subtitle, children, f
61
61
  e.preventDefault();
62
62
  if (backdropClose)
63
63
  guardedClose();
64
- }, onInteractOutside: (e) => e.preventDefault(), onEscapeKeyDown: (e) => {
64
+ }, onInteractOutside: (e) => e.preventDefault(), onOpenAutoFocus: focusPanelOnOpen, onEscapeKeyDown: (e) => {
65
65
  e.preventDefault();
66
66
  if (isConfirming)
67
67
  cancelDiscard();
@@ -1 +1 @@
1
- {"version":3,"file":"Modal.js","sourceRoot":"","sources":["../../../src/react/components/Modal.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAC;AAEjC,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAI7C,wFAAwF;AACxF,0FAA0F;AAC1F,wFAAwF;AACxF,MAAM,IAAI,GAA8B;IACtC,EAAE,EAAE,UAAU;IACd,EAAE,EAAE,UAAU;IACd,EAAE,EAAE,WAAW;IACf,EAAE,EAAE,WAAW;IACf,IAAI,EAAE,oDAAoD;CAC3D,CAAC;AAqFF;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,KAAK,CAAC,EACpB,IAAI,EACJ,OAAO,EACP,IAAI,GAAG,IAAI,EACX,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,OAAO,EACP,OAAO,EACP,MAAM,EACN,UAAU,GAAG,IAAI,EACjB,aAAa,GAAG,IAAI,EACpB,KAAK,EACL,UAAU,GAAG,IAAI,EACjB,SAAS,GACE;IACX,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,GAAG,eAAe,CAAC;QACpF,IAAI;QACJ,OAAO;QACP,OAAO;QACP,MAAM;KACP,CAAC,CAAC;IAEH,OAAO,CACL,KAAC,MAAM,IACL,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE;YAClB,IAAI,CAAC,CAAC;gBAAE,YAAY,EAAE,CAAC;QACzB,CAAC,YAED,MAAC,aAAa,IACZ,SAAS,EAAE,EAAE;YACX,8EAA8E;YAC9E,wEAAwE;YACxE,0DAA0D,EAC1D,0DAA0D,EAC1D,mFAAmF,EACnF,sCAAsC,EACtC,uFAAuF,EACvF,gGAAgG,EAChG,IAAI,CAAC,IAAI,CAAC,EACV,SAAS,CACV;YACD,iFAAiF;YACjF,8EAA8E;YAC9E,gFAAgF;YAChF,gDAAgD;YAChD,EAAE;YACF,6EAA6E;YAC7E,4EAA4E;YAC5E,8EAA8E;YAC9E,yEAAyE;YACzE,8EAA8E;YAC9E,8EAA8E;YAC9E,4EAA4E;YAC5E,6EAA6E;YAC7E,+EAA+E;YAC/E,oEAAoE;YACpE,oBAAoB,EAAE,CAAC,CAAC,EAAE,EAAE;gBAC1B,CAAC,CAAC,cAAc,EAAE,CAAC;gBACnB,IAAI,aAAa;oBAAE,YAAY,EAAE,CAAC;YACpC,CAAC,EACD,iBAAiB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE,EAC5C,eAAe,EAAE,CAAC,CAAC,EAAE,EAAE;gBACrB,CAAC,CAAC,cAAc,EAAE,CAAC;gBACnB,IAAI,YAAY;oBAAE,aAAa,EAAE,CAAC;;oBAC7B,YAAY,EAAE,CAAC;YACtB,CAAC,aAED,eAAK,SAAS,EAAC,oFAAoF,aAChG,KAAK,CAAC,CAAC,CAAC,CACP,eAAK,SAAS,EAAC,gBAAgB,aAC7B,KAAC,WAAW,IAAC,SAAS,EAAC,yCAAyC,YAAE,KAAK,GAAe,EACrF,QAAQ,CAAC,CAAC,CAAC,CACV,YAAG,SAAS,EAAC,yDAAyD,YAAE,QAAQ,GAAK,CACtF,CAAC,CAAC,CAAC,IAAI,IACJ,CACP,CAAC,CAAC,CAAC,CAEF,KAAC,WAAW,IAAC,SAAS,EAAC,SAAS,uBAAqB,CACtD,EACD,KAAC,UAAU,IAAC,KAAK,EAAC,OAAO,EAAC,OAAO,EAAE,YAAY,YAC7C,KAAC,CAAC,KAAG,GACM,IACT,EAEL,KAAK,CAAC,CAAC,CAAC,CAIP,sCACsB,EAAE,EACtB,SAAS,EAAC,uEAAuE,aAEjF,qCACsB,EAAE,EACtB,SAAS,EAAE,EAAE,CACX,oCAAoC,EACpC,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAC9C,YAED,KAAC,UAAU,IAAC,SAAS,EAAC,WAAW,YAAE,KAAK,GAAc,GAClD,EACN,cAAK,SAAS,EAAC,8BAA8B,YAC1C,UAAU,CAAC,CAAC,CAAC,CACZ,KAAC,UAAU,IAAC,SAAS,EAAC,WAAW,YAAE,QAAQ,GAAc,CAC1D,CAAC,CAAC,CAAC,CACF,cAAK,SAAS,EAAC,8BAA8B,YAAE,QAAQ,GAAO,CAC/D,GACG,IACF,CACP,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAEf,KAAC,UAAU,IAAC,SAAS,EAAC,WAAW,YAAE,QAAQ,GAAc,CAC1D,CAAC,CAAC,CAAC,CAGF,cAAK,SAAS,EAAC,8BAA8B,YAAE,QAAQ,GAAO,CAC/D,EAEA,OAAO,CAAC,CAAC,CAAC,CAKT,cAAK,SAAS,EAAC,kCAAkC,YAAE,OAAO,GAAO,CAClE,CAAC,CAAC,CAAC,IAAI,EAER,KAAC,UAAU,IAAC,MAAM,EAAE,MAAM,GAAI,EAI7B,cAAc,IACD,GACT,CACV,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"Modal.js","sourceRoot":"","sources":["../../../src/react/components/Modal.tsx"],"names":[],"mappings":"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"}
@@ -18,6 +18,18 @@ export declare const DialogPortal: import("react").FC<DialogPrimitive.DialogPort
18
18
  export declare const DialogClose: import("react").ForwardRefExoticComponent<DialogPrimitive.DialogCloseProps & import("react").RefAttributes<HTMLButtonElement>>;
19
19
  export declare const DialogTitle: import("react").ForwardRefExoticComponent<DialogPrimitive.DialogTitleProps & import("react").RefAttributes<HTMLHeadingElement>>;
20
20
  export declare const DialogDescription: import("react").ForwardRefExoticComponent<DialogPrimitive.DialogDescriptionProps & import("react").RefAttributes<HTMLParagraphElement>>;
21
+ /**
22
+ * `onOpenAutoFocus` for a panel whose first focusable is its header's Close button (Modal,
23
+ * Drawer): focus the PANEL instead — Radix gives it `tabIndex=-1`, so Tab still reaches the X
24
+ * first and the focus trap is unchanged.
25
+ *
26
+ * 🔴 Radix's default lands on that X, and the X's tooltip opens on focus. Radix hands Escape to
27
+ * the TOPMOST dismissable layer, which was the tooltip: the first Escape shut the tooltip and the
28
+ * dialog needed a second one, with a "Close" bubble hanging over the header until then. Measured
29
+ * in binary-server's admin, 2026-09-28. A field with `autoFocus` keeps its focus, because Radix
30
+ * only fires this when nothing inside the panel has claimed focus yet.
31
+ */
32
+ export declare const focusPanelOnOpen: (e: Event) => void;
21
33
  /** Shared dimmer behind every overlay: a fixed, blurred scrim that fades with state. */
22
34
  export declare const DialogOverlay: import("react").ForwardRefExoticComponent<Omit<DialogPrimitive.DialogOverlayProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
23
35
  export interface DialogContentProps extends ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {
@@ -1 +1 @@
1
- {"version":3,"file":"dialog.d.ts","sourceRoot":"","sources":["../../../src/react/components/dialog.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,eAAe,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,KAAK,wBAAwB,EAAc,MAAM,OAAO,CAAC;AAGlE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,MAAM,iDAAuB,CAAC;AAC3C,eAAO,MAAM,aAAa,kIAA0B,CAAC;AACrD,eAAO,MAAM,YAAY,uDAAyB,CAAC;AACnD,eAAO,MAAM,WAAW,gIAAwB,CAAC;AACjD,eAAO,MAAM,WAAW,iIAAwB,CAAC;AACjD,eAAO,MAAM,iBAAiB,yIAA8B,CAAC;AAE7D,wFAAwF;AACxF,eAAO,MAAM,aAAa,4LAcxB,CAAC;AAGH,MAAM,WAAW,kBACf,SAAQ,wBAAwB,CAAC,OAAO,eAAe,CAAC,OAAO,CAAC;IAChE,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,+GASzB,CAAC"}
1
+ {"version":3,"file":"dialog.d.ts","sourceRoot":"","sources":["../../../src/react/components/dialog.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,eAAe,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,KAAK,wBAAwB,EAAc,MAAM,OAAO,CAAC;AAGlE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,MAAM,iDAAuB,CAAC;AAC3C,eAAO,MAAM,aAAa,kIAA0B,CAAC;AACrD,eAAO,MAAM,YAAY,uDAAyB,CAAC;AACnD,eAAO,MAAM,WAAW,gIAAwB,CAAC;AACjD,eAAO,MAAM,WAAW,iIAAwB,CAAC;AACjD,eAAO,MAAM,iBAAiB,yIAA8B,CAAC;AAE7D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,GAAI,GAAG,KAAK,KAAG,IAG3C,CAAC;AAEF,wFAAwF;AACxF,eAAO,MAAM,aAAa,4LAcxB,CAAC;AAGH,MAAM,WAAW,kBACf,SAAQ,wBAAwB,CAAC,OAAO,eAAe,CAAC,OAAO,CAAC;IAChE,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,+GASzB,CAAC"}
@@ -20,6 +20,21 @@ export const DialogPortal = DialogPrimitive.Portal;
20
20
  export const DialogClose = DialogPrimitive.Close;
21
21
  export const DialogTitle = DialogPrimitive.Title;
22
22
  export const DialogDescription = DialogPrimitive.Description;
23
+ /**
24
+ * `onOpenAutoFocus` for a panel whose first focusable is its header's Close button (Modal,
25
+ * Drawer): focus the PANEL instead — Radix gives it `tabIndex=-1`, so Tab still reaches the X
26
+ * first and the focus trap is unchanged.
27
+ *
28
+ * 🔴 Radix's default lands on that X, and the X's tooltip opens on focus. Radix hands Escape to
29
+ * the TOPMOST dismissable layer, which was the tooltip: the first Escape shut the tooltip and the
30
+ * dialog needed a second one, with a "Close" bubble hanging over the header until then. Measured
31
+ * in binary-server's admin, 2026-09-28. A field with `autoFocus` keeps its focus, because Radix
32
+ * only fires this when nothing inside the panel has claimed focus yet.
33
+ */
34
+ export const focusPanelOnOpen = (e) => {
35
+ e.preventDefault();
36
+ e.currentTarget?.focus();
37
+ };
23
38
  /** Shared dimmer behind every overlay: a fixed, blurred scrim that fades with state. */
24
39
  export const DialogOverlay = forwardRef(({ className, ...props }, ref) => (_jsx(DialogPrimitive.Overlay, { ref: ref, className: cn('fixed inset-0 z-40 bg-black/50 backdrop-blur-sm', 'data-[state=open]:animate-in data-[state=open]:fade-in-0', 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0', className), ...props })));
25
40
  DialogOverlay.displayName = 'DialogOverlay';
@@ -1 +1 @@
1
- {"version":3,"file":"dialog.js","sourceRoot":"","sources":["../../../src/react/components/dialog.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,eAAe,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAiC,UAAU,EAAE,MAAM,OAAO,CAAC;AAClE,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAElC;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC;AAC3C,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAC,OAAO,CAAC;AACrD,MAAM,CAAC,MAAM,YAAY,GAAG,eAAe,CAAC,MAAM,CAAC;AACnD,MAAM,CAAC,MAAM,WAAW,GAAG,eAAe,CAAC,KAAK,CAAC;AACjD,MAAM,CAAC,MAAM,WAAW,GAAG,eAAe,CAAC,KAAK,CAAC;AACjD,MAAM,CAAC,MAAM,iBAAiB,GAAG,eAAe,CAAC,WAAW,CAAC;AAE7D,wFAAwF;AACxF,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CAGrC,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,CAClC,KAAC,eAAe,CAAC,OAAO,IACtB,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,EAAE,CACX,iDAAiD,EACjD,0DAA0D,EAC1D,gEAAgE,EAChE,SAAS,CACV,KACG,KAAK,GACT,CACH,CAAC,CAAC;AACH,aAAa,CAAC,WAAW,GAAG,eAAe,CAAC;AAQ5C;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CACrC,CAAC,EAAE,SAAS,EAAE,gBAAgB,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,CAC5D,MAAC,YAAY,eACX,KAAC,aAAa,IAAC,SAAS,EAAE,gBAAgB,GAAI,EAC9C,KAAC,eAAe,CAAC,OAAO,IAAC,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,CAAC,mBAAmB,EAAE,SAAS,CAAC,KAAM,KAAK,YACxF,QAAQ,GACe,IACb,CAChB,CACF,CAAC;AACF,aAAa,CAAC,WAAW,GAAG,eAAe,CAAC"}
1
+ {"version":3,"file":"dialog.js","sourceRoot":"","sources":["../../../src/react/components/dialog.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,eAAe,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAiC,UAAU,EAAE,MAAM,OAAO,CAAC;AAClE,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAElC;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC;AAC3C,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAC,OAAO,CAAC;AACrD,MAAM,CAAC,MAAM,YAAY,GAAG,eAAe,CAAC,MAAM,CAAC;AACnD,MAAM,CAAC,MAAM,WAAW,GAAG,eAAe,CAAC,KAAK,CAAC;AACjD,MAAM,CAAC,MAAM,WAAW,GAAG,eAAe,CAAC,KAAK,CAAC;AACjD,MAAM,CAAC,MAAM,iBAAiB,GAAG,eAAe,CAAC,WAAW,CAAC;AAE7D;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAQ,EAAQ,EAAE;IACjD,CAAC,CAAC,cAAc,EAAE,CAAC;IAClB,CAAC,CAAC,aAAoC,EAAE,KAAK,EAAE,CAAC;AACnD,CAAC,CAAC;AAEF,wFAAwF;AACxF,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CAGrC,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,CAClC,KAAC,eAAe,CAAC,OAAO,IACtB,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,EAAE,CACX,iDAAiD,EACjD,0DAA0D,EAC1D,gEAAgE,EAChE,SAAS,CACV,KACG,KAAK,GACT,CACH,CAAC,CAAC;AACH,aAAa,CAAC,WAAW,GAAG,eAAe,CAAC;AAQ5C;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CACrC,CAAC,EAAE,SAAS,EAAE,gBAAgB,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,CAC5D,MAAC,YAAY,eACX,KAAC,aAAa,IAAC,SAAS,EAAE,gBAAgB,GAAI,EAC9C,KAAC,eAAe,CAAC,OAAO,IAAC,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,CAAC,mBAAmB,EAAE,SAAS,CAAC,KAAM,KAAK,YACxF,QAAQ,GACe,IACb,CAChB,CACF,CAAC;AACF,aAAa,CAAC,WAAW,GAAG,eAAe,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursedbelt",
3
- "version": "5.16.0",
3
+ "version": "5.17.0",
4
4
  "license": "ISC",
5
5
  "type": "module",
6
6
  "description": "The React design system of the cursedbelt split — components, styles, theme. cursedbelt-core below it; server tier in cursedbelt-server.",
@@ -4,7 +4,7 @@ import { useDiscardGuard } from '../hooks/useDiscardGuard.js';
4
4
  import { type SwipeDismissDirection, useSwipeToDismiss } from '../hooks/useSwipeToDismiss.js';
5
5
  import { cn } from '../lib/cn.js';
6
6
  import { FooterSlot } from './ActionBar.js';
7
- import { Dialog, DialogContent, DialogTitle } from './dialog.js';
7
+ import { Dialog, DialogContent, DialogTitle, focusPanelOnOpen } from './dialog.js';
8
8
  import { IconButton } from './IconButton.js';
9
9
  import { ScrollArea } from './ScrollArea.js';
10
10
 
@@ -122,6 +122,7 @@ export function Drawer({
122
122
  e.preventDefault();
123
123
  guardedClose();
124
124
  }}
125
+ onOpenAutoFocus={focusPanelOnOpen}
125
126
  onEscapeKeyDown={(e) => {
126
127
  e.preventDefault();
127
128
  if (isConfirming) cancelDiscard();
@@ -162,6 +162,85 @@ describe('FileDropZone', () => {
162
162
  expect(input.className).toContain('peer');
163
163
  });
164
164
 
165
+ /**
166
+ * 🔴 The 2026-09-28 roms report: the owner unpacked a `.7z` in Finder and dropped
167
+ * the FOLDER — `Loaded (USA) (En,Fr)/` holding a readme and, one level down, a
168
+ * `.cue` and thirty `.bin`s. `dataTransfer.files` lists a folder as one nameless
169
+ * File, so every one of those files was refused as "not a file this accepts".
170
+ */
171
+ describe('a dropped folder', () => {
172
+ type Entry = {
173
+ isFile: boolean;
174
+ isDirectory: boolean;
175
+ name: string;
176
+ file?: (ok: (f: File) => void) => void;
177
+ createReader?: () => { readEntries: (ok: (e: Entry[]) => void) => void };
178
+ };
179
+ const fileEntry = (f: File): Entry => ({ isFile: true, isDirectory: false, name: f.name, file: (ok) => ok(f) });
180
+ /** A directory whose reader answers in batches of two, then empty — as browsers batch. */
181
+ const dirEntry = (name: string, children: Entry[]): Entry => ({
182
+ isFile: false,
183
+ isDirectory: true,
184
+ name,
185
+ createReader: () => {
186
+ let at = 0;
187
+ return {
188
+ readEntries: (ok) => {
189
+ const batch = children.slice(at, at + 2);
190
+ at += 2;
191
+ ok(batch);
192
+ },
193
+ };
194
+ },
195
+ });
196
+ const dropEntries = (zone: HTMLElement, entries: Entry[], files: File[]) =>
197
+ fireEvent.drop(zone, {
198
+ dataTransfer: {
199
+ files,
200
+ items: entries.map((entry) => ({ kind: 'file', webkitGetAsEntry: () => entry })),
201
+ },
202
+ });
203
+ const settle = () => new Promise((resolve) => setTimeout(resolve, 0));
204
+
205
+ test("is its files — nested ones included, the ones `accept` turns away and hidden ones skipped", async () => {
206
+ let result: { accepted: File[]; rejected: File[] } | null = null;
207
+ const { container } = render(
208
+ <FileDropZone accept='.cue,.bin' onFiles={() => {}} onDrop={(r) => (result = r)} />,
209
+ );
210
+ const folder = dirEntry('Loaded (USA) (En,Fr)', [
211
+ fileEntry(file("Vimm's Lair.txt", 'text/plain')),
212
+ fileEntry(file('.DS_Store', '')),
213
+ dirEntry('Loaded (USA) (En,Fr)', [
214
+ fileEntry(file('Loaded.cue', '')),
215
+ fileEntry(file('Loaded (Track 01).bin', '')),
216
+ fileEntry(file('Loaded (Track 02).bin', '')),
217
+ ]),
218
+ ]);
219
+ dropEntries(container.firstElementChild as HTMLElement, [folder], [file('Loaded (USA) (En,Fr)', '')]);
220
+ for (let i = 0; i < 10 && !result; i++) await settle();
221
+ const got = result as { accepted: File[]; rejected: File[] } | null;
222
+ expect(got?.accepted.map((f) => f.name)).toEqual(['Loaded.cue', 'Loaded (Track 01).bin', 'Loaded (Track 02).bin']);
223
+ expect(got?.rejected).toEqual([]);
224
+ });
225
+
226
+ test('a folder with nothing acceptable in it is still a refusal, under its own name — never silence', async () => {
227
+ let result: { accepted: File[]; rejected: File[] } | null = null;
228
+ const { container } = render(
229
+ <FileDropZone accept='.zip' onFiles={() => {}} onDrop={(r) => (result = r)} />,
230
+ );
231
+ const loose = file('game.zip', 'application/zip');
232
+ dropEntries(
233
+ container.firstElementChild as HTMLElement,
234
+ [fileEntry(loose), dirEntry('Screenshots', [fileEntry(file('a.png', 'image/png'))])],
235
+ [loose, file('Screenshots', '')],
236
+ );
237
+ for (let i = 0; i < 10 && !result; i++) await settle();
238
+ const got = result as { accepted: File[]; rejected: File[] } | null;
239
+ expect(got?.accepted.map((f) => f.name)).toEqual(['game.zip']);
240
+ expect(got?.rejected.map((f) => f.name)).toEqual(['Screenshots']);
241
+ });
242
+ });
243
+
165
244
  test('renders custom children and the default hint otherwise', () => {
166
245
  const { getByText, rerender } = render(
167
246
  <FileDropZone onFiles={() => {}}>
@@ -42,6 +42,83 @@ export interface FileDropZoneProps {
42
42
  className?: string;
43
43
  }
44
44
 
45
+ /** The slice of the File and Directory Entries API a drop needs — every browser ships it. */
46
+ interface DropEntry {
47
+ isFile: boolean;
48
+ isDirectory: boolean;
49
+ name: string;
50
+ file?: (ok: (file: File) => void, fail: (error: unknown) => void) => void;
51
+ createReader?: () => {
52
+ readEntries: (ok: (entries: DropEntry[]) => void, fail: (error: unknown) => void) => void;
53
+ };
54
+ }
55
+
56
+ /** What a folder can hold before walking it stops — a drop, not a disk scan. */
57
+ const MAX_FOLDER_FILES = 5000;
58
+
59
+ const isHidden = (name: string) => name.startsWith('.');
60
+
61
+ /** Every file under `entry`, depth-first. A directory's reader answers in batches until empty. */
62
+ async function filesUnder(entry: DropEntry, out: File[]): Promise<void> {
63
+ if (out.length >= MAX_FOLDER_FILES || isHidden(entry.name)) return;
64
+ if (entry.isFile && entry.file) {
65
+ const read = entry.file.bind(entry);
66
+ out.push(await new Promise<File>((ok, fail) => read(ok, fail)));
67
+ return;
68
+ }
69
+ if (!entry.isDirectory || !entry.createReader) return;
70
+ const reader = entry.createReader();
71
+ while (out.length < MAX_FOLDER_FILES) {
72
+ const batch = await new Promise<DropEntry[]>((ok, fail) => reader.readEntries(ok, fail));
73
+ if (batch.length === 0) return;
74
+ for (const child of batch) await filesUnder(child, out);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * A drop's files, with every dropped FOLDER replaced by the accepted files inside it — see the
80
+ * FOLDER section above. `null` when nothing in the drop is a folder, so the ordinary path stays
81
+ * exactly `dataTransfer.files`.
82
+ *
83
+ * 🔴 The entries are taken SYNCHRONOUSLY, before the first `await`: a browser empties the
84
+ * `DataTransfer` once the drop event returns.
85
+ */
86
+ function folderAwareDrop(
87
+ transfer: DataTransfer,
88
+ accept: string | undefined,
89
+ ): Promise<{ loose: File[]; fromFolders: File[]; emptyFolders: File[] }> | null {
90
+ const items = transfer.items ? [...transfer.items] : [];
91
+ const entries = items.map((item) =>
92
+ item.kind === 'file' && typeof item.webkitGetAsEntry === 'function'
93
+ ? (item.webkitGetAsEntry() as DropEntry | null)
94
+ : null,
95
+ );
96
+ if (!entries.some((entry) => entry?.isDirectory)) return null;
97
+ const looseFiles = [...transfer.files];
98
+ return (async () => {
99
+ const loose: File[] = [];
100
+ const fromFolders: File[] = [];
101
+ const emptyFolders: File[] = [];
102
+ for (const [i, entry] of entries.entries()) {
103
+ if (!entry?.isDirectory) {
104
+ const file = looseFiles[i] ?? (entry ? await new Promise<File | null>((ok) => entry.file?.(ok, () => ok(null))) : null);
105
+ if (file) loose.push(file);
106
+ continue;
107
+ }
108
+ const inside: File[] = [];
109
+ try {
110
+ await filesUnder(entry, inside);
111
+ } catch {
112
+ // An unreadable folder yields what was read before the error — and, if that is nothing, a refusal below.
113
+ }
114
+ const kept = inside.filter((f) => matchesAccept(f, accept));
115
+ if (kept.length > 0) fromFolders.push(...kept);
116
+ else emptyFolders.push(new File([], entry.name));
117
+ }
118
+ return { loose, fromFolders, emptyFolders };
119
+ })();
120
+ }
121
+
45
122
  const matchesAccept = (file: File, accept?: string): boolean => {
46
123
  if (!accept) return true;
47
124
  return accept.split(',').some((raw) => {
@@ -116,6 +193,25 @@ const matchesAccept = (file: File, accept?: string): boolean => {
116
193
  * The notice is inline and dismissible, never a modal or a toast: it belongs
117
194
  * beside the zone the files were aimed at, and it must not auto-dismiss, because
118
195
  * a message the user did not get to read is the same as no message.
196
+ *
197
+ * ── 🔴 A DROPPED FOLDER IS ITS FILES (2026-09-28) ─────────────────────────────
198
+ * `dataTransfer.files` lists a dropped folder as ONE `File` — the folder's own
199
+ * name, no extension, no bytes — so it failed every `accept` and reached the app
200
+ * as a single refusal. Found in roms: the owner double-clicked a `.7z` in Finder
201
+ * and dropped the folder it unpacked to (a PlayStation disc's `.cue` and thirty
202
+ * `.bin`s, one level down) and got "not a file this accepts" for the lot, when
203
+ * what he dropped was exactly the files the zone takes. Finder's "unpack, then
204
+ * drag the result" is the ordinary way a person hands over a set of files.
205
+ *
206
+ * So a folder is walked (`webkitGetAsEntry`, the only door every browser ships)
207
+ * and its files are judged one by one, as if they had been dropped loose. Two
208
+ * rules keep that honest rather than noisy:
209
+ *
210
+ * · inside a folder, a file `accept` turns away is SKIPPED, as an archive's
211
+ * readme is — the person dropped the folder, not its `.txt` — and hidden files
212
+ * (`.DS_Store`, AppleDouble `._x`) are never files anyone meant;
213
+ * · but a folder that yields NOTHING acceptable is itself reported as rejected,
214
+ * under its own name — the 2026-08-14 rule, one level up: a drop never vanishes.
119
215
  */
120
216
  export function FileDropZone({
121
217
  onFiles,
@@ -133,13 +229,13 @@ export function FileDropZone({
133
229
  const [dragOver, setDragOver] = useState(false);
134
230
  const [rejected, setRejected] = useState<string[]>([]);
135
231
 
136
- const emit = (list: FileList | File[] | null) => {
232
+ const emit = (list: FileList | File[] | null, fromFolders: File[] = [], emptyFolders: File[] = []) => {
137
233
  if (!list) return;
138
234
  const all = [...list];
139
- if (all.length === 0) return;
140
- const matched = all.filter((f) => matchesAccept(f, accept));
235
+ if (all.length === 0 && fromFolders.length === 0 && emptyFolders.length === 0) return;
236
+ const matched = [...all.filter((f) => matchesAccept(f, accept)), ...fromFolders];
141
237
  const accepted = multiple ? matched : matched.slice(0, 1);
142
- const turnedAway = all.filter((f) => !matchesAccept(f, accept));
238
+ const turnedAway = [...all.filter((f) => !matchesAccept(f, accept)), ...emptyFolders];
143
239
 
144
240
  // One gesture, one call — the whole reason this overload exists.
145
241
  if (onDropResult) {
@@ -161,7 +257,13 @@ export function FileDropZone({
161
257
  const onDrop = (e: DragEvent) => {
162
258
  e.preventDefault();
163
259
  setDragOver(false);
164
- if (!disabled) emit(e.dataTransfer.files);
260
+ if (disabled) return;
261
+ const walking = folderAwareDrop(e.dataTransfer, accept);
262
+ if (!walking) {
263
+ emit(e.dataTransfer.files);
264
+ return;
265
+ }
266
+ void walking.then(({ loose, fromFolders, emptyFolders }) => emit(loose, fromFolders, emptyFolders));
165
267
  };
166
268
 
167
269
  return (
@@ -1,7 +1,7 @@
1
1
  import { X } from 'lucide-react';
2
2
  import type { ReactNode } from 'react';
3
3
  import { cn } from '../lib/cn.js';
4
- import { Dialog, DialogContent, DialogTitle } from './dialog.js';
4
+ import { Dialog, DialogContent, DialogTitle, focusPanelOnOpen } from './dialog.js';
5
5
  import { IconButton } from './IconButton.js';
6
6
 
7
7
  export interface LightboxProps {
@@ -36,6 +36,7 @@ export function Lightbox({ open, onClose, children, caption }: LightboxProps) {
36
36
  'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
37
37
  )}
38
38
  onInteractOutside={() => onClose()}
39
+ onOpenAutoFocus={focusPanelOnOpen}
39
40
  >
40
41
  {/* Names the dialog for assistive tech (Radix requires a title). */}
41
42
  <DialogTitle className='sr-only'>{caption ?? 'Media viewer'}</DialogTitle>
@@ -3,6 +3,7 @@ import { cleanup, fireEvent, render, waitFor } from '@testing-library/react';
3
3
  import { confirm } from '../confirm.js';
4
4
  import { ConfirmDialog } from './ConfirmDialog.js';
5
5
  import { Drawer } from './Drawer.js';
6
+ import { Input } from './Input.js';
6
7
  import { Lightbox } from './Lightbox.js';
7
8
  import { Modal } from './Modal.js';
8
9
 
@@ -54,6 +55,31 @@ describe('Modal', () => {
54
55
  expect(contentPanel()?.className).toContain('max-h-[calc(100dvh-2rem)]');
55
56
  });
56
57
 
58
+ // 🔴 Radix's default auto-focus lands on the first focusable — always the header's X, whose
59
+ // tooltip opens on focus. Radix hands Escape to the TOPMOST layer, so the first Escape only shut
60
+ // the tooltip and the dialog needed two: measured in binary-server's admin, 2026-09-28.
61
+ test('opening focuses the panel, not the X — one Escape closes it', async () => {
62
+ let closed = 0;
63
+ render(
64
+ <Modal open onClose={() => closed++} title='t'>
65
+ <p>x</p>
66
+ </Modal>,
67
+ );
68
+ await waitFor(() => expect(document.activeElement).toBe(contentPanel() ?? null));
69
+ expect(body().querySelector('[role="tooltip"]')).toBeNull();
70
+ fireEvent.keyDown(document.activeElement as HTMLElement, { key: 'Escape' });
71
+ expect(closed).toBe(1);
72
+ });
73
+
74
+ test('a field with autoFocus keeps it — the panel only takes focus nothing inside claimed', async () => {
75
+ render(
76
+ <Modal open onClose={() => {}} isDirty={false} title='t'>
77
+ <Input aria-label='name' autoFocus />
78
+ </Modal>,
79
+ );
80
+ await waitFor(() => expect(document.activeElement).toBe(body().querySelector('[aria-label="name"]')));
81
+ });
82
+
57
83
  test('a clean close (the X) calls onClose directly — no discard prompt', () => {
58
84
  let closed = 0;
59
85
  render(
@@ -336,6 +362,18 @@ describe('Drawer', () => {
336
362
  expect(closed).toBe(1);
337
363
  });
338
364
 
365
+ test('opening focuses the panel, not the X — one Escape closes it', async () => {
366
+ let closed = 0;
367
+ render(
368
+ <Drawer open onClose={() => closed++} title='t'>
369
+ <p>x</p>
370
+ </Drawer>,
371
+ );
372
+ await waitFor(() => expect(document.activeElement?.getAttribute('role')).toBe('dialog'));
373
+ fireEvent.keyDown(document.activeElement as HTMLElement, { key: 'Escape' });
374
+ expect(closed).toBe(1);
375
+ });
376
+
339
377
  test('a plain click on the header (no drag) still lets the Close button work', () => {
340
378
  let closed = 0;
341
379
  render(
@@ -387,6 +425,18 @@ describe('Lightbox', () => {
387
425
  fireEvent.click(panel?.querySelector('[aria-label="Close"]') as HTMLElement);
388
426
  expect(closed).toBe(1);
389
427
  });
428
+
429
+ test('opening focuses the panel, not the X — one Escape closes it', async () => {
430
+ let closed = 0;
431
+ render(
432
+ <Lightbox open onClose={() => closed++}>
433
+ <img alt='' src='x.png' />
434
+ </Lightbox>,
435
+ );
436
+ await waitFor(() => expect(document.activeElement).toBe(dialogs()[0] ?? null));
437
+ fireEvent.keyDown(document.activeElement as HTMLElement, { key: 'Escape' });
438
+ expect(closed).toBe(1);
439
+ });
390
440
  });
391
441
 
392
442
  describe('Modal — DESIGN.md §6, a widescreen creation flow is TWO columns', () => {
@@ -3,7 +3,7 @@ import type { ReactNode } from 'react';
3
3
  import { useDiscardGuard } from '../hooks/useDiscardGuard.js';
4
4
  import { cn } from '../lib/cn.js';
5
5
  import { FooterSlot } from './ActionBar.js';
6
- import { Dialog, DialogContent, DialogTitle } from './dialog.js';
6
+ import { Dialog, DialogContent, DialogTitle, focusPanelOnOpen } from './dialog.js';
7
7
  import { IconButton } from './IconButton.js';
8
8
  import { ScrollArea } from './ScrollArea.js';
9
9
 
@@ -179,6 +179,7 @@ export function Modal({
179
179
  if (backdropClose) guardedClose();
180
180
  }}
181
181
  onInteractOutside={(e) => e.preventDefault()}
182
+ onOpenAutoFocus={focusPanelOnOpen}
182
183
  onEscapeKeyDown={(e) => {
183
184
  e.preventDefault();
184
185
  if (isConfirming) cancelDiscard();
@@ -21,6 +21,22 @@ export const DialogClose = DialogPrimitive.Close;
21
21
  export const DialogTitle = DialogPrimitive.Title;
22
22
  export const DialogDescription = DialogPrimitive.Description;
23
23
 
24
+ /**
25
+ * `onOpenAutoFocus` for a panel whose first focusable is its header's Close button (Modal,
26
+ * Drawer): focus the PANEL instead — Radix gives it `tabIndex=-1`, so Tab still reaches the X
27
+ * first and the focus trap is unchanged.
28
+ *
29
+ * 🔴 Radix's default lands on that X, and the X's tooltip opens on focus. Radix hands Escape to
30
+ * the TOPMOST dismissable layer, which was the tooltip: the first Escape shut the tooltip and the
31
+ * dialog needed a second one, with a "Close" bubble hanging over the header until then. Measured
32
+ * in binary-server's admin, 2026-09-28. A field with `autoFocus` keeps its focus, because Radix
33
+ * only fires this when nothing inside the panel has claimed focus yet.
34
+ */
35
+ export const focusPanelOnOpen = (e: Event): void => {
36
+ e.preventDefault();
37
+ (e.currentTarget as HTMLElement | null)?.focus();
38
+ };
39
+
24
40
  /** Shared dimmer behind every overlay: a fixed, blurred scrim that fades with state. */
25
41
  export const DialogOverlay = forwardRef<
26
42
  HTMLDivElement,