gridsmith-ui 0.29.1 → 0.30.1

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.
Files changed (108) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +5 -1
  3. package/dist/DropdownMenu-DqT7ZQ0D.d.ts +39 -0
  4. package/dist/charts.js +24 -9
  5. package/dist/charts.js.map +1 -1
  6. package/dist/chat.js +7 -7
  7. package/dist/{chunk-MFAWJSMJ.js → chunk-6C2IUMY7.js} +4 -23
  8. package/dist/chunk-6C2IUMY7.js.map +1 -0
  9. package/dist/{chunk-OYNUU346.js → chunk-7QLXYRPD.js} +3 -12
  10. package/dist/chunk-7QLXYRPD.js.map +1 -0
  11. package/dist/{chunk-FD4YED7O.js → chunk-DBHVJ7Z3.js} +5 -49
  12. package/dist/chunk-DBHVJ7Z3.js.map +1 -0
  13. package/dist/chunk-IELIF4PJ.js +110 -0
  14. package/dist/chunk-IELIF4PJ.js.map +1 -0
  15. package/dist/chunk-ISONVOA7.js +316 -0
  16. package/dist/chunk-ISONVOA7.js.map +1 -0
  17. package/dist/{chunk-4TUJXDRU.js → chunk-KMKBOTFR.js} +93 -32
  18. package/dist/chunk-KMKBOTFR.js.map +1 -0
  19. package/dist/{chunk-MKFWHWBK.js → chunk-L6HWACK7.js} +7 -7
  20. package/dist/chunk-L6HWACK7.js.map +1 -0
  21. package/dist/chunk-LJYSVZMI.js +25 -0
  22. package/dist/chunk-LJYSVZMI.js.map +1 -0
  23. package/dist/{chunk-6A5RHW2W.js → chunk-NEUWZUK6.js} +4 -5
  24. package/dist/chunk-NEUWZUK6.js.map +1 -0
  25. package/dist/chunk-NKUCS67T.js +13 -0
  26. package/dist/chunk-NKUCS67T.js.map +1 -0
  27. package/dist/{chunk-RECCCA7Z.js → chunk-OVHBQT3Z.js} +6 -5
  28. package/dist/chunk-OVHBQT3Z.js.map +1 -0
  29. package/dist/{chunk-I3MTQ3CS.js → chunk-OX3NNMWG.js} +6 -6
  30. package/dist/{chunk-I3MTQ3CS.js.map → chunk-OX3NNMWG.js.map} +1 -1
  31. package/dist/{chunk-ZI3OQDET.js → chunk-R5QXMNW7.js} +297 -15
  32. package/dist/chunk-R5QXMNW7.js.map +1 -0
  33. package/dist/chunk-SJZWEBHL.js +58 -0
  34. package/dist/chunk-SJZWEBHL.js.map +1 -0
  35. package/dist/{chunk-FUKSLQ77.js → chunk-SLRTSBCJ.js} +91 -51
  36. package/dist/chunk-SLRTSBCJ.js.map +1 -0
  37. package/dist/chunk-T52LAFKG.js +1350 -0
  38. package/dist/chunk-T52LAFKG.js.map +1 -0
  39. package/dist/chunk-TYNDXMBV.js +105 -0
  40. package/dist/chunk-TYNDXMBV.js.map +1 -0
  41. package/dist/{chunk-37WOC7F7.js → chunk-UDJOIVKQ.js} +31 -113
  42. package/dist/chunk-UDJOIVKQ.js.map +1 -0
  43. package/dist/{chunk-RJEUQ7RL.js → chunk-ULCACQSA.js} +47 -90
  44. package/dist/chunk-ULCACQSA.js.map +1 -0
  45. package/dist/{chunk-HP7WUZPK.js → chunk-WIUGPZOH.js} +10 -3
  46. package/dist/chunk-WIUGPZOH.js.map +1 -0
  47. package/dist/{chunk-JTU7OBYJ.js → chunk-YL5KD5RC.js} +4 -3
  48. package/dist/chunk-YL5KD5RC.js.map +1 -0
  49. package/dist/data-grid.d.ts +25 -6
  50. package/dist/data-grid.js +180 -58
  51. package/dist/data-grid.js.map +1 -1
  52. package/dist/editor-mentions.js +4 -5
  53. package/dist/editor-mentions.js.map +1 -1
  54. package/dist/editor.js +11 -8
  55. package/dist/editor.js.map +1 -1
  56. package/dist/{feather-FNBQ74RR.js → feather-XFFEFLTV.js} +5 -3
  57. package/dist/feather-XFFEFLTV.js.map +1 -0
  58. package/dist/{heroicons-DODCSYWD.js → heroicons-L6DOHBEA.js} +5 -3
  59. package/dist/heroicons-L6DOHBEA.js.map +1 -0
  60. package/dist/icons.d.ts +1 -1
  61. package/dist/icons.js +1 -1
  62. package/dist/index.d.ts +138 -39
  63. package/dist/index.js +846 -1667
  64. package/dist/index.js.map +1 -1
  65. package/dist/kanban.d.ts +26 -8
  66. package/dist/kanban.js +204 -79
  67. package/dist/kanban.js.map +1 -1
  68. package/dist/{lucide-ZWE3EPGP.js → lucide-JOORDTO6.js} +5 -3
  69. package/dist/lucide-JOORDTO6.js.map +1 -0
  70. package/dist/{phosphor-DIHPA4AJ.js → phosphor-TKGDYNGR.js} +5 -3
  71. package/dist/phosphor-TKGDYNGR.js.map +1 -0
  72. package/dist/resizable.js +47 -16
  73. package/dist/resizable.js.map +1 -1
  74. package/dist/roots.css +12 -8
  75. package/dist/{table-utils-CxnpFHRF.d.ts → table-utils-BXkP-2Hx.d.ts} +3 -1
  76. package/dist/{tabler-D3VYTSGI.js → tabler-YGETLYRU.js} +5 -3
  77. package/dist/tabler-YGETLYRU.js.map +1 -0
  78. package/dist/tokens.css +14 -0
  79. package/dist/utils.js.map +1 -1
  80. package/dist/virtualized.d.ts +6 -1
  81. package/dist/virtualized.js +17 -13
  82. package/dist/virtualized.js.map +1 -1
  83. package/dist-static/tokens.css +14 -0
  84. package/package.json +1 -1
  85. package/dist/chunk-37WOC7F7.js.map +0 -1
  86. package/dist/chunk-4TUJXDRU.js.map +0 -1
  87. package/dist/chunk-6A5RHW2W.js.map +0 -1
  88. package/dist/chunk-FD4YED7O.js.map +0 -1
  89. package/dist/chunk-FUKSLQ77.js.map +0 -1
  90. package/dist/chunk-HP7WUZPK.js.map +0 -1
  91. package/dist/chunk-JTU7OBYJ.js.map +0 -1
  92. package/dist/chunk-MFAWJSMJ.js.map +0 -1
  93. package/dist/chunk-MKFWHWBK.js.map +0 -1
  94. package/dist/chunk-OYNUU346.js.map +0 -1
  95. package/dist/chunk-QM4SJKWV.js +0 -51
  96. package/dist/chunk-QM4SJKWV.js.map +0 -1
  97. package/dist/chunk-QNHVPE2B.js +0 -117
  98. package/dist/chunk-QNHVPE2B.js.map +0 -1
  99. package/dist/chunk-RECCCA7Z.js.map +0 -1
  100. package/dist/chunk-RJEUQ7RL.js.map +0 -1
  101. package/dist/chunk-WH2FUFAC.js +0 -15
  102. package/dist/chunk-WH2FUFAC.js.map +0 -1
  103. package/dist/chunk-ZI3OQDET.js.map +0 -1
  104. package/dist/feather-FNBQ74RR.js.map +0 -1
  105. package/dist/heroicons-DODCSYWD.js.map +0 -1
  106. package/dist/lucide-ZWE3EPGP.js.map +0 -1
  107. package/dist/phosphor-DIHPA4AJ.js.map +0 -1
  108. package/dist/tabler-D3VYTSGI.js.map +0 -1
