@flood-md/react 0.2.6 → 0.2.8
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/README.md +42 -1
- package/dist/alert-dialog.js +1 -1
- package/dist/calendar-period-grid.d.ts +17 -0
- package/dist/calendar-period-grid.js +13 -0
- package/dist/calendar-value.d.ts +10 -0
- package/dist/calendar-value.js +48 -0
- package/dist/calendar.d.ts +21 -0
- package/dist/calendar.js +259 -0
- package/dist/checkbox.js +1 -1
- package/dist/choice-group.js +1 -1
- package/dist/collection-cards.d.ts +4 -0
- package/dist/collection-cards.js +21 -0
- package/dist/color-controls.d.ts +5 -0
- package/dist/color-controls.js +110 -0
- package/dist/combobox.js +22 -13
- package/dist/command-list.d.ts +3 -0
- package/dist/command-list.js +93 -0
- package/dist/content-elements.js +7 -7
- package/dist/context-menu.d.ts +2 -0
- package/dist/context-menu.js +76 -0
- package/dist/data-display.js +2 -2
- package/dist/data-table.d.ts +2 -0
- package/dist/data-table.js +58 -0
- package/dist/date-field.d.ts +2 -0
- package/dist/date-field.js +172 -0
- package/dist/date-picker.d.ts +3 -0
- package/dist/date-picker.js +108 -0
- package/dist/date-range-field.d.ts +3 -0
- package/dist/date-range-field.js +84 -0
- package/dist/date-range-picker.d.ts +2 -0
- package/dist/date-range-picker.js +81 -0
- package/dist/date-range-value.d.ts +8 -0
- package/dist/date-range-value.js +13 -0
- package/dist/date-time-picker.d.ts +3 -0
- package/dist/date-time-picker.js +31 -0
- package/dist/date-time-range-picker.d.ts +2 -0
- package/dist/date-time-range-picker.js +51 -0
- package/dist/date-value.d.ts +10 -0
- package/dist/date-value.js +31 -0
- package/dist/disclosure.js +1 -1
- package/dist/editable.d.ts +3 -0
- package/dist/editable.js +135 -0
- package/dist/error-summary.d.ts +3 -0
- package/dist/error-summary.js +39 -0
- package/dist/feedback-actions.js +2 -2
- package/dist/feedback.js +2 -2
- package/dist/file-input.js +1 -1
- package/dist/file-records.d.ts +5 -0
- package/dist/file-records.js +55 -0
- package/dist/form-controls.js +25 -13
- package/dist/foundations.css +387 -1
- package/dist/hover-card.d.ts +3 -0
- package/dist/hover-card.js +144 -0
- package/dist/index.d.ts +661 -2
- package/dist/index.js +190 -86
- package/dist/menu.d.ts +27 -1
- package/dist/menu.js +327 -89
- package/dist/navigation-layout.js +6 -6
- package/dist/navigation-tree.d.ts +3 -0
- package/dist/navigation-tree.js +98 -0
- package/dist/navigation.js +3 -3
- package/dist/numeric-input.js +1 -1
- package/dist/overlays.js +11 -10
- package/dist/range-calendar.d.ts +2 -0
- package/dist/range-calendar.js +35 -0
- package/dist/resizable-panels.d.ts +3 -0
- package/dist/resizable-panels.js +205 -0
- package/dist/scroll-area.js +1 -1
- package/dist/select.js +1 -1
- package/dist/slider.js +1 -1
- package/dist/tag-input.d.ts +3 -0
- package/dist/tag-input.js +181 -0
- package/dist/text-inputs.js +2 -2
- package/dist/time-field.d.ts +2 -0
- package/dist/time-field.js +176 -0
- package/dist/time-picker.d.ts +2 -0
- package/dist/time-picker.js +83 -0
- package/dist/time-value.d.ts +12 -0
- package/dist/time-value.js +36 -0
- package/dist/toolbar.js +1 -1
- package/dist/tree-select.d.ts +3 -0
- package/dist/tree-select.js +113 -0
- package/dist/tree.d.ts +3 -0
- package/dist/tree.js +206 -0
- package/package.json +2 -2
package/dist/menu.js
CHANGED
|
@@ -1,42 +1,127 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
|
|
2
|
+
import { Fragment, forwardRef, useCallback, useEffect, useId, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
|
|
3
3
|
import { Button, Kbd, ScrollArea } from "./index.js";
|
|
4
4
|
import { positionLayer, registerLayer, returnFocus, tabbables } from "./overlay-runtime.js";
|
|
5
5
|
const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
6
|
+
/** Validate each level while retaining globally unique action IDs. */
|
|
7
|
+
export function validateMenuTree(items) {
|
|
8
|
+
const ids = new Set();
|
|
9
|
+
const pending = [{ items, ancestors: new Set() }];
|
|
10
|
+
while (pending.length) {
|
|
11
|
+
const current = pending.pop();
|
|
12
|
+
if (current.ancestors.has(current.items))
|
|
13
|
+
throw new Error("Flood Menu does not accept recursive item arrays");
|
|
14
|
+
if (!current.items.some(item => item.type !== "separator"))
|
|
15
|
+
throw new Error("Flood Menu requires at least one action item in every menu");
|
|
16
|
+
const ancestors = new Set(current.ancestors);
|
|
17
|
+
ancestors.add(current.items);
|
|
18
|
+
const checkedGroups = new Set();
|
|
19
|
+
for (const item of current.items) {
|
|
20
|
+
if (!item.id.trim() || ids.has(item.id) || (item.type !== "separator" && !item.label.trim()))
|
|
21
|
+
throw new Error("Flood menu items need unique nonempty IDs across the tree and action labels");
|
|
22
|
+
ids.add(item.id);
|
|
23
|
+
if (item.type === "submenu")
|
|
24
|
+
pending.push({ items: item.items, ancestors });
|
|
25
|
+
if (item.type === "radio") {
|
|
26
|
+
if (!item.group.trim())
|
|
27
|
+
throw new Error("Flood Menu radio items need a nonempty group label");
|
|
28
|
+
if (item.checked && checkedGroups.has(item.group))
|
|
29
|
+
throw new Error("Flood Menu radio groups accept only one checked item");
|
|
30
|
+
if (item.checked)
|
|
31
|
+
checkedGroups.add(item.group);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const ownButtons = (panel) => Array.from(panel?.querySelectorAll('[role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"]') ?? [])
|
|
37
|
+
.filter(button => button.closest('[role="menu"]') === panel);
|
|
38
|
+
export function focusMenuEdge(panel, edge = "first") {
|
|
39
|
+
const all = ownButtons(panel);
|
|
40
|
+
((edge === "last" ? all.at(-1) : all[0]) ?? panel)?.focus({ preventScroll: true });
|
|
41
|
+
}
|
|
42
|
+
/** Internal native top-layer engine shared by Menu and ContextMenu. */
|
|
43
|
+
export const MenuLayer = /* @__PURE__ */ forwardRef(function MenuLayerRender({ label, items, open, requestOpen, closeTree, anchor, rootAnchor, rootPanel, onAction, edge, nested = false, position, side = "bottom", align = "start", controlSize = "sm", id: explicitId, className, onKeyDown, ...props }, forwardedRef) {
|
|
9
44
|
const generated = useId(), id = explicitId ?? `flood-menu-${generated}`;
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
45
|
+
const panel = useRef(null), anchors = useRef(new Map()), children = useRef(new Map());
|
|
46
|
+
const anchorRefs = useRef(new Map());
|
|
47
|
+
const childRefs = useRef(new Map());
|
|
48
|
+
const [openChild, setOpenChild] = useState(undefined);
|
|
49
|
+
const hover = useRef(undefined), pointerType = useRef("");
|
|
50
|
+
const hoverIntent = useRef(undefined);
|
|
51
|
+
const latestItems = useRef(new Map());
|
|
52
|
+
const search = useRef({ text: "", time: 0 }), pointerTarget = useRef(null);
|
|
53
|
+
const current = useRef({ open, requestOpen, closeTree, anchor, rootAnchor, rootPanel, position, edge });
|
|
54
|
+
current.current = { open, requestOpen, closeTree, anchor, rootAnchor, rootPanel, position, edge };
|
|
55
|
+
useImperativeHandle(forwardedRef, () => panel.current, []);
|
|
56
|
+
const clearHover = () => { clearTimeout(hover.current); hover.current = undefined; hoverIntent.current = undefined; };
|
|
57
|
+
useEffect(() => () => clearHover(), []);
|
|
58
|
+
useBrowserLayoutEffect(() => {
|
|
59
|
+
latestItems.current = new Map(items.map(item => [item.id, item]));
|
|
60
|
+
const intent = hoverIntent.current, target = intent && latestItems.current.get(intent.id);
|
|
61
|
+
// Ordinary host rerenders often create an equivalent array. Preserve the
|
|
62
|
+
// pending pointer intent unless the actual target can no longer receive it.
|
|
63
|
+
if (intent && (!target || target.type === "separator" || target.disabled || (target.type ?? "action") !== intent.type || !intent.node.isConnected || anchors.current.get(intent.id) !== intent.node))
|
|
64
|
+
clearHover();
|
|
65
|
+
const keys = new Set(items.filter(item => item.type !== "separator").map(item => item.id));
|
|
66
|
+
for (const key of anchorRefs.current.keys())
|
|
67
|
+
if (!keys.has(key))
|
|
68
|
+
anchorRefs.current.delete(key);
|
|
69
|
+
for (const key of childRefs.current.keys())
|
|
70
|
+
if (!keys.has(key))
|
|
71
|
+
childRefs.current.delete(key);
|
|
72
|
+
});
|
|
73
|
+
const anchorRef = (key) => {
|
|
74
|
+
if (!anchorRefs.current.has(key))
|
|
75
|
+
anchorRefs.current.set(key, node => { if (node)
|
|
76
|
+
anchors.current.set(key, node);
|
|
77
|
+
else
|
|
78
|
+
anchors.current.delete(key); });
|
|
79
|
+
return anchorRefs.current.get(key);
|
|
80
|
+
};
|
|
81
|
+
const childRef = (key) => {
|
|
82
|
+
if (!childRefs.current.has(key))
|
|
83
|
+
childRefs.current.set(key, node => { if (node)
|
|
84
|
+
children.current.set(key, node);
|
|
85
|
+
else
|
|
86
|
+
children.current.delete(key); });
|
|
87
|
+
return childRefs.current.get(key);
|
|
88
|
+
};
|
|
89
|
+
const wholePanel = () => current.current.rootPanel?.() ?? panel.current;
|
|
90
|
+
const place = (node) => {
|
|
91
|
+
const target = current.current.anchor();
|
|
92
|
+
if (!target)
|
|
18
93
|
return;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
|
|
94
|
+
const direction = node.ownerDocument.defaultView.getComputedStyle(target).direction;
|
|
95
|
+
node.dataset.menuDirection = direction;
|
|
96
|
+
if (current.current.position) {
|
|
97
|
+
current.current.position(node);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
positionLayer(target, node, nested ? direction === "rtl" ? "left" : "right" : side, nested ? "start" : align);
|
|
24
101
|
};
|
|
25
|
-
const current = useRef({ open, request });
|
|
26
|
-
useBrowserLayoutEffect(() => { current.current = { open, request }; });
|
|
27
|
-
const setAnchor = useCallback((node) => { anchor.current = node; }, []);
|
|
28
|
-
useImperativeHandle(ref, () => panel.current, []);
|
|
29
|
-
const buttons = () => Array.from(panel.current?.querySelectorAll('[role="menuitem"]') ?? []);
|
|
30
|
-
const focusEdge = () => { const all = buttons(); ((edge.current === "last" ? all.at(-1) : all[0]) ?? panel.current)?.focus({ preventScroll: true }); };
|
|
31
102
|
useBrowserLayoutEffect(() => {
|
|
32
|
-
if (!open || !panel.current)
|
|
103
|
+
if (!open || !panel.current) {
|
|
104
|
+
clearHover();
|
|
105
|
+
if (openChild !== undefined)
|
|
106
|
+
setOpenChild(undefined);
|
|
33
107
|
return;
|
|
108
|
+
}
|
|
34
109
|
const element = panel.current, document = element.ownerDocument, view = document.defaultView;
|
|
35
110
|
let alive = true, frame = 0, unregister = () => { };
|
|
111
|
+
const closeLevel = () => {
|
|
112
|
+
const active = document.activeElement;
|
|
113
|
+
if (nested && active && !wholePanel()?.contains(active) && !current.current.rootAnchor()?.contains(active))
|
|
114
|
+
current.current.closeTree();
|
|
115
|
+
else
|
|
116
|
+
current.current.requestOpen(false);
|
|
117
|
+
};
|
|
118
|
+
const trackPointer = (event) => { pointerTarget.current = event.target; };
|
|
119
|
+
document.addEventListener("pointerdown", trackPointer, true);
|
|
36
120
|
const start = () => {
|
|
37
|
-
|
|
121
|
+
const target = current.current.anchor();
|
|
122
|
+
if (!alive || !target?.isConnected)
|
|
38
123
|
return;
|
|
39
|
-
const parent =
|
|
124
|
+
const parent = target.closest("dialog");
|
|
40
125
|
if (parent && !parent.open) {
|
|
41
126
|
frame = view.requestAnimationFrame(start);
|
|
42
127
|
return;
|
|
@@ -45,86 +130,239 @@ export const ActionMenu = forwardRef(function ActionMenu({ label, trigger, items
|
|
|
45
130
|
throw new Error("Flood Menu requires the native Popover API");
|
|
46
131
|
element.style.visibility = "hidden";
|
|
47
132
|
element.showPopover();
|
|
48
|
-
|
|
49
|
-
unregister = registerLayer(document, { panel: element, anchor: () =>
|
|
133
|
+
place(element);
|
|
134
|
+
unregister = registerLayer(document, { panel: element, anchor: () => current.current.anchor(), close: closeLevel,
|
|
135
|
+
outside: () => {
|
|
136
|
+
if (nested && pointerTarget.current && wholePanel()?.contains(pointerTarget.current))
|
|
137
|
+
current.current.requestOpen(false);
|
|
138
|
+
else
|
|
139
|
+
current.current.closeTree();
|
|
140
|
+
} });
|
|
50
141
|
search.current = { text: "", time: 0 };
|
|
51
|
-
|
|
142
|
+
focusMenuEdge(element, current.current.edge?.() ?? "first");
|
|
52
143
|
const update = () => { if (!alive)
|
|
53
|
-
return; if (
|
|
54
|
-
|
|
144
|
+
return; if (current.current.anchor()?.isConnected)
|
|
145
|
+
place(element); frame = view.requestAnimationFrame(update); };
|
|
55
146
|
frame = view.requestAnimationFrame(update);
|
|
56
147
|
};
|
|
57
148
|
start();
|
|
58
149
|
const toggle = (event) => {
|
|
59
150
|
if (!alive || event.newState !== "closed" || element.matches(":popover-open") || !current.current.open)
|
|
60
151
|
return;
|
|
61
|
-
current.current.
|
|
62
|
-
queueMicrotask(() => {
|
|
63
|
-
element.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
152
|
+
current.current.requestOpen(false);
|
|
153
|
+
queueMicrotask(() => {
|
|
154
|
+
if (alive && current.current.open && element.isConnected && !element.matches(":popover-open") && current.current.anchor()?.isConnected) {
|
|
155
|
+
element.showPopover();
|
|
156
|
+
place(element);
|
|
157
|
+
focusMenuEdge(element, current.current.edge?.() ?? "first");
|
|
158
|
+
}
|
|
159
|
+
});
|
|
67
160
|
};
|
|
68
161
|
element.addEventListener("toggle", toggle);
|
|
69
|
-
return () => {
|
|
70
|
-
|
|
71
|
-
|
|
162
|
+
return () => {
|
|
163
|
+
alive = false;
|
|
164
|
+
clearHover();
|
|
165
|
+
view.cancelAnimationFrame(frame);
|
|
166
|
+
unregister();
|
|
167
|
+
document.removeEventListener("pointerdown", trackPointer, true);
|
|
168
|
+
element.removeEventListener("toggle", toggle);
|
|
169
|
+
returnFocus(element, current.current.anchor());
|
|
170
|
+
if (element.matches(":popover-open"))
|
|
171
|
+
element.hidePopover();
|
|
172
|
+
};
|
|
173
|
+
}, [open, side, align, nested]);
|
|
72
174
|
useBrowserLayoutEffect(() => {
|
|
175
|
+
if (openChild && !items.some(item => item.id === openChild && item.type === "submenu" && !item.disabled))
|
|
176
|
+
setOpenChild(undefined);
|
|
73
177
|
if (open && panel.current?.matches(":popover-open") && panel.current.ownerDocument.activeElement === panel.current.ownerDocument.body)
|
|
74
|
-
|
|
75
|
-
}, [items, open]);
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
178
|
+
focusMenuEdge(panel.current, edge?.() ?? "first");
|
|
179
|
+
}, [items, open, openChild]);
|
|
180
|
+
const showChild = (item) => {
|
|
181
|
+
if (item.disabled)
|
|
182
|
+
return;
|
|
183
|
+
clearHover();
|
|
184
|
+
if (openChild === item.id)
|
|
185
|
+
focusMenuEdge(children.current.get(item.id) ?? null);
|
|
81
186
|
else
|
|
82
|
-
|
|
187
|
+
setOpenChild(item.id);
|
|
188
|
+
};
|
|
189
|
+
const scheduleHover = (item, node) => {
|
|
190
|
+
clearHover();
|
|
191
|
+
if (!openChild && item.type !== "submenu") {
|
|
192
|
+
node.focus({ preventScroll: true });
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const intent = { id: item.id, type: item.type ?? "action", node };
|
|
196
|
+
hoverIntent.current = intent;
|
|
197
|
+
hover.current = setTimeout(() => {
|
|
198
|
+
if (hoverIntent.current !== intent)
|
|
199
|
+
return;
|
|
200
|
+
hover.current = undefined;
|
|
201
|
+
hoverIntent.current = undefined;
|
|
202
|
+
const target = latestItems.current.get(intent.id);
|
|
203
|
+
if (!target || target.type === "separator" || target.disabled || (target.type ?? "action") !== intent.type || !node.isConnected || anchors.current.get(intent.id) !== node || !panel.current?.matches(":popover-open"))
|
|
204
|
+
return;
|
|
205
|
+
node.focus({ preventScroll: true });
|
|
206
|
+
setOpenChild(target.type === "submenu" ? target.id : undefined);
|
|
207
|
+
}, 150);
|
|
83
208
|
};
|
|
84
209
|
const hasIcons = items.some(item => item.type !== "separator" && item.icon != null);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
210
|
+
const renderItem = (item) => {
|
|
211
|
+
if (item.type === "separator")
|
|
212
|
+
return _jsx("div", { role: "separator", className: "flood-menu__separator" }, item.id);
|
|
213
|
+
const submenu = item.type === "submenu", childId = `${id}-submenu-${encodeURIComponent(item.id)}`;
|
|
214
|
+
return _jsxs(Fragment, { children: [_jsxs(Button, { ref: anchorRef(item.id), role: item.type === "checkbox" ? "menuitemcheckbox" : item.type === "radio" ? "menuitemradio" : "menuitem", "aria-checked": item.type === "checkbox" || item.type === "radio" ? item.checked : undefined, "aria-haspopup": submenu ? "menu" : undefined, "aria-expanded": submenu ? openChild === item.id && !item.disabled : undefined, "aria-controls": submenu ? childId : undefined, tabIndex: -1, variant: "ghost", size: controlSize, "aria-label": item.label, "aria-disabled": item.disabled || undefined, "data-label": item.label, "data-menu-id": item.id, "data-tone": "tone" in item ? item.tone : undefined, "data-submenu": submenu || undefined, className: "flood-menu__item", onPointerDown: event => { pointerType.current = event.pointerType; }, onPointerEnter: event => { if (event.pointerType === "mouse")
|
|
215
|
+
scheduleHover(item, event.currentTarget); }, onPointerLeave: () => clearHover(), onPointerMove: event => {
|
|
216
|
+
if (event.pointerType === "touch" || openChild)
|
|
217
|
+
return;
|
|
218
|
+
if (event.currentTarget.ownerDocument.activeElement !== event.currentTarget)
|
|
219
|
+
event.currentTarget.focus({ preventScroll: true });
|
|
220
|
+
}, onClick: event => {
|
|
221
|
+
clearHover();
|
|
222
|
+
if (item.disabled) {
|
|
223
|
+
event.preventDefault();
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (item.type === "submenu") {
|
|
227
|
+
if (event.detail > 0 && pointerType.current === "touch" && openChild === item.id)
|
|
228
|
+
setOpenChild(undefined);
|
|
100
229
|
else
|
|
101
|
-
|
|
230
|
+
showChild(item);
|
|
102
231
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
232
|
+
else if (item.type === "checkbox") {
|
|
233
|
+
item.onCheckedChange(item.checked !== true);
|
|
234
|
+
if (item.closeOnSelect)
|
|
235
|
+
closeTree();
|
|
236
|
+
}
|
|
237
|
+
else if (item.type === "radio") {
|
|
238
|
+
if (!item.checked)
|
|
239
|
+
item.onCheckedChange(true);
|
|
240
|
+
if (item.closeOnSelect)
|
|
241
|
+
closeTree();
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
closeTree();
|
|
245
|
+
onAction?.(item.id);
|
|
246
|
+
}
|
|
247
|
+
}, children: [hasIcons && _jsx("span", { className: "flood-menu__icon", "aria-hidden": "true", children: item.icon }), _jsx("span", { className: "flood-menu__label", children: item.label }), "shortcut" in item && item.shortcut && _jsx("span", { className: "flood-menu__shortcut", "aria-hidden": "true", children: _jsx(Kbd, { children: item.shortcut }) }), (item.type === "checkbox" || item.type === "radio") && _jsx("span", { className: "flood-menu__icon flood-menu__indicator", "aria-hidden": "true", children: item.checked &&
|
|
248
|
+
_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: item.type === "radio" ? _jsx("circle", { cx: "12", cy: "12", r: "4", fill: "currentColor", stroke: "none" }) : item.checked === "mixed" ? _jsx("path", { d: "M6 12h12" }) : _jsx("path", { d: "m5 12 4 4L19 6" }) }) }), submenu && _jsx("span", { className: "flood-menu__icon flood-menu__submenu-indicator", "aria-hidden": "true", children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "m9 5 7 7-7 7" }) }) })] }), item.type === "submenu" && _jsx(MenuLayer, { id: childId, ref: childRef(item.id), label: item.label, items: item.items, open: openChild === item.id && !item.disabled, requestOpen: next => { if (next && !item.disabled)
|
|
249
|
+
setOpenChild(item.id);
|
|
250
|
+
else
|
|
251
|
+
setOpenChild(current => current === item.id ? undefined : current); }, closeTree: closeTree, anchor: () => anchors.current.get(item.id) ?? null, rootAnchor: rootAnchor, rootPanel: rootPanel ?? (() => panel.current), onAction: onAction, controlSize: controlSize, nested: true })] }, item.id);
|
|
252
|
+
};
|
|
253
|
+
const rows = [];
|
|
254
|
+
for (let index = 0; index < items.length; index++) {
|
|
255
|
+
const item = items[index];
|
|
256
|
+
if (item.type !== "radio") {
|
|
257
|
+
rows.push(renderItem(item));
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
const group = [item];
|
|
261
|
+
while (items[index + 1]?.type === "radio" && items[index + 1].group === item.group)
|
|
262
|
+
group.push(items[++index]);
|
|
263
|
+
rows.push(_jsxs("div", { role: "group", "aria-label": item.group, children: [_jsx("div", { className: "flood-menu__group-label", "aria-hidden": "true", children: item.group }), group.map(renderItem)] }, `radio-${item.id}`));
|
|
264
|
+
}
|
|
265
|
+
return _jsx("div", { ...props, ref: panel, id: id, popover: "manual", role: "menu", "aria-label": label, "aria-orientation": "vertical", tabIndex: -1, className: ["flood-overlay-panel", "flood-menu", nested && "flood-menu--submenu", className].filter(Boolean).join(" "), onKeyDown: event => {
|
|
266
|
+
if (event.target.closest('[role="menu"]') !== panel.current)
|
|
267
|
+
return;
|
|
268
|
+
onKeyDown?.(event);
|
|
269
|
+
if (event.defaultPrevented)
|
|
270
|
+
return;
|
|
271
|
+
const all = ownButtons(panel.current), index = all.indexOf(event.target);
|
|
272
|
+
if (event.key === "Tab") {
|
|
273
|
+
clearHover();
|
|
274
|
+
const source = current.current.rootAnchor();
|
|
275
|
+
if (source) {
|
|
276
|
+
const scope = source.closest("dialog[open]") ?? source.ownerDocument.body;
|
|
277
|
+
const stops = tabbables(scope).filter(node => !wholePanel()?.contains(node));
|
|
278
|
+
const at = stops.indexOf(source), target = event.shiftKey ? source : at >= 0 ? stops[at + 1] : undefined;
|
|
279
|
+
if (target) {
|
|
120
280
|
event.preventDefault();
|
|
121
|
-
|
|
122
|
-
all[next].scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
281
|
+
target.focus();
|
|
123
282
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
283
|
+
else
|
|
284
|
+
source.focus();
|
|
285
|
+
}
|
|
286
|
+
closeTree();
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (event.altKey || event.ctrlKey || event.metaKey || event.nativeEvent.isComposing)
|
|
290
|
+
return;
|
|
291
|
+
const rtl = panel.current && getComputedStyle(panel.current).direction === "rtl";
|
|
292
|
+
if (nested && event.key === (rtl ? "ArrowRight" : "ArrowLeft")) {
|
|
293
|
+
event.preventDefault();
|
|
294
|
+
event.stopPropagation();
|
|
295
|
+
requestOpen(false);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (event.key === (rtl ? "ArrowLeft" : "ArrowRight")) {
|
|
299
|
+
const item = items.find(item => item.id === all[index]?.dataset.menuId);
|
|
300
|
+
if (item?.type === "submenu") {
|
|
301
|
+
event.preventDefault();
|
|
302
|
+
if (!item.disabled)
|
|
303
|
+
showChild(item);
|
|
304
|
+
}
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
let next = event.key === "Home" ? 0 : event.key === "End" ? all.length - 1 : event.key === "ArrowDown" ? (index + 1) % all.length : event.key === "ArrowUp" ? (index - 1 + all.length) % all.length : -1;
|
|
308
|
+
if (["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) {
|
|
309
|
+
clearHover();
|
|
310
|
+
setOpenChild(undefined);
|
|
311
|
+
search.current = { text: "", time: 0 };
|
|
312
|
+
}
|
|
313
|
+
if (event.key.length === 1 && event.key !== " ") {
|
|
314
|
+
clearHover();
|
|
315
|
+
setOpenChild(undefined);
|
|
316
|
+
const now = Date.now();
|
|
317
|
+
search.current = { text: (now - search.current.time < 700 ? search.current.text : "") + event.key.toLocaleLowerCase(), time: now };
|
|
318
|
+
const text = Array.from(search.current.text).every(char => char === search.current.text[0]) ? search.current.text[0] : search.current.text;
|
|
319
|
+
const start = text.length === 1 ? index + 1 : Math.max(0, index);
|
|
320
|
+
next = Array.from({ length: all.length }, (_, offset) => (start + offset) % all.length).find(candidate => all[candidate].dataset.label?.toLocaleLowerCase().startsWith(text)) ?? -1;
|
|
321
|
+
event.preventDefault();
|
|
322
|
+
}
|
|
323
|
+
if (next >= 0 && all[next]) {
|
|
324
|
+
event.preventDefault();
|
|
325
|
+
all[next].focus({ preventScroll: true });
|
|
326
|
+
all[next].scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
327
|
+
}
|
|
328
|
+
}, children: open && _jsx(ScrollArea, { orientation: "vertical", role: "group", tabIndex: -1, className: "flood-menu__viewport", children: rows }) });
|
|
329
|
+
});
|
|
330
|
+
export const ActionMenu = /* @__PURE__ */ forwardRef(function ActionMenu({ label, trigger, items, onAction, open: controlled, defaultOpen, onOpenChange, disabled, side = "bottom", align = "start", controlSize = "sm", ...props }, forwardedRef) {
|
|
331
|
+
if (!label.trim())
|
|
332
|
+
throw new Error("Flood Menu requires a nonempty label");
|
|
333
|
+
validateMenuTree(items);
|
|
334
|
+
const [local, setLocal] = useState(Boolean(defaultOpen)), requestedOpen = controlled ?? local;
|
|
335
|
+
const open = !disabled && requestedOpen;
|
|
336
|
+
const anchor = useRef(null), panel = useRef(null), edge = useRef("first");
|
|
337
|
+
const pending = useRef(undefined);
|
|
338
|
+
const current = useRef({ controlled, onOpenChange });
|
|
339
|
+
current.current = { controlled, onOpenChange };
|
|
340
|
+
const request = (next) => {
|
|
341
|
+
if (pending.current === next)
|
|
342
|
+
return;
|
|
343
|
+
pending.current = next;
|
|
344
|
+
queueMicrotask(() => { pending.current = undefined; });
|
|
345
|
+
if (current.current.controlled === undefined)
|
|
346
|
+
setLocal(next);
|
|
347
|
+
current.current.onOpenChange?.(next);
|
|
348
|
+
};
|
|
349
|
+
useImperativeHandle(forwardedRef, () => panel.current, []);
|
|
350
|
+
useEffect(() => { if (disabled && requestedOpen)
|
|
351
|
+
request(false); }, [disabled, requestedOpen]);
|
|
352
|
+
const setAnchor = useCallback((node) => { anchor.current = node; }, []);
|
|
353
|
+
const triggerProps = { ref: setAnchor, type: "button", disabled, "aria-haspopup": "menu", "aria-expanded": open,
|
|
354
|
+
onClick: () => { edge.current = "first"; request(!open); },
|
|
355
|
+
onKeyDown: event => {
|
|
356
|
+
if (event.defaultPrevented || disabled || event.altKey || event.ctrlKey || event.metaKey || !["ArrowDown", "ArrowUp"].includes(event.key))
|
|
357
|
+
return;
|
|
358
|
+
event.preventDefault();
|
|
359
|
+
edge.current = event.key === "ArrowUp" ? "last" : "first";
|
|
360
|
+
if (open && panel.current?.matches(":popover-open"))
|
|
361
|
+
focusMenuEdge(panel.current, edge.current);
|
|
362
|
+
else
|
|
363
|
+
request(true);
|
|
364
|
+
} };
|
|
365
|
+
const generated = useId(), id = props.id ?? `flood-menu-${generated}`;
|
|
366
|
+
triggerProps["aria-controls"] = id;
|
|
367
|
+
return _jsxs(_Fragment, { children: [trigger(triggerProps), _jsx(MenuLayer, { ...props, id: id, ref: panel, label: label, items: items, open: open, requestOpen: request, closeTree: () => request(false), anchor: () => anchor.current, rootAnchor: () => anchor.current, onAction: onAction, edge: () => edge.current, side: side, align: align, controlSize: controlSize })] });
|
|
130
368
|
});
|
|
@@ -6,7 +6,7 @@ function uniqueValues(items, component) {
|
|
|
6
6
|
if (items.some(item => !item.value.trim()) || new Set(items.map(item => item.value)).size !== items.length)
|
|
7
7
|
throw new Error(`Flood ${component} items require unique nonempty values`);
|
|
8
8
|
}
|
|
9
|
-
export const NavigationMenu = forwardRef(function NavigationMenu({ items, label, currentValue, size = "md", className, ...props }, ref) {
|
|
9
|
+
export const NavigationMenu = /* @__PURE__ */ forwardRef(function NavigationMenu({ items, label, currentValue, size = "md", className, ...props }, ref) {
|
|
10
10
|
uniqueValues(items, "NavigationList");
|
|
11
11
|
if (!label.trim() || items.some(item => !item.label.trim() || !item.href.trim()))
|
|
12
12
|
throw new Error("Flood NavigationList requires a name and labelled links with destinations");
|
|
@@ -22,7 +22,7 @@ export const NavigationMenu = forwardRef(function NavigationMenu({ items, label,
|
|
|
22
22
|
const links = (entries) => entries.map(item => _jsx("li", { children: _jsxs("a", { ...item.linkProps, href: item.href, "aria-current": item.value === currentValue ? "page" : undefined, className: classes("flood-navigation-list__link", item.linkProps?.className), children: [item.icon != null && _jsx(IconSlot, { size: size, children: item.icon }), _jsxs("span", { className: "flood-navigation-list__text", children: [_jsx("span", { className: "flood-navigation-list__label", children: item.label }), item.description && _jsx("span", { className: "flood-navigation-list__description", children: item.description })] })] }) }, item.value));
|
|
23
23
|
return _jsx("nav", { ...props, ref: ref, "aria-label": label, className: classes("flood-navigation-list", className), "data-size": size, children: _jsx("ul", { role: "list", className: "flood-navigation-list__groups", children: groups.map((group, index) => group.label ? _jsxs("li", { className: "flood-navigation-list__group", children: [_jsx("p", { className: "flood-navigation-list__group-title", id: `${id}-${index}`, children: group.label }), _jsx("ul", { role: "list", "aria-labelledby": `${id}-${index}`, children: links(group.items) })] }, index) : links(group.items)) }) });
|
|
24
24
|
});
|
|
25
|
-
export const StepSequence = forwardRef(function StepSequence({ items, label, currentValue = items[0]?.value, orientation = "horizontal", onStepChange, statusLabels = { complete: "Completed", current: "Current step", upcoming: "Upcoming", error: "Needs attention" }, className, ...props }, ref) {
|
|
25
|
+
export const StepSequence = /* @__PURE__ */ forwardRef(function StepSequence({ items, label, currentValue = items[0]?.value, orientation = "horizontal", onStepChange, statusLabels = { complete: "Completed", current: "Current step", upcoming: "Upcoming", error: "Needs attention" }, className, ...props }, ref) {
|
|
26
26
|
uniqueValues(items, "Steps");
|
|
27
27
|
if (!label.trim() || items.some(item => !item.title.trim()))
|
|
28
28
|
throw new Error("Flood Steps requires a sequence name and step titles");
|
|
@@ -36,20 +36,20 @@ export const StepSequence = forwardRef(function StepSequence({ items, label, cur
|
|
|
36
36
|
return _jsx("li", { "data-state": state, "aria-current": current ? "step" : undefined, children: onStepChange ? _jsx("button", { type: "button", className: "flood-steps__content flood-steps__action", disabled: item.disabled, onClick: () => onStepChange(item.value), children: content }) : _jsx("div", { className: "flood-steps__content", children: content }) }, item.value);
|
|
37
37
|
}) });
|
|
38
38
|
});
|
|
39
|
-
export const ContentSection = forwardRef(function ContentSection({ title, description, actions, headingLevel = 2, headingSize = "section", children, id: explicitId, className, "aria-labelledby": labelledBy, ...props }, ref) {
|
|
39
|
+
export const ContentSection = /* @__PURE__ */ forwardRef(function ContentSection({ title, description, actions, headingLevel = 2, headingSize = "section", children, id: explicitId, className, "aria-labelledby": labelledBy, ...props }, ref) {
|
|
40
40
|
if (!title.trim() || ![2, 3, 4, 5, 6].includes(headingLevel))
|
|
41
41
|
throw new Error("Flood Section requires a nonempty title and headingLevel from 2 to 6");
|
|
42
42
|
const generated = useId(), headingId = `${explicitId ?? `flood-section-${generated}`}-title`;
|
|
43
43
|
return _jsxs("section", { ...props, ref: ref, id: explicitId, "aria-labelledby": labelledBy ?? headingId, className: classes("flood-section", className), children: [_jsxs("header", { className: "flood-section__header", children: [_jsxs("div", { className: "flood-section__heading", children: [_jsx(Heading, { id: headingId, level: headingLevel, size: headingSize, className: "flood-section__title", children: title }), description != null && _jsx("div", { className: "flood-section__description", children: description })] }), actions != null && _jsx("div", { className: "flood-section__actions", children: actions })] }), children != null && _jsx("div", { className: "flood-section__body", children: children })] });
|
|
44
44
|
});
|
|
45
|
-
export const SplitLayout = forwardRef(function SplitLayout({ main, aside, side = "end", ratio = "main", collapseAt = "md", gap = "6", className, style, ...props }, ref) {
|
|
45
|
+
export const SplitLayout = /* @__PURE__ */ forwardRef(function SplitLayout({ main, aside, side = "end", ratio = "main", collapseAt = "md", gap = "6", className, style, ...props }, ref) {
|
|
46
46
|
const primary = _jsx("div", { className: "flood-split__main", children: main }, "main"), secondary = _jsx("div", { className: "flood-split__aside", children: aside }, "aside");
|
|
47
47
|
return _jsx("div", { ...props, ref: ref, className: classes("flood-split", className), style: { "--flood-layout-gap": `var(--flood-sys-space-${gap})`, ...style }, children: _jsx("div", { className: "flood-split__layout", "data-side": side, "data-ratio": ratio, "data-collapse": collapseAt, children: side === "start" ? [secondary, primary] : [primary, secondary] }) });
|
|
48
48
|
});
|
|
49
|
-
export const CenterLayout = forwardRef(function CenterLayout({ inline = false, maxWidth, minHeight, className, style, children, ...props }, ref) {
|
|
49
|
+
export const CenterLayout = /* @__PURE__ */ forwardRef(function CenterLayout({ inline = false, maxWidth, minHeight, className, style, children, ...props }, ref) {
|
|
50
50
|
return _jsx("div", { ...props, ref: ref, className: classes("flood-center", className), "data-inline": inline || undefined, style: { minBlockSize: minHeight, "--flood-center-max": maxWidth, ...style }, children: _jsx("div", { className: "flood-center__content", children: children }) });
|
|
51
51
|
});
|
|
52
|
-
export const SkipNavigation = forwardRef(function SkipNavigation({ children = "Skip to content", className, onClick, href, ...props }, ref) {
|
|
52
|
+
export const SkipNavigation = /* @__PURE__ */ forwardRef(function SkipNavigation({ children = "Skip to content", className, onClick, href, ...props }, ref) {
|
|
53
53
|
if (!href.startsWith("#") || href.length < 2)
|
|
54
54
|
throw new Error("Flood SkipLink requires a nonempty same-page fragment href");
|
|
55
55
|
return _jsx("a", { ...props, ref: ref, href: href, className: classes("flood-skip-link", className), onClick: event => {
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { NavigationTreeProps } from "./index.js";
|
|
2
|
+
/** Ordinary website navigation uses disclosure lists, not application tree selection semantics. */
|
|
3
|
+
export declare const HierarchicalNavigation: import("react").ForwardRefExoticComponent<NavigationTreeProps & import("react").RefAttributes<HTMLElement>>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useEffect, useId, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
|
|
3
|
+
import { IconButton, IconSlot, Link } from "./index.js";
|
|
4
|
+
const useBrowserLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
5
|
+
const branch = (item) => item.children !== undefined || item.content !== undefined;
|
|
6
|
+
/** Ordinary website navigation uses disclosure lists, not application tree selection semantics. */
|
|
7
|
+
export const HierarchicalNavigation = /* @__PURE__ */ forwardRef(function HierarchicalNavigation({ label, items, currentValue, expandedValues, defaultExpandedValues = [], onExpandedValuesChange, expandLabel = (item, expanded) => `${expanded ? "Collapse" : "Expand"} ${item.label}`, size = "md", id: explicitId, className, tabIndex = -1, ...props }, forwardedRef) {
|
|
8
|
+
if (!label.trim())
|
|
9
|
+
throw new Error("Flood NavigationTree requires a nonempty label");
|
|
10
|
+
const generated = useId(), id = explicitId ?? `flood-navigation-tree-${generated}`;
|
|
11
|
+
const root = useRef(null);
|
|
12
|
+
useImperativeHandle(forwardedRef, () => root.current, []);
|
|
13
|
+
const [local, setLocal] = useState(() => [...new Set(defaultExpandedValues)]);
|
|
14
|
+
const expanded = new Set(expandedValues ?? local);
|
|
15
|
+
const known = new Set(), visible = new Set();
|
|
16
|
+
const parents = new Map();
|
|
17
|
+
const pending = [{ items, shown: true, ancestors: new Set() }];
|
|
18
|
+
while (pending.length) {
|
|
19
|
+
const level = pending.pop();
|
|
20
|
+
if (level.ancestors.has(level.items))
|
|
21
|
+
throw new Error("Flood NavigationTree does not accept recursive item arrays");
|
|
22
|
+
const ancestors = new Set(level.ancestors);
|
|
23
|
+
ancestors.add(level.items);
|
|
24
|
+
for (const item of level.items) {
|
|
25
|
+
if (!item.value.trim() || !item.label.trim() || known.has(item.value))
|
|
26
|
+
throw new Error("Flood NavigationTree requires globally unique nonempty values and labels");
|
|
27
|
+
known.add(item.value);
|
|
28
|
+
parents.set(item.value, level.parent);
|
|
29
|
+
if (level.shown)
|
|
30
|
+
visible.add(item.value);
|
|
31
|
+
if (item.children)
|
|
32
|
+
pending.push({ items: item.children, parent: item.value, shown: level.shown && expanded.has(item.value), ancestors });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const latest = useRef({ visible, parents });
|
|
36
|
+
latest.current = { visible, parents };
|
|
37
|
+
const destinations = useRef(new Map());
|
|
38
|
+
const refs = useRef(new Map());
|
|
39
|
+
const refAncestors = useRef(new Map());
|
|
40
|
+
const repair = (removed, ancestors) => {
|
|
41
|
+
const active = removed.ownerDocument.activeElement;
|
|
42
|
+
if (!active || !removed.contains(active))
|
|
43
|
+
return;
|
|
44
|
+
for (const value of ancestors) {
|
|
45
|
+
const target = destinations.current.get(`disclosure:${value}`) ?? destinations.current.get(`link:${value}`);
|
|
46
|
+
if (latest.current.visible.has(value) && target?.isConnected && !target.matches(":disabled")) {
|
|
47
|
+
target.focus({ preventScroll: true });
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
root.current?.focus({ preventScroll: true });
|
|
52
|
+
};
|
|
53
|
+
const bind = (key, ancestors) => {
|
|
54
|
+
refAncestors.current.set(key, ancestors);
|
|
55
|
+
if (!refs.current.has(key)) {
|
|
56
|
+
let previous = null;
|
|
57
|
+
refs.current.set(key, node => {
|
|
58
|
+
if (!node && previous)
|
|
59
|
+
repair(previous, refAncestors.current.get(key) ?? []);
|
|
60
|
+
previous = node;
|
|
61
|
+
if (node)
|
|
62
|
+
destinations.current.set(key, node);
|
|
63
|
+
else
|
|
64
|
+
destinations.current.delete(key);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return refs.current.get(key);
|
|
68
|
+
};
|
|
69
|
+
useBrowserLayoutEffect(() => {
|
|
70
|
+
for (const key of refs.current.keys())
|
|
71
|
+
if (!known.has(key.slice(key.indexOf(":") + 1))) {
|
|
72
|
+
refs.current.delete(key);
|
|
73
|
+
refAncestors.current.delete(key);
|
|
74
|
+
}
|
|
75
|
+
}, [items]);
|
|
76
|
+
const toggle = (value) => {
|
|
77
|
+
const next = new Set(expandedValues ?? local);
|
|
78
|
+
if (next.has(value))
|
|
79
|
+
next.delete(value);
|
|
80
|
+
else
|
|
81
|
+
next.add(value);
|
|
82
|
+
const values = [...next];
|
|
83
|
+
if (expandedValues === undefined)
|
|
84
|
+
setLocal(values);
|
|
85
|
+
onExpandedValuesChange?.(values);
|
|
86
|
+
};
|
|
87
|
+
const render = (entries, ancestors = []) => entries.map(item => {
|
|
88
|
+
const open = expanded.has(item.value), expandable = branch(item), current = item.value === currentValue;
|
|
89
|
+
const contentId = `${id}-branch-${encodeURIComponent(item.value)}`;
|
|
90
|
+
const disclosureLabel = expandable ? expandLabel(item, open) : "";
|
|
91
|
+
if (expandable && !disclosureLabel.trim())
|
|
92
|
+
throw new Error("Flood NavigationTree disclosure labels must be nonempty");
|
|
93
|
+
const body = _jsxs(_Fragment, { children: [item.icon != null && _jsx(IconSlot, { size: size, children: item.icon }), _jsx("span", { className: "flood-navigation-tree__label", children: item.label })] });
|
|
94
|
+
return _jsxs("li", { children: [_jsxs("div", { className: "flood-navigation-tree__row", "data-current": current || undefined, children: [item.href !== undefined && !item.disabled ? _jsx(Link, { ...item.linkProps, href: item.href, ref: bind(`link:${item.value}`, ancestors), variant: "ghost", size: size, "aria-current": current ? "page" : undefined, className: ["flood-navigation-tree__link", item.linkProps?.className].filter(Boolean).join(" "), children: body })
|
|
95
|
+
: _jsx("span", { className: "flood-navigation-tree__text", "aria-disabled": item.disabled || undefined, "aria-current": current ? "page" : undefined, children: body }), expandable && _jsx(IconButton, { ref: bind(`disclosure:${item.value}`, ancestors), variant: "ghost", size: size, label: disclosureLabel, className: "flood-navigation-tree__disclosure", disabled: item.disabled, "aria-expanded": open, "aria-controls": open ? contentId : undefined, onClick: () => toggle(item.value), children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "m9 5 7 7-7 7" }) }) })] }), expandable && open && _jsxs("div", { id: contentId, ref: bind(`group:${item.value}`, [item.value, ...ancestors]), className: "flood-navigation-tree__branch", children: [item.children && item.children.length > 0 && _jsx("ul", { children: render(item.children, [item.value, ...ancestors]) }), item.content != null && _jsx("div", { className: "flood-navigation-tree__content", children: item.content })] })] }, item.value);
|
|
96
|
+
});
|
|
97
|
+
return _jsx("nav", { ...props, ref: root, id: id, "aria-label": label, tabIndex: tabIndex, "data-size": size, className: ["flood-navigation-tree", className].filter(Boolean).join(" "), children: _jsx("ul", { children: render(items) }) });
|
|
98
|
+
});
|