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.
- package/CHANGELOG.md +94 -0
- package/README.md +5 -1
- package/dist/DropdownMenu-DqT7ZQ0D.d.ts +39 -0
- package/dist/charts.js +24 -9
- package/dist/charts.js.map +1 -1
- package/dist/chat.js +7 -7
- package/dist/{chunk-MFAWJSMJ.js → chunk-6C2IUMY7.js} +4 -23
- package/dist/chunk-6C2IUMY7.js.map +1 -0
- package/dist/{chunk-OYNUU346.js → chunk-7QLXYRPD.js} +3 -12
- package/dist/chunk-7QLXYRPD.js.map +1 -0
- package/dist/{chunk-FD4YED7O.js → chunk-DBHVJ7Z3.js} +5 -49
- package/dist/chunk-DBHVJ7Z3.js.map +1 -0
- package/dist/chunk-IELIF4PJ.js +110 -0
- package/dist/chunk-IELIF4PJ.js.map +1 -0
- package/dist/chunk-ISONVOA7.js +316 -0
- package/dist/chunk-ISONVOA7.js.map +1 -0
- package/dist/{chunk-4TUJXDRU.js → chunk-KMKBOTFR.js} +93 -32
- package/dist/chunk-KMKBOTFR.js.map +1 -0
- package/dist/{chunk-MKFWHWBK.js → chunk-L6HWACK7.js} +7 -7
- package/dist/chunk-L6HWACK7.js.map +1 -0
- package/dist/chunk-LJYSVZMI.js +25 -0
- package/dist/chunk-LJYSVZMI.js.map +1 -0
- package/dist/{chunk-6A5RHW2W.js → chunk-NEUWZUK6.js} +4 -5
- package/dist/chunk-NEUWZUK6.js.map +1 -0
- package/dist/chunk-NKUCS67T.js +13 -0
- package/dist/chunk-NKUCS67T.js.map +1 -0
- package/dist/{chunk-RECCCA7Z.js → chunk-OVHBQT3Z.js} +6 -5
- package/dist/chunk-OVHBQT3Z.js.map +1 -0
- package/dist/{chunk-I3MTQ3CS.js → chunk-OX3NNMWG.js} +6 -6
- package/dist/{chunk-I3MTQ3CS.js.map → chunk-OX3NNMWG.js.map} +1 -1
- package/dist/{chunk-ZI3OQDET.js → chunk-R5QXMNW7.js} +297 -15
- package/dist/chunk-R5QXMNW7.js.map +1 -0
- package/dist/chunk-SJZWEBHL.js +58 -0
- package/dist/chunk-SJZWEBHL.js.map +1 -0
- package/dist/{chunk-FUKSLQ77.js → chunk-SLRTSBCJ.js} +91 -51
- package/dist/chunk-SLRTSBCJ.js.map +1 -0
- package/dist/chunk-T52LAFKG.js +1350 -0
- package/dist/chunk-T52LAFKG.js.map +1 -0
- package/dist/chunk-TYNDXMBV.js +105 -0
- package/dist/chunk-TYNDXMBV.js.map +1 -0
- package/dist/{chunk-37WOC7F7.js → chunk-UDJOIVKQ.js} +31 -113
- package/dist/chunk-UDJOIVKQ.js.map +1 -0
- package/dist/{chunk-RJEUQ7RL.js → chunk-ULCACQSA.js} +47 -90
- package/dist/chunk-ULCACQSA.js.map +1 -0
- package/dist/{chunk-HP7WUZPK.js → chunk-WIUGPZOH.js} +10 -3
- package/dist/chunk-WIUGPZOH.js.map +1 -0
- package/dist/{chunk-JTU7OBYJ.js → chunk-YL5KD5RC.js} +4 -3
- package/dist/chunk-YL5KD5RC.js.map +1 -0
- package/dist/data-grid.d.ts +25 -6
- package/dist/data-grid.js +180 -58
- package/dist/data-grid.js.map +1 -1
- package/dist/editor-mentions.js +4 -5
- package/dist/editor-mentions.js.map +1 -1
- package/dist/editor.js +11 -8
- package/dist/editor.js.map +1 -1
- package/dist/{feather-FNBQ74RR.js → feather-XFFEFLTV.js} +5 -3
- package/dist/feather-XFFEFLTV.js.map +1 -0
- package/dist/{heroicons-DODCSYWD.js → heroicons-L6DOHBEA.js} +5 -3
- package/dist/heroicons-L6DOHBEA.js.map +1 -0
- package/dist/icons.d.ts +1 -1
- package/dist/icons.js +1 -1
- package/dist/index.d.ts +138 -39
- package/dist/index.js +846 -1667
- package/dist/index.js.map +1 -1
- package/dist/kanban.d.ts +26 -8
- package/dist/kanban.js +204 -79
- package/dist/kanban.js.map +1 -1
- package/dist/{lucide-ZWE3EPGP.js → lucide-JOORDTO6.js} +5 -3
- package/dist/lucide-JOORDTO6.js.map +1 -0
- package/dist/{phosphor-DIHPA4AJ.js → phosphor-TKGDYNGR.js} +5 -3
- package/dist/phosphor-TKGDYNGR.js.map +1 -0
- package/dist/resizable.js +47 -16
- package/dist/resizable.js.map +1 -1
- package/dist/roots.css +12 -8
- package/dist/{table-utils-CxnpFHRF.d.ts → table-utils-BXkP-2Hx.d.ts} +3 -1
- package/dist/{tabler-D3VYTSGI.js → tabler-YGETLYRU.js} +5 -3
- package/dist/tabler-YGETLYRU.js.map +1 -0
- package/dist/tokens.css +14 -0
- package/dist/utils.js.map +1 -1
- package/dist/virtualized.d.ts +6 -1
- package/dist/virtualized.js +17 -13
- package/dist/virtualized.js.map +1 -1
- package/dist-static/tokens.css +14 -0
- package/package.json +1 -1
- package/dist/chunk-37WOC7F7.js.map +0 -1
- package/dist/chunk-4TUJXDRU.js.map +0 -1
- package/dist/chunk-6A5RHW2W.js.map +0 -1
- package/dist/chunk-FD4YED7O.js.map +0 -1
- package/dist/chunk-FUKSLQ77.js.map +0 -1
- package/dist/chunk-HP7WUZPK.js.map +0 -1
- package/dist/chunk-JTU7OBYJ.js.map +0 -1
- package/dist/chunk-MFAWJSMJ.js.map +0 -1
- package/dist/chunk-MKFWHWBK.js.map +0 -1
- package/dist/chunk-OYNUU346.js.map +0 -1
- package/dist/chunk-QM4SJKWV.js +0 -51
- package/dist/chunk-QM4SJKWV.js.map +0 -1
- package/dist/chunk-QNHVPE2B.js +0 -117
- package/dist/chunk-QNHVPE2B.js.map +0 -1
- package/dist/chunk-RECCCA7Z.js.map +0 -1
- package/dist/chunk-RJEUQ7RL.js.map +0 -1
- package/dist/chunk-WH2FUFAC.js +0 -15
- package/dist/chunk-WH2FUFAC.js.map +0 -1
- package/dist/chunk-ZI3OQDET.js.map +0 -1
- package/dist/feather-FNBQ74RR.js.map +0 -1
- package/dist/heroicons-DODCSYWD.js.map +0 -1
- package/dist/lucide-ZWE3EPGP.js.map +0 -1
- package/dist/phosphor-DIHPA4AJ.js.map +0 -1
- 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 {
|
|
3
|
-
import {
|
|
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-
|
|
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-
|
|
8
|
-
import { useTextOverflow
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { useLatest } from './chunk-
|
|
12
|
-
import { cn, FOCUS_RING, FOCUS_INSET, lockBodyScroll, unlockBodyScroll, OVERLAY_SURFACE, GLASS_OVERLAY, GROUP_FOCUS_RING } from './chunk-
|
|
13
|
-
import { createContext, version, forwardRef, useState, useCallback, useRef, useEffect, useContext, useMemo,
|
|
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
|
|
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
|
-
|
|
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" &&
|
|
924
|
-
layout === "multi-column" &&
|
|
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" &&
|
|
930
|
-
layout === "stacked" &&
|
|
971
|
+
layout === "sidebar" && navbarSlot,
|
|
972
|
+
layout === "stacked" && navbarSlot,
|
|
931
973
|
/* @__PURE__ */ jsx(
|
|
932
974
|
"main",
|
|
933
975
|
{
|
|
934
|
-
ref:
|
|
976
|
+
ref: setMain,
|
|
935
977
|
id: mainId,
|
|
936
978
|
"data-shell-main": "",
|
|
937
979
|
tabIndex: -1,
|
|
938
980
|
className: cn(
|
|
939
|
-
|
|
940
|
-
|
|
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 &&
|
|
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
|
-
|
|
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-
|
|
1301
|
-
//# sourceMappingURL=chunk-
|
|
1361
|
+
//# sourceMappingURL=chunk-KMKBOTFR.js.map
|
|
1362
|
+
//# sourceMappingURL=chunk-KMKBOTFR.js.map
|