@@ -0,0 +1,316 @@
1
+ "use client";
2
+ import { triggerType } from './chunk-ULCACQSA.js';
3
+ import { useBreakpoint } from './chunk-ZHUVLBCC.js';
4
+ import { usePopupFocus } from './chunk-R5QXMNW7.js';
5
+ import { Icon } from './chunk-L6HWACK7.js';
6
+ import { Slot } from './chunk-7Q46JXSK.js';
7
+ import { usePosition } from './chunk-WIUGPZOH.js';
8
+ import { cn, GLASS_DROPDOWN, FOCUS_RING } from './chunk-YL5KD5RC.js';
9
+ import { useState, useRef, useEffect, useLayoutEffect } from 'react';
10
+ import { createPortal } from 'react-dom';
11
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
12
+
13
+ function isDivider(entry) {
14
+ return "type" in entry && entry.type === "divider";
15
+ }
16
+ function isSubmenu(entry) {
17
+ return "items" in entry;
18
+ }
19
+ var stopsOf = (entries) => entries.reduce((acc, e, i) => {
20
+ if (!isDivider(e) && !isUnavailable(e)) acc.push(i);
21
+ return acc;
22
+ }, []);
23
+ function isUnavailable(entry) {
24
+ return !!entry.disabled || isSubmenu(entry) && stopsOf(entry.items).length === 0;
25
+ }
26
+ var SUBMENU_GRACE_MS = 300;
27
+ var ITEM = "flex items-center gap-ds-2 w-full text-left px-ds-3 py-ds-2 text-sm cursor-pointer transition-colors rounded-[var(--ds-radius-sm)]";
28
+ var itemLook = (entry, open = false) => cn(
29
+ ITEM,
30
+ FOCUS_RING,
31
+ isUnavailable(entry) ? "opacity-disabled cursor-not-allowed" : "variant" in entry && entry.variant === "destructive" ? "text-[var(--ds-status-error-text)] hover:bg-[var(--ds-status-error-bg)] focus-visible:bg-[var(--ds-status-error-bg)]" : cn("text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-emphasis)] focus-visible:bg-[var(--ds-bg-emphasis)]", open && "bg-[var(--ds-bg-emphasis)]")
32
+ );
33
+ var PANEL = cn(
34
+ // the items' outline has its 4 px of room at any density (Sprint 30, P3)
35
+ "fixed z-[var(--ds-z-popover)] min-w-[180px] p-[max(var(--ds-space-1),4px)]",
36
+ "bg-[var(--ds-bg-surface-elevated)]",
37
+ GLASS_DROPDOWN,
38
+ "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
39
+ "rounded-[var(--ds-radius-md)] overflow-hidden"
40
+ );
41
+ var Divider = () => /* @__PURE__ */ jsx("div", { role: "separator", className: "my-ds-1 h-px bg-[var(--ds-border-default)]" });
42
+ function DropdownMenu({ trigger, items, align = "end", className, container }) {
43
+ const [open, setOpen] = useState(false);
44
+ const [focusedIdx, setFocusedIdx] = useState(-1);
45
+ const [sub, setSub] = useState(null);
46
+ const [subFocusedIdx, setSubFocusedIdx] = useState(-1);
47
+ const triggerRef = useRef(null);
48
+ const panelsRef = useRef(null);
49
+ const menuRef = useRef(null);
50
+ const subRef = useRef(null);
51
+ const subItemRef = useRef(null);
52
+ const isPhone = useBreakpoint("sm");
53
+ const graceTimer = useRef(null);
54
+ const cancelGrace = () => {
55
+ if (graceTimer.current) {
56
+ clearTimeout(graceTimer.current);
57
+ graceTimer.current = null;
58
+ }
59
+ };
60
+ const afterGrace = (then) => {
61
+ cancelGrace();
62
+ graceTimer.current = setTimeout(() => {
63
+ graceTimer.current = null;
64
+ then();
65
+ }, SUBMENU_GRACE_MS);
66
+ };
67
+ useEffect(() => {
68
+ if (!open) cancelGrace();
69
+ }, [open]);
70
+ useEffect(() => cancelGrace, []);
71
+ const pos = usePosition(triggerRef, menuRef, open, { side: "bottom", align, offset: 4, flip: true });
72
+ const [subPos, setSubPos] = useState({ top: 0, left: 0 });
73
+ useLayoutEffect(() => {
74
+ if (!open || sub === null || isPhone) return;
75
+ const place = () => {
76
+ const menu = menuRef.current;
77
+ const entry = subItemRef.current;
78
+ const panel = subRef.current;
79
+ if (!menu || !entry || !panel) return;
80
+ const m = menu.getBoundingClientRect();
81
+ const e = entry.getBoundingClientRect();
82
+ const p = panel.getBoundingClientRect();
83
+ const first = panel.querySelector("[data-sub-index]")?.getBoundingClientRect();
84
+ let left = m.right + 4;
85
+ if (left + p.width > window.innerWidth - 8) left = Math.max(8, m.left - 4 - p.width);
86
+ const top = Math.max(8, Math.min(e.top - (first ? first.top - p.top : 0), window.innerHeight - p.height - 8));
87
+ setSubPos((cur) => cur.top === top && cur.left === left ? cur : { top, left });
88
+ };
89
+ place();
90
+ window.addEventListener("resize", place);
91
+ window.addEventListener("scroll", place, true);
92
+ return () => {
93
+ window.removeEventListener("resize", place);
94
+ window.removeEventListener("scroll", place, true);
95
+ };
96
+ }, [open, sub, isPhone]);
97
+ const enabledIndices = stopsOf(items);
98
+ const subEntry = sub !== null && isSubmenu(items[sub]) ? items[sub] : null;
99
+ const subIndices = subEntry ? stopsOf(subEntry.items) : [];
100
+ const [prevOpen, setPrevOpen] = useState(open);
101
+ if (open !== prevOpen) {
102
+ setPrevOpen(open);
103
+ if (!open) {
104
+ setFocusedIdx(-1);
105
+ setSub(null);
106
+ setSubFocusedIdx(-1);
107
+ }
108
+ }
109
+ const { isTop: isTopLayer, returnFocus } = usePopupFocus(open, { trigger: triggerRef, panel: panelsRef, close: () => setOpen(false), tabExits: "any" });
110
+ const closeSub = () => {
111
+ if (sub === null) return;
112
+ setFocusedIdx(sub);
113
+ setSub(null);
114
+ setSubFocusedIdx(-1);
115
+ };
116
+ const openSub = (i, focusFirst) => {
117
+ const entry = items[i];
118
+ if (!isSubmenu(entry) || isUnavailable(entry)) return;
119
+ cancelGrace();
120
+ setSub(i);
121
+ setFocusedIdx(i);
122
+ setSubFocusedIdx(focusFirst ? stopsOf(entry.items)[0] ?? -1 : -1);
123
+ };
124
+ useEffect(() => {
125
+ if (!open) return;
126
+ const onKeyDown = (e) => {
127
+ if (e.key !== "Escape" || e.defaultPrevented || !isTopLayer()) return;
128
+ e.preventDefault();
129
+ if (sub !== null) {
130
+ closeSub();
131
+ return;
132
+ }
133
+ returnFocus();
134
+ setOpen(false);
135
+ };
136
+ const onClick = (e) => {
137
+ const target = e.target;
138
+ if (triggerRef.current?.contains(target) || panelsRef.current?.contains(target)) return;
139
+ setOpen(false);
140
+ };
141
+ document.addEventListener("keydown", onKeyDown);
142
+ document.addEventListener("mousedown", onClick);
143
+ return () => {
144
+ document.removeEventListener("keydown", onKeyDown);
145
+ document.removeEventListener("mousedown", onClick);
146
+ };
147
+ });
148
+ useEffect(() => {
149
+ if (!open) return;
150
+ if (sub !== null && subFocusedIdx >= 0) panelsRef.current?.querySelector(`[data-sub-index="${subFocusedIdx}"]`)?.focus();
151
+ else if (sub !== null && isPhone) panelsRef.current?.querySelector("[data-sub-back]")?.focus();
152
+ else if (focusedIdx >= 0) menuRef.current?.querySelector(`[data-index="${focusedIdx}"]`)?.focus();
153
+ }, [open, focusedIdx, sub, subFocusedIdx, isPhone]);
154
+ const step = (stops, at, by) => stops[((stops.indexOf(at) + by) % stops.length + stops.length) % stops.length];
155
+ function handleMenuKeyDown(e) {
156
+ const inSub = sub !== null;
157
+ const stops = inSub ? subIndices : enabledIndices;
158
+ const at = inSub ? subFocusedIdx : focusedIdx;
159
+ const go = (i) => {
160
+ if (i === void 0) return;
161
+ if (inSub) setSubFocusedIdx(i);
162
+ else setFocusedIdx(i);
163
+ };
164
+ if (e.key === "ArrowDown") {
165
+ e.preventDefault();
166
+ go(at < 0 ? stops[0] : step(stops, at, 1));
167
+ } else if (e.key === "ArrowUp") {
168
+ e.preventDefault();
169
+ go(at < 0 ? stops[stops.length - 1] : step(stops, at, -1));
170
+ } else if (e.key === "Home") {
171
+ e.preventDefault();
172
+ go(stops[0]);
173
+ } else if (e.key === "End") {
174
+ e.preventDefault();
175
+ go(stops[stops.length - 1]);
176
+ } else if (e.key === "ArrowRight" && !inSub && focusedIdx >= 0 && isSubmenu(items[focusedIdx])) {
177
+ e.preventDefault();
178
+ openSub(focusedIdx, true);
179
+ } else if (e.key === "ArrowLeft" && inSub) {
180
+ e.preventDefault();
181
+ closeSub();
182
+ }
183
+ }
184
+ const choose = (item) => {
185
+ if (item.disabled) return;
186
+ returnFocus();
187
+ item.onClick();
188
+ setOpen(false);
189
+ };
190
+ const subItems = (entries) => entries.map((item, i) => isDivider(item) ? /* @__PURE__ */ jsx(Divider, {}, i) : /* @__PURE__ */ jsxs("button", { type: "button", "data-sub-index": i, role: "menuitem", disabled: item.disabled, tabIndex: subFocusedIdx === i ? 0 : -1, onFocus: () => setSubFocusedIdx(i), onClick: () => choose(item), className: itemLook(item), children: [
191
+ item.icon && /* @__PURE__ */ jsx(Icon, { name: item.icon, size: 16, className: "shrink-0" }),
192
+ /* @__PURE__ */ jsx("span", { children: item.label })
193
+ ] }, i));
194
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
195
+ /* @__PURE__ */ jsx(
196
+ Slot,
197
+ {
198
+ ...triggerType(trigger),
199
+ "aria-haspopup": "menu",
200
+ "aria-expanded": open,
201
+ ref: triggerRef,
202
+ onClick: () => {
203
+ setOpen(!open);
204
+ if (!open && enabledIndices.length) setFocusedIdx(enabledIndices[0]);
205
+ },
206
+ children: trigger
207
+ }
208
+ ),
209
+ open && createPortal(
210
+ /* @__PURE__ */ jsxs("div", { ref: panelsRef, className: "contents", children: [
211
+ /* @__PURE__ */ jsx(
212
+ "div",
213
+ {
214
+ ref: menuRef,
215
+ role: "menu",
216
+ "data-gs": "menu",
217
+ "aria-label": isPhone && subEntry ? subEntry.label : void 0,
218
+ onKeyDown: handleMenuKeyDown,
219
+ className: cn(PANEL, className),
220
+ style: { top: pos.top, left: pos.left },
221
+ onClick: (e) => e.stopPropagation(),
222
+ children: isPhone && subEntry ? (
223
+ // on a phone the submenu takes the menu's place: Back first, its entry's name on it
224
+ /* @__PURE__ */ jsxs(Fragment, { children: [
225
+ /* @__PURE__ */ jsxs("button", { type: "button", "data-sub-back": "", role: "menuitem", "aria-label": `Back from ${subEntry.label}`, tabIndex: -1, onClick: closeSub, className: cn(itemLook(subEntry), "text-[var(--ds-text-secondary)] font-[var(--ds-weight-heading)]"), children: [
226
+ /* @__PURE__ */ jsx(Icon, { name: "chevron-left", size: 16, className: "shrink-0" }),
227
+ /* @__PURE__ */ jsx("span", { children: subEntry.label })
228
+ ] }),
229
+ /* @__PURE__ */ jsx(Divider, {}),
230
+ subItems(subEntry.items)
231
+ ] })
232
+ ) : items.map((item, i) => {
233
+ if (isDivider(item)) return /* @__PURE__ */ jsx(Divider, {}, i);
234
+ if (isSubmenu(item)) {
235
+ const isOpen = sub === i;
236
+ return /* @__PURE__ */ jsxs(
237
+ "button",
238
+ {
239
+ type: "button",
240
+ ref: isOpen ? (el) => {
241
+ subItemRef.current = el;
242
+ } : void 0,
243
+ "data-index": i,
244
+ role: "menuitem",
245
+ "aria-haspopup": "menu",
246
+ "aria-expanded": isOpen,
247
+ disabled: isUnavailable(item),
248
+ tabIndex: focusedIdx === i ? 0 : -1,
249
+ onFocus: () => setFocusedIdx(i),
250
+ onClick: () => openSub(i, true),
251
+ onMouseEnter: (e) => {
252
+ if (isPhone || e.buttons !== 0) return;
253
+ if (isOpen) cancelGrace();
254
+ else if (sub === null) openSub(i, false);
255
+ else afterGrace(() => openSub(i, false));
256
+ },
257
+ className: itemLook(item, isOpen),
258
+ children: [
259
+ item.icon && /* @__PURE__ */ jsx(Icon, { name: item.icon, size: 16, className: "shrink-0" }),
260
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: item.label }),
261
+ /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 14, className: "shrink-0 text-[var(--ds-text-muted)]" })
262
+ ]
263
+ },
264
+ i
265
+ );
266
+ }
267
+ return /* @__PURE__ */ jsxs(
268
+ "button",
269
+ {
270
+ type: "button",
271
+ "data-index": i,
272
+ role: "menuitem",
273
+ disabled: item.disabled,
274
+ tabIndex: focusedIdx === i ? 0 : -1,
275
+ onFocus: () => setFocusedIdx(i),
276
+ onClick: () => choose(item),
277
+ onMouseEnter: () => {
278
+ if (!isPhone && sub !== null) afterGrace(() => {
279
+ setSub(null);
280
+ setSubFocusedIdx(-1);
281
+ });
282
+ },
283
+ className: itemLook(item),
284
+ children: [
285
+ item.icon && /* @__PURE__ */ jsx(Icon, { name: item.icon, size: 16, className: "shrink-0" }),
286
+ /* @__PURE__ */ jsx("span", { children: item.label })
287
+ ]
288
+ },
289
+ i
290
+ );
291
+ })
292
+ }
293
+ ),
294
+ !isPhone && subEntry && /* @__PURE__ */ jsx(
295
+ "div",
296
+ {
297
+ ref: subRef,
298
+ role: "menu",
299
+ "aria-label": subEntry.label,
300
+ onKeyDown: handleMenuKeyDown,
301
+ className: PANEL,
302
+ style: { top: subPos.top, left: subPos.left },
303
+ onClick: (e) => e.stopPropagation(),
304
+ onMouseEnter: cancelGrace,
305
+ children: subItems(subEntry.items)
306
+ }
307
+ )
308
+ ] }),
309
+ container ?? document.body
310
+ )
311
+ ] });
312
+ }
313
+
314
+ export { DropdownMenu };
315
+ //# sourceMappingURL=chunk-ISONVOA7.js.map
316
+ //# sourceMappingURL=chunk-ISONVOA7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../showcase/src/components/DropdownMenu.tsx"],"names":[],"mappings":"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","file":"chunk-ISONVOA7.js","sourcesContent":["import { useRef, useEffect, useLayoutEffect, useState, type ReactNode, type KeyboardEvent as ReactKeyboardEvent } from \"react\";\nimport { useBreakpoint } from \"../lib/use-breakpoint\";\nimport { createPortal } from \"react-dom\";\nimport { cn, FOCUS_RING, GLASS_DROPDOWN } from \"../lib/utils\";\nimport { Icon, type IconName } from \"../lib/icons\";\nimport { Slot } from \"../lib/slot\";\nimport { triggerType } from \"./Button\";\nimport { usePosition, type Align } from \"../lib/use-position\";\nimport { usePopupFocus } from \"../lib/focus\";\n\nexport interface DropdownMenuItem {\n label: string;\n icon?: IconName;\n onClick: () => void;\n variant?: \"default\" | \"destructive\";\n disabled?: boolean;\n}\n\nexport interface DropdownMenuDivider {\n type: \"divider\";\n}\n\n/**\n * An entry that opens a submenu of its own (Sprint 25.8.7's look check: a card's Move among its other actions), one\n * level deep. On a desktop it opens beside the menu: → or Enter opens it, its first item focused; ← or Escape closes it,\n * focus back on this entry; a hover opens it too. On a phone its items take the menu's place, under a Back item.\n */\nexport interface DropdownMenuSubmenu {\n label: string;\n icon?: IconName;\n items: (DropdownMenuItem | DropdownMenuDivider)[];\n disabled?: boolean;\n}\n\nexport type DropdownMenuEntry = DropdownMenuItem | DropdownMenuDivider | DropdownMenuSubmenu;\n\nfunction isDivider(entry: DropdownMenuEntry): entry is DropdownMenuDivider {\n return \"type\" in entry && entry.type === \"divider\";\n}\nfunction isSubmenu(entry: DropdownMenuEntry): entry is DropdownMenuSubmenu {\n return \"items\" in entry;\n}\n/** The indices of a list's items the arrows stop at: no divider, nothing unavailable. */\nconst stopsOf = (entries: DropdownMenuEntry[]): number[] => entries.reduce<number[]>((acc, e, i) => {\n if (!isDivider(e) && !isUnavailable(e)) acc.push(i);\n return acc;\n}, []);\n/** An entry that can't be chosen: disabled, or a submenu with nothing in it to choose, which never opens empty. */\nfunction isUnavailable(entry: DropdownMenuItem | DropdownMenuSubmenu): boolean {\n return !!entry.disabled || (isSubmenu(entry) && stopsOf(entry.items).length === 0);\n}\n/**\n * How long a hover on another entry waits before it closes the open submenu (or opens the other's): a pointer on its way\n * to the submenu crosses the entries below its own, and reaching the submenu in that time keeps it open.\n */\nconst SUBMENU_GRACE_MS = 300;\n\nconst ITEM = \"flex items-center gap-ds-2 w-full text-left px-ds-3 py-ds-2 text-sm cursor-pointer transition-colors rounded-[var(--ds-radius-sm)]\";\nconst itemLook = (entry: DropdownMenuItem | DropdownMenuSubmenu, open = false) => cn(\n ITEM,\n FOCUS_RING,\n isUnavailable(entry)\n ? \"opacity-disabled cursor-not-allowed\"\n : \"variant\" in entry && entry.variant === \"destructive\"\n ? \"text-[var(--ds-status-error-text)] hover:bg-[var(--ds-status-error-bg)] focus-visible:bg-[var(--ds-status-error-bg)]\"\n : cn(\"text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-emphasis)] focus-visible:bg-[var(--ds-bg-emphasis)]\", open && \"bg-[var(--ds-bg-emphasis)]\"),\n);\nconst PANEL = cn(\n // the items' outline has its 4 px of room at any density (Sprint 30, P3)\n \"fixed z-[var(--ds-z-popover)] min-w-[180px] p-[max(var(--ds-space-1),4px)]\",\n \"bg-[var(--ds-bg-surface-elevated)]\",\n GLASS_DROPDOWN,\n \"border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]\",\n \"rounded-[var(--ds-radius-md)] overflow-hidden\",\n);\nconst Divider = () => <div role=\"separator\" className=\"my-ds-1 h-px bg-[var(--ds-border-default)]\" />;\n\ninterface DropdownMenuProps {\n trigger: ReactNode;\n items: DropdownMenuEntry[];\n align?: Align;\n className?: string;\n container?: Element | DocumentFragment;\n}\n\nexport function DropdownMenu({ trigger, items, align = \"end\", className, container }: DropdownMenuProps) {\n const [open, setOpen] = useState(false);\n const [focusedIdx, setFocusedIdx] = useState(-1);\n // the submenu open, by its entry's index, and the focus inside it\n const [sub, setSub] = useState<number | null>(null);\n const [subFocusedIdx, setSubFocusedIdx] = useState(-1);\n const triggerRef = useRef<HTMLElement>(null);\n // both panels, the menu and its submenu, sit in one box (display: contents) that is the popup's for focus and presses\n const panelsRef = useRef<HTMLDivElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n const subRef = useRef<HTMLDivElement>(null);\n const subItemRef = useRef<HTMLElement>(null);\n const isPhone = useBreakpoint(\"sm\");\n // a hover elsewhere, waiting before it closes the open submenu (SUBMENU_GRACE_MS)\n const graceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const cancelGrace = () => {\n if (graceTimer.current) { clearTimeout(graceTimer.current); graceTimer.current = null; }\n };\n const afterGrace = (then: () => void) => {\n cancelGrace();\n graceTimer.current = setTimeout(() => { graceTimer.current = null; then(); }, SUBMENU_GRACE_MS);\n };\n // closing the menu, or leaving the page, drops a hover's wait\n useEffect(() => { if (!open) cancelGrace(); }, [open]);\n useEffect(() => cancelGrace, []);\n\n const pos = usePosition(triggerRef, menuRef, open, { side: \"bottom\", align, offset: 4, flip: true });\n // beside the menu, 4 px from its edge, its first item level with its entry; on the other side where the screen has no\n // room, and inside the screen (measured against the menu, not the entry: the menu's padding and edge sit between them)\n const [subPos, setSubPos] = useState({ top: 0, left: 0 });\n useLayoutEffect(() => {\n if (!open || sub === null || isPhone) return;\n const place = () => {\n const menu = menuRef.current;\n const entry = subItemRef.current;\n const panel = subRef.current;\n if (!menu || !entry || !panel) return;\n const m = menu.getBoundingClientRect();\n const e = entry.getBoundingClientRect();\n const p = panel.getBoundingClientRect();\n const first = panel.querySelector(\"[data-sub-index]\")?.getBoundingClientRect();\n let left = m.right + 4;\n if (left + p.width > window.innerWidth - 8) left = Math.max(8, m.left - 4 - p.width);\n const top = Math.max(8, Math.min(e.top - (first ? first.top - p.top : 0), window.innerHeight - p.height - 8));\n setSubPos((cur) => (cur.top === top && cur.left === left ? cur : { top, left }));\n };\n place();\n window.addEventListener(\"resize\", place);\n window.addEventListener(\"scroll\", place, true);\n return () => {\n window.removeEventListener(\"resize\", place);\n window.removeEventListener(\"scroll\", place, true);\n };\n }, [open, sub, isPhone]);\n\n const enabledIndices = stopsOf(items);\n const subEntry = sub !== null && isSubmenu(items[sub]) ? (items[sub] as DropdownMenuSubmenu) : null;\n const subIndices = subEntry ? stopsOf(subEntry.items) : [];\n\n // Closing clears the keyboard focus index and the submenu (render-time state adjustment).\n const [prevOpen, setPrevOpen] = useState(open);\n if (open !== prevOpen) {\n setPrevOpen(open);\n if (!open) { setFocusedIdx(-1); setSub(null); setSubFocusedIdx(-1); }\n }\n\n // Sprint 25.7: focus hands back on close, and Tab out carries on from the trigger (lib/focus usePopupFocus).\n const { isTop: isTopLayer, returnFocus } = usePopupFocus(open, { trigger: triggerRef, panel: panelsRef, close: () => setOpen(false), tabExits: \"any\" });\n\n /** The submenu closes, and focus goes back to the entry that opened it. */\n const closeSub = () => {\n if (sub === null) return;\n setFocusedIdx(sub);\n setSub(null);\n setSubFocusedIdx(-1);\n };\n const openSub = (i: number, focusFirst: boolean) => {\n const entry = items[i];\n if (!isSubmenu(entry) || isUnavailable(entry)) return;\n cancelGrace();\n setSub(i);\n setFocusedIdx(i);\n setSubFocusedIdx(focusFirst ? stopsOf(entry.items)[0] ?? -1 : -1);\n };\n\n useEffect(() => {\n if (!open) return;\n\n // An Escape a control inside already handled is its own; the one this acts on is marked for overlays around it.\n // In a submenu it closes the submenu alone.\n const onKeyDown = (e: globalThis.KeyboardEvent) => {\n if (e.key !== \"Escape\" || e.defaultPrevented || !isTopLayer()) return;\n e.preventDefault();\n if (sub !== null) { closeSub(); return; }\n returnFocus();\n setOpen(false);\n };\n\n const onClick = (e: MouseEvent) => {\n const target = e.target as Node;\n if (triggerRef.current?.contains(target) || panelsRef.current?.contains(target)) return;\n setOpen(false);\n };\n\n document.addEventListener(\"keydown\", onKeyDown);\n document.addEventListener(\"mousedown\", onClick);\n return () => {\n document.removeEventListener(\"keydown\", onKeyDown);\n document.removeEventListener(\"mousedown\", onClick);\n };\n });\n\n useEffect(() => {\n if (!open) return;\n // By the item's own index: items and dividers share one numbering, so indexing the buttons alone left every\n // item after a divider unreachable by the arrows (the shell's user menu: Settings, divider, Log out).\n if (sub !== null && subFocusedIdx >= 0) panelsRef.current?.querySelector<HTMLElement>(`[data-sub-index=\"${subFocusedIdx}\"]`)?.focus();\n else if (sub !== null && isPhone) panelsRef.current?.querySelector<HTMLElement>(\"[data-sub-back]\")?.focus();\n else if (focusedIdx >= 0) menuRef.current?.querySelector<HTMLElement>(`[data-index=\"${focusedIdx}\"]`)?.focus();\n }, [open, focusedIdx, sub, subFocusedIdx, isPhone]);\n\n const step = (stops: number[], at: number, by: number) => stops[((stops.indexOf(at) + by) % stops.length + stops.length) % stops.length];\n function handleMenuKeyDown(e: ReactKeyboardEvent) {\n const inSub = sub !== null;\n const stops = inSub ? subIndices : enabledIndices;\n const at = inSub ? subFocusedIdx : focusedIdx;\n const go = (i: number | undefined) => { if (i === undefined) return; if (inSub) setSubFocusedIdx(i); else setFocusedIdx(i); };\n if (e.key === \"ArrowDown\") { e.preventDefault(); go(at < 0 ? stops[0] : step(stops, at, 1)); }\n else if (e.key === \"ArrowUp\") { e.preventDefault(); go(at < 0 ? stops[stops.length - 1] : step(stops, at, -1)); }\n else if (e.key === \"Home\") { e.preventDefault(); go(stops[0]); }\n else if (e.key === \"End\") { e.preventDefault(); go(stops[stops.length - 1]); }\n else if (e.key === \"ArrowRight\" && !inSub && focusedIdx >= 0 && isSubmenu(items[focusedIdx])) { e.preventDefault(); openSub(focusedIdx, true); }\n else if (e.key === \"ArrowLeft\" && inSub) { e.preventDefault(); closeSub(); }\n }\n\n /** A choice: focus back on the trigger first, so a dialog the item opens returns there (Sprint 25.7). */\n const choose = (item: DropdownMenuItem) => {\n if (item.disabled) return;\n returnFocus();\n item.onClick();\n setOpen(false);\n };\n\n const subItems = (entries: (DropdownMenuItem | DropdownMenuDivider)[]) => entries.map((item, i) => (isDivider(item) ? <Divider key={i} /> : (\n <button type=\"button\" key={i} data-sub-index={i} role=\"menuitem\" disabled={item.disabled} tabIndex={subFocusedIdx === i ? 0 : -1} onFocus={() => setSubFocusedIdx(i)} onClick={() => choose(item)} className={itemLook(item)}>\n {item.icon && <Icon name={item.icon} size={16} className=\"shrink-0\" />}\n <span>{item.label}</span>\n </button>\n )));\n\n return (\n <>\n <Slot\n {...triggerType(trigger)}\n // the APG's menu button: the trigger says it opens a menu, and whether it is open\n aria-haspopup=\"menu\"\n aria-expanded={open}\n ref={triggerRef}\n onClick={() => {\n setOpen(!open);\n if (!open && enabledIndices.length) setFocusedIdx(enabledIndices[0]);\n }}\n >\n {trigger}\n </Slot>\n {open && createPortal(\n <div ref={panelsRef} className=\"contents\">\n <div\n ref={menuRef}\n role=\"menu\"\n data-gs=\"menu\"\n aria-label={isPhone && subEntry ? subEntry.label : undefined}\n onKeyDown={handleMenuKeyDown}\n className={cn(PANEL, className)}\n style={{ top: pos.top, left: pos.left }}\n // A choice is the menu's own: the click must not also reach a clickable row or card the menu sits in\n // (React carries events from a portal to its React parents).\n onClick={(e) => e.stopPropagation()}\n >\n {isPhone && subEntry ? (\n // on a phone the submenu takes the menu's place: Back first, its entry's name on it\n <>\n <button type=\"button\" data-sub-back=\"\" role=\"menuitem\" aria-label={`Back from ${subEntry.label}`} tabIndex={-1} onClick={closeSub} className={cn(itemLook(subEntry), \"text-[var(--ds-text-secondary)] font-[var(--ds-weight-heading)]\")}>\n <Icon name=\"chevron-left\" size={16} className=\"shrink-0\" />\n <span>{subEntry.label}</span>\n </button>\n <Divider />\n {subItems(subEntry.items)}\n </>\n ) : items.map((item, i) => {\n if (isDivider(item)) return <Divider key={i} />;\n if (isSubmenu(item)) {\n const isOpen = sub === i;\n return (\n <button type=\"button\"\n key={i}\n ref={isOpen ? (el) => { subItemRef.current = el; } : undefined}\n data-index={i}\n role=\"menuitem\"\n aria-haspopup=\"menu\"\n aria-expanded={isOpen}\n disabled={isUnavailable(item)}\n tabIndex={focusedIdx === i ? 0 : -1}\n // the arrows go on from wherever focus is, however it got there (a click, a screen reader's command)\n onFocus={() => setFocusedIdx(i)}\n // Enter (its click) or a press goes into the submenu, its first item focused, whether a hover had\n // opened it or not (the APG's menu: Enter opens it and focuses its first item); ←, Escape or Back\n // closes it\n onClick={() => openSub(i, true)}\n // a hover opens it at once where no other is open; with another open it waits, as the pointer may be\n // on its way to that one (SUBMENU_GRACE_MS); back on its own entry, the wait for a close is dropped\n onMouseEnter={(e) => {\n if (isPhone || e.buttons !== 0) return;\n if (isOpen) cancelGrace();\n else if (sub === null) openSub(i, false);\n else afterGrace(() => openSub(i, false));\n }}\n className={itemLook(item, isOpen)}\n >\n {item.icon && <Icon name={item.icon} size={16} className=\"shrink-0\" />}\n <span className=\"flex-1\">{item.label}</span>\n <Icon name=\"chevron-right\" size={14} className=\"shrink-0 text-[var(--ds-text-muted)]\" />\n </button>\n );\n }\n return (\n <button type=\"button\"\n key={i}\n data-index={i}\n role=\"menuitem\"\n disabled={item.disabled}\n tabIndex={focusedIdx === i ? 0 : -1}\n onFocus={() => setFocusedIdx(i)}\n onClick={() => choose(item)}\n // a hover on another entry closes an open submenu, as moving on from it does, after a moment: the\n // pointer may only be crossing it on its way to the submenu (SUBMENU_GRACE_MS)\n onMouseEnter={() => { if (!isPhone && sub !== null) afterGrace(() => { setSub(null); setSubFocusedIdx(-1); }); }}\n className={itemLook(item)}\n >\n {item.icon && <Icon name={item.icon} size={16} className=\"shrink-0\" />}\n <span>{item.label}</span>\n </button>\n );\n })}\n </div>\n {!isPhone && subEntry && (\n <div\n ref={subRef}\n role=\"menu\"\n aria-label={subEntry.label}\n onKeyDown={handleMenuKeyDown}\n className={PANEL}\n style={{ top: subPos.top, left: subPos.left }}\n onClick={(e) => e.stopPropagation()}\n // the pointer has reached the submenu: a hover it crossed on the way closes nothing\n onMouseEnter={cancelGrace}\n >\n {subItems(subEntry.items)}\n </div>\n )}\n </div>,\n container ?? document.body\n )}\n </>\n );\n}\n"]}
@@ -1,16 +1,17 @@
1
1
  "use client";
2
- import { PreferencesContext, Button } from './chunk-RJEUQ7RL.js';
3
- import { useBreakpoint, readBreakpointPx } from './chunk-ZHUVLBCC.js';
2
+ import { useKeyboardScroll } from './chunk-SJZWEBHL.js';
3
+ import { PreferencesContext } from './chunk-TYNDXMBV.js';
4
4
  import { FormChangeCtx } from './chunk-72SDAX54.js';
5
- import { SurfaceBoundary } from './chunk-QM4SJKWV.js';
5
+ import { SurfaceBoundary, Button } from './chunk-ULCACQSA.js';
6
+ import { useBreakpoint, readBreakpointPx } from './chunk-ZHUVLBCC.js';
6
7
  import { useRevealFocus } from './chunk-HTXP5BMM.js';
7
- import { usePopupFocus, useOverlayLayer, useReturnFocus, useFocusTrap, focusInitial, useNonModalDialog, OVERLAY_CLOSE_ATTR } from './chunk-ZI3OQDET.js';
8
- import { useTextOverflow, AutoTooltip } from './chunk-37WOC7F7.js';
9
- import { Icon } from './chunk-MKFWHWBK.js';
10
- import { usePosition } from './chunk-HP7WUZPK.js';
11
- import { useLatest } from './chunk-WH2FUFAC.js';
12
- import { cn, FOCUS_RING, FOCUS_INSET, lockBodyScroll, unlockBodyScroll, OVERLAY_SURFACE, GLASS_OVERLAY, GROUP_FOCUS_RING } from './chunk-JTU7OBYJ.js';
13
- import { createContext, version, forwardRef, useState, useCallback, useRef, useEffect, useContext, useMemo, useId, useLayoutEffect } from 'react';
8
+ import { usePopupFocus, useOverlayLayer, useReturnFocus, useFocusTrap, inertOutside, focusInitial, useNonModalDialog, OVERLAY_CLOSE_ATTR } from './chunk-R5QXMNW7.js';
9
+ import { useTextOverflow } from './chunk-IELIF4PJ.js';
10
+ import { AutoTooltip } from './chunk-UDJOIVKQ.js';
11
+ import { Icon } from './chunk-L6HWACK7.js';
12
+ import { usePosition, useLatest } from './chunk-WIUGPZOH.js';
13
+ import { cn, FOCUS_INSET_WHILE_STOP, FOCUS_RING, FOCUS_INSET, lockBodyScroll, unlockBodyScroll, OVERLAY_SURFACE, GLASS_OVERLAY, GROUP_FOCUS_RING } from './chunk-YL5KD5RC.js';
14
+ import { createContext, version, forwardRef, useState, useCallback, useRef, useId, useEffect, useContext, useMemo, useLayoutEffect } from 'react';
14
15
  import { createPortal } from 'react-dom';
15
16
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
16
17
 
@@ -268,6 +269,7 @@ function useOverlayMark(open) {
268
269
  const round = useRef(0);
269
270
  const [changed, setChanged] = useState(false);
270
271
  const [holds, setHolds] = useState(0);
272
+ const [pends, setPends] = useState(0);
271
273
  const [answers, setAnswers] = useState([]);
272
274
  const [wasOpen, setWasOpen] = useState(open);
273
275
  if (open !== wasOpen) {
@@ -325,6 +327,15 @@ function useOverlayMark(open) {
325
327
  setHolds((n) => n - 1);
326
328
  };
327
329
  },
330
+ pend: () => {
331
+ setPends((n) => n + 1);
332
+ let released = false;
333
+ return () => {
334
+ if (released) return;
335
+ released = true;
336
+ setPends((n) => n - 1);
337
+ };
338
+ },
328
339
  open
329
340
  }), [open]);
330
341
  useEffect(() => {
@@ -345,7 +356,7 @@ function useOverlayMark(open) {
345
356
  setAnswers([]);
346
357
  setChanged([...forms.current.values()].some((f) => f.stamp > 0));
347
358
  }, [answers]);
348
- return { marked: changed || holds > 0, mark };
359
+ return { marked: changed || holds > 0, pending: pends > 0, mark };
349
360
  }
350
361
  var OverlayCloseCtx = createContext(null);
351
362
  function useDirtyGuard(dirty, onClose, onDiscard) {
@@ -439,6 +450,7 @@ function useScrollbarInset() {
439
450
  }
440
451
  var insetLessScrollbar = (gutter) => `max(0px, calc(${gutter} - var(--gs-scrollbar, 0px)))`;
441
452
  var DrawerFooterCtx = createContext(null);
453
+ var DrawerScopeCtx = createContext(null);
442
454
  var slideClasses = {
443
455
  right: {
444
456
  panel: "top-0 right-0 h-full",
@@ -466,8 +478,8 @@ function Drawer(props) {
466
478
  const shellWidth = useContext(DrawerWidthCtx) ?? void 0;
467
479
  const isPhone = useBreakpoint("sm");
468
480
  const mobileMode = mobile ?? (position === "left" && props.modal !== false ? "panel" : "sheet");
469
- const { marked, mark } = useOverlayMark(open);
470
- const guard = useDirtyGuard(dirty ?? marked, onClose, onDiscard);
481
+ const { marked, pending, mark } = useOverlayMark(open);
482
+ const guard = useDirtyGuard((dirty ?? marked) || pending, onClose, onDiscard);
471
483
  const confirm = /* @__PURE__ */ jsx(DiscardConfirm, { open: guard.confirming, onKeep: guard.keep, onDiscard: guard.discard });
472
484
  const [actionsMap, setActionsMap] = useState(/* @__PURE__ */ new Map());
473
485
  const registerActions = useCallback((formId, acts) => {
@@ -503,6 +515,9 @@ function DrawerPanel({ form, open, onClose, position = "right", width = "400px",
503
515
  const { ref: titleOverflowRef, isTruncated: titleTruncated } = useTextOverflow({ lines: 1 });
504
516
  const isMobile = useBreakpoint("sm");
505
517
  const panelRef = useRef(null);
518
+ const wrapperRef = useRef(null);
519
+ const scope = useContext(DrawerScopeCtx);
520
+ const scoped = !!scope && blocking && !sheet;
506
521
  const [mounted, setMounted] = useState(open);
507
522
  const [hidden, setHidden] = useState(!open);
508
523
  const [snapIdx, setSnapIdx] = useState(0);
@@ -538,7 +553,13 @@ function DrawerPanel({ form, open, onClose, position = "right", width = "400px",
538
553
  return () => unlockBodyScroll();
539
554
  }, [open, blocking, container]);
540
555
  useReturnFocus(open && !nonModal, panelRef);
541
- useFocusTrap(panelRef, open && blocking && (sheet || !topOffset), isTopLayer);
556
+ useFocusTrap(panelRef, open && blocking && (sheet || !topOffset || scoped), isTopLayer, scoped ? scope : null);
557
+ useEffect(() => {
558
+ const keep = scope?.current;
559
+ const own = wrapperRef.current;
560
+ if (!open || !mounted || !scoped || !keep || !own) return;
561
+ return inertOutside([keep, own], container instanceof Element ? container : document.body);
562
+ }, [open, mounted, scoped, scope, container]);
542
563
  useEffect(() => {
543
564
  if (!open || !mounted || nonModal) return;
544
565
  const raf = requestAnimationFrame(() => focusInitial(panelRef.current));
@@ -573,7 +594,13 @@ function DrawerPanel({ form, open, onClose, position = "right", width = "400px",
573
594
  const panel = panelRef.current;
574
595
  if (panel && !panel.contains(document.activeElement)) focusInitial(panel);
575
596
  }, [open, mounted, blocking]);
576
- const bodyRef = useScrollbarInset();
597
+ const bodyInset = useScrollbarInset();
598
+ const [bodyEl, setBodyEl] = useState(null);
599
+ const bodyRef = useCallback((el) => {
600
+ bodyInset(el);
601
+ setBodyEl(el);
602
+ }, [bodyInset]);
603
+ useKeyboardScroll(bodyEl, typeof title === "string" ? title : "Content", open);
577
604
  useCloseSettle(open, panelRef, () => {
578
605
  if (keepMounted) setHidden(true);
579
606
  else setMounted(false);
@@ -627,6 +654,7 @@ function DrawerPanel({ form, open, onClose, position = "right", width = "400px",
627
654
  /* @__PURE__ */ jsxs(
628
655
  "div",
629
656
  {
657
+ ref: wrapperRef,
630
658
  hidden: keepMounted && hidden ? true : void 0,
631
659
  className: cn(
632
660
  `${positionClass} transition-opacity duration-[var(--ds-transition-speed)]`,
@@ -657,7 +685,7 @@ function DrawerPanel({ form, open, onClose, position = "right", width = "400px",
657
685
  "data-gs": sheet ? "sheet" : "drawer",
658
686
  "data-ds-sheet": sheet ? "" : void 0,
659
687
  ...{ [OVERLAY_ROOT_ATTR]: "" },
660
- "aria-modal": blocking ? "true" : void 0,
688
+ "aria-modal": blocking && !scoped ? "true" : void 0,
661
689
  "aria-labelledby": title ? titleId : labelledBy,
662
690
  style: sheet ? {
663
691
  containerType: "inline-size",
@@ -731,7 +759,7 @@ function DrawerPanel({ form, open, onClose, position = "right", width = "400px",
731
759
  },
732
760
  children: [
733
761
  !sheet && titleTruncated ? /* @__PURE__ */ jsx(AutoTooltip, { content: title, children: /* @__PURE__ */ jsx("h2", { ref: titleOverflowRef, id: titleId, className: heading, children: title }) }) : /* @__PURE__ */ jsx("h2", { ref: titleOverflowRef, id: titleId, className: heading, children: title }),
734
- /* @__PURE__ */ jsx(
762
+ /* @__PURE__ */ jsx(AutoTooltip, { content: "Close", children: /* @__PURE__ */ jsx(
735
763
  "button",
736
764
  {
737
765
  type: "button",
@@ -741,7 +769,7 @@ function DrawerPanel({ form, open, onClose, position = "right", width = "400px",
741
769
  "aria-label": "Close",
742
770
  children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 18 })
743
771
  }
744
- )
772
+ ) })
745
773
  ]
746
774
  }
747
775
  ),
@@ -749,7 +777,7 @@ function DrawerPanel({ form, open, onClose, position = "right", width = "400px",
749
777
  "div",
750
778
  {
751
779
  ref: bodyRef,
752
- className: "flex-1 overflow-y-auto",
780
+ className: cn("flex-1 overflow-y-auto", FOCUS_INSET),
753
781
  style: flush ? void 0 : { padding: `var(--ds-space-5) ${insetLessScrollbar(INSET)} var(--ds-space-5) ${INSET}` },
754
782
  children: /* @__PURE__ */ jsx(SurfaceBoundary, { children })
755
783
  }
@@ -882,6 +910,15 @@ var AppShell = forwardRef(function AppShell2({
882
910
  const isHome = currentPath != null && currentPath === homePath;
883
911
  const hasFooter = footer != null && scrollable;
884
912
  const mainRef = useRef(null);
913
+ const [mainEl, setMainEl] = useState(null);
914
+ const setMain = useCallback((el) => {
915
+ mainRef.current = el;
916
+ setMainEl(el);
917
+ }, []);
918
+ useKeyboardScroll(mainEl, "Main content");
919
+ const navbarRef = useRef(null);
920
+ const navbarSlot = navbar != null && navbar !== false ? /* @__PURE__ */ jsx("div", { ref: navbarRef, "data-ds-navbar-slot": "", className: "contents", children: navbar }) : navbar;
921
+ const menuNameId = useId();
885
922
  useEffect(() => {
886
923
  const main = mainRef.current;
887
924
  if (!hasFooter || !shellEl || !main || typeof ResizeObserver === "undefined") {
@@ -916,28 +953,45 @@ var AppShell = forwardRef(function AppShell2({
916
953
  // with the toolbar hidden). Sprint 25.21 S1: h-dvh alone, which an app's own height class replaces through cn();
917
954
  // the supports-[…] variant that stood beside it survived that merge and beat the app's class (the builder's
918
955
  // preview ran under its footer). Every browser that draws the themes' oklch() colours has dvh.
919
- "relative h-dvh flex flex-col bg-[var(--ds-bg-canvas)] overflow-hidden",
956
+ // Sprint 25.8.7 item 31: clipped, not hidden. A box that hides its overflow is still a scroll container, which
957
+ // the browser scrolls to show a focused element past its edge: on run 73's roles page at 390 px it had moved
958
+ // 424 px after 15 Tab stops, and the next 50 stops sat in a blank band below <main>. Nothing can scroll a
959
+ // clipped box. The page scrolls in <main>, never here, and holds what it positions (below), so in a browser
960
+ // without overflow: clip (Safari 15) nothing of the library's runs past this box to need clipping.
961
+ "relative h-dvh flex flex-col bg-[var(--ds-bg-canvas)] overflow-clip",
920
962
  className
921
963
  ), children: [
922
964
  skipLink && /* @__PURE__ */ jsx(SkipLink, { href: `#${mainId}` }),
923
- layout === "sidebar-header" && navbar,
924
- layout === "multi-column" && navbar,
965
+ layout === "sidebar-header" && navbarSlot,
966
+ layout === "multi-column" && navbarSlot,
925
967
  /* @__PURE__ */ jsxs("div", { className: "flex flex-1 min-h-0", children: [
926
968
  hasSidebar && !isMobile && /* @__PURE__ */ jsx("div", { className: "shrink-0 h-full", children: sidebar }),
927
969
  aside && asideSide === "left" && aside,
928
970
  /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col min-w-0 min-h-0", children: [
929
- layout === "sidebar" && navbar,
930
- layout === "stacked" && navbar,
971
+ layout === "sidebar" && navbarSlot,
972
+ layout === "stacked" && navbarSlot,
931
973
  /* @__PURE__ */ jsx(
932
974
  "main",
933
975
  {
934
- ref: mainRef,
976
+ ref: setMain,
935
977
  id: mainId,
936
978
  "data-shell-main": "",
937
979
  tabIndex: -1,
938
980
  className: cn(
939
- "flex-1 min-h-0 focus:outline-none",
940
- scrollable ? "overflow-y-auto" : "relative",
981
+ // Sprint 25.8.7 item 31: <main> is the page's containing block, so what the page positions with no
982
+ // positioned box of its own (a Checkbox's visually hidden input) scrolls with the page, and Tab scrolls
983
+ // <main> to it. Placed against the shell instead, it stayed where the unscrolled page had put it, out of
984
+ // <main>'s reach, and focus scrolled the shell's outer box to show it
985
+ "relative flex-1 min-h-0 focus:outline-none",
986
+ // the skip link's target draws no ring (GOV.UK's); a Tab stop while it scrolls with nothing in it to Tab
987
+ // to, it draws the inset ring (Sprint 25.29 item 10)
988
+ FOCUS_INSET_WHILE_STOP,
989
+ // the outline's reach at the foot as scroll padding, which a control Tab reaches across the bottom edge
990
+ // stops short of (Sprint 25.29 item 19; a pinned PageHeader claims the top inline). The page's last control
991
+ // has its room from the page's own foot (PageContainer's bottom padding): padding here would sit under a
992
+ // view that fills the page, past the gutter its sides keep (Sprint 25.16 A42)
993
+ "scroll-pb-[4px]",
994
+ scrollable && "overflow-y-auto",
941
995
  layout === "multi-column" && "flex"
942
996
  ),
943
997
  style: { containerType: "inline-size" },
@@ -950,7 +1004,10 @@ var AppShell = forwardRef(function AppShell2({
950
1004
  ] }),
951
1005
  aside && asideSide === "right" && aside
952
1006
  ] }),
953
- showMobileDrawer && isMobile && /* @__PURE__ */ jsx(
1007
+ showMobileDrawer && isMobile && // Under the navbar, the menu is modal over the page with the navbar in its scope: the rest inert, Tab cycling the
1008
+ // two, Escape closing it (the Authoring Practices; Sprint 25.29 item 9). The stacked shell, with no offset, traps
1009
+ // in the menu alone.
1010
+ /* @__PURE__ */ jsx(DrawerScopeCtx, { value: layout !== "stacked" && navbar != null ? navbarRef : null, children: /* @__PURE__ */ jsxs(
954
1011
  Drawer,
955
1012
  {
956
1013
  open: sidebarOpen,
@@ -961,9 +1018,13 @@ var AppShell = forwardRef(function AppShell2({
961
1018
  flush: mobileMenuFlush,
962
1019
  lightBackdrop: mobileMenuLightBackdrop,
963
1020
  topOffset: layout !== "stacked" && navbar != null ? "var(--ds-navbar-height)" : void 0,
964
- children: mobileMenu
1021
+ labelledBy: menuNameId,
1022
+ children: [
1023
+ /* @__PURE__ */ jsx("span", { id: menuNameId, hidden: true, children: "Menu" }),
1024
+ mobileMenu
1025
+ ]
965
1026
  }
966
- ),
1027
+ ) }),
967
1028
  isSmallMobile && hasMobileNav && /* @__PURE__ */ jsx("div", { className: "sticky bottom-0 z-[var(--ds-z-sticky)]", children: mobileNav }),
968
1029
  !isSmallMobile && launcher
969
1030
  ] })
@@ -1297,5 +1358,5 @@ function useElementWidth(ref) {
1297
1358
  }
1298
1359
 
1299
1360
  export { AppShell, Breadcrumbs, DiscardConfirm, Drawer, DrawerFooterCtx, DrawerPanel, LAUNCHER_INSET_PRIORITY, NAVBAR_INSET_PRIORITY, OVERLAY_ROOT_ATTR, OverlayCloseCtx, OverlayMarkCtx, OverlayMarkScope, PANEL_INSET_PRIORITY, REACT_VERSION_PROBLEM, ResizeEdge, SkipLink, besideRoom, crumbGoes, defaultMaxWidth, insetLessScrollbar, pxOf, useAppShell, useBelow, useCloseSettle, useCompact, useCornerInset, useCrumbFollow, useDirtyGuard, useElementWidth, useOverlayMark, useRememberedWidth, useScrollbarInset, useUnsavedChanges };
1300
- //# sourceMappingURL=chunk-4TUJXDRU.js.map
1301
- //# sourceMappingURL=chunk-4TUJXDRU.js.map
1361
+ //# sourceMappingURL=chunk-KMKBOTFR.js.map
1362
+ //# sourceMappingURL=chunk-KMKBOTFR.js.map