@arun-dev/headless 4.14.0 → 4.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-ETNICT5M.js +8 -0
- package/dist/combobox/index.cjs +3 -1
- package/dist/combobox/index.js +7 -7
- package/dist/menu/index.cjs +509 -33
- package/dist/menu/index.d.cts +216 -20
- package/dist/menu/index.d.ts +216 -20
- package/dist/menu/index.js +509 -37
- package/package.json +1 -1
package/dist/menu/index.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
anchoredPopupStyle
|
|
5
|
-
} from "../chunk-HCOCPQXS.js";
|
|
2
|
+
fold
|
|
3
|
+
} from "../chunk-ETNICT5M.js";
|
|
6
4
|
import {
|
|
7
5
|
byDocumentOrder,
|
|
8
6
|
rovingIndex
|
|
9
7
|
} from "../chunk-2S4XG4VD.js";
|
|
8
|
+
import {
|
|
9
|
+
anchorNameFor,
|
|
10
|
+
anchoredDataAttributes,
|
|
11
|
+
anchoredPopupStyle
|
|
12
|
+
} from "../chunk-HCOCPQXS.js";
|
|
10
13
|
import {
|
|
11
14
|
useControlled
|
|
12
15
|
} from "../chunk-UEIB3I3T.js";
|
|
@@ -18,9 +21,17 @@ import {
|
|
|
18
21
|
// src/menu/index.parts.ts
|
|
19
22
|
var index_parts_exports = {};
|
|
20
23
|
__export(index_parts_exports, {
|
|
24
|
+
CheckboxItem: () => MenuCheckboxItem,
|
|
25
|
+
Group: () => MenuGroup,
|
|
26
|
+
GroupLabel: () => MenuGroupLabel,
|
|
21
27
|
Item: () => MenuItem,
|
|
28
|
+
ItemIndicator: () => MenuItemIndicator,
|
|
22
29
|
Popup: () => MenuPopup,
|
|
30
|
+
RadioGroup: () => MenuRadioGroup,
|
|
31
|
+
RadioItem: () => MenuRadioItem,
|
|
23
32
|
Root: () => MenuRoot,
|
|
33
|
+
SubmenuRoot: () => MenuSubmenuRoot,
|
|
34
|
+
SubmenuTrigger: () => MenuSubmenuTrigger,
|
|
24
35
|
Trigger: () => MenuTrigger
|
|
25
36
|
});
|
|
26
37
|
|
|
@@ -40,17 +51,20 @@ function useMenuRootContext(part) {
|
|
|
40
51
|
|
|
41
52
|
// src/menu/MenuRoot.tsx
|
|
42
53
|
import { jsx } from "react/jsx-runtime";
|
|
43
|
-
function MenuRoot({
|
|
54
|
+
function MenuRoot({ children, ...props }) {
|
|
55
|
+
const context = useMenuRootValue("Menu.Root", props, null);
|
|
56
|
+
return /* @__PURE__ */ jsx(MenuRootContext.Provider, { value: context, children });
|
|
57
|
+
}
|
|
58
|
+
function useMenuRootValue(name, {
|
|
44
59
|
open: openProp,
|
|
45
60
|
defaultOpen,
|
|
46
61
|
onOpenChange,
|
|
47
|
-
focusableWhenDisabled = false
|
|
48
|
-
|
|
49
|
-
}) {
|
|
62
|
+
focusableWhenDisabled = false
|
|
63
|
+
}, parent) {
|
|
50
64
|
const [open, setOpenState] = useControlled({
|
|
51
65
|
controlled: openProp,
|
|
52
66
|
default: defaultOpen ?? false,
|
|
53
|
-
name
|
|
67
|
+
name,
|
|
54
68
|
state: "open"
|
|
55
69
|
});
|
|
56
70
|
const setOpen = useCallback(
|
|
@@ -61,6 +75,10 @@ function MenuRoot({
|
|
|
61
75
|
},
|
|
62
76
|
[open, setOpenState, onOpenChange]
|
|
63
77
|
);
|
|
78
|
+
const closeAll = useCallback(() => {
|
|
79
|
+
setOpen(false);
|
|
80
|
+
parent?.closeAll();
|
|
81
|
+
}, [setOpen, parent]);
|
|
64
82
|
const [items, setItems] = useState([]);
|
|
65
83
|
const register = useCallback((entry) => {
|
|
66
84
|
setItems((current) => [...current, entry].sort(byDocumentOrder));
|
|
@@ -73,10 +91,14 @@ function MenuRoot({
|
|
|
73
91
|
const id = useId();
|
|
74
92
|
const triggerRef = useRef(null);
|
|
75
93
|
const focusOnOpenRef = useRef("first");
|
|
76
|
-
const
|
|
94
|
+
const popupRef = useRef(null);
|
|
95
|
+
const openSubmenuRef = useRef(null);
|
|
96
|
+
const graceRef = useRef(null);
|
|
97
|
+
return useMemo(
|
|
77
98
|
() => ({
|
|
78
99
|
open,
|
|
79
100
|
setOpen,
|
|
101
|
+
closeAll,
|
|
80
102
|
navigableItems,
|
|
81
103
|
focusableWhenDisabled,
|
|
82
104
|
register,
|
|
@@ -84,11 +106,14 @@ function MenuRoot({
|
|
|
84
106
|
triggerId: `${id}-trigger`,
|
|
85
107
|
popupId: `${id}-popup`,
|
|
86
108
|
anchorName: anchorNameFor(id),
|
|
87
|
-
triggerRef
|
|
109
|
+
triggerRef,
|
|
110
|
+
parent,
|
|
111
|
+
popupRef,
|
|
112
|
+
openSubmenuRef,
|
|
113
|
+
graceRef
|
|
88
114
|
}),
|
|
89
|
-
[open, setOpen, navigableItems, focusableWhenDisabled, register, id]
|
|
115
|
+
[open, setOpen, closeAll, navigableItems, focusableWhenDisabled, register, id, parent]
|
|
90
116
|
);
|
|
91
|
-
return /* @__PURE__ */ jsx(MenuRootContext.Provider, { value: context, children });
|
|
92
117
|
}
|
|
93
118
|
|
|
94
119
|
// src/menu/menuDataAttributes.ts
|
|
@@ -98,9 +123,6 @@ function menuDataAttributes({ open }) {
|
|
|
98
123
|
"data-closed": open ? void 0 : ""
|
|
99
124
|
};
|
|
100
125
|
}
|
|
101
|
-
function menuItemDataAttributes(disabled) {
|
|
102
|
-
return { "data-disabled": disabled ? "" : void 0 };
|
|
103
|
-
}
|
|
104
126
|
|
|
105
127
|
// src/menu/MenuTrigger.tsx
|
|
106
128
|
function MenuTrigger({ className, children, render, ...rest }) {
|
|
@@ -135,9 +157,53 @@ function MenuTrigger({ className, children, render, ...rest }) {
|
|
|
135
157
|
}
|
|
136
158
|
|
|
137
159
|
// src/menu/MenuPopup.tsx
|
|
138
|
-
import { useEffect, useReducer, useRef as
|
|
160
|
+
import { useEffect as useEffect2, useReducer, useRef as useRef3 } from "react";
|
|
161
|
+
|
|
162
|
+
// src/core/typeahead.ts
|
|
163
|
+
import { Children, isValidElement, useCallback as useCallback2, useEffect, useRef as useRef2 } from "react";
|
|
164
|
+
var TYPEAHEAD_TIMEOUT = 500;
|
|
165
|
+
function textOf(node) {
|
|
166
|
+
let text = "";
|
|
167
|
+
Children.forEach(node, (child) => {
|
|
168
|
+
if (typeof child === "string" || typeof child === "number") text += child;
|
|
169
|
+
else if (isValidElement(child)) text += textOf(child.props.children);
|
|
170
|
+
});
|
|
171
|
+
return text;
|
|
172
|
+
}
|
|
173
|
+
function typeaheadIndex(labels, current, search) {
|
|
174
|
+
const count = labels.length;
|
|
175
|
+
if (count === 0 || search === "") return null;
|
|
176
|
+
const [first = "", ...others] = [...search];
|
|
177
|
+
const repeated = others.every((char) => char === first);
|
|
178
|
+
const needle = fold(repeated ? first : search);
|
|
179
|
+
const start = current < 0 ? 0 : repeated ? current + 1 : current;
|
|
180
|
+
for (let step = 0; step < count; step += 1) {
|
|
181
|
+
const index = (start + step) % count;
|
|
182
|
+
if (fold(labels[index] ?? "").startsWith(needle)) return index;
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
function useTypeahead() {
|
|
187
|
+
const searchRef = useRef2("");
|
|
188
|
+
const timerRef = useRef2(void 0);
|
|
189
|
+
useEffect(() => () => clearTimeout(timerRef.current), []);
|
|
190
|
+
return useCallback2((event) => {
|
|
191
|
+
const { key } = event;
|
|
192
|
+
if (event.ctrlKey || event.metaKey || event.altKey) return null;
|
|
193
|
+
if ([...key].length !== 1) return null;
|
|
194
|
+
if (key === " " && searchRef.current === "") return null;
|
|
195
|
+
searchRef.current += key;
|
|
196
|
+
clearTimeout(timerRef.current);
|
|
197
|
+
timerRef.current = setTimeout(() => {
|
|
198
|
+
searchRef.current = "";
|
|
199
|
+
}, TYPEAHEAD_TIMEOUT);
|
|
200
|
+
return searchRef.current;
|
|
201
|
+
}, []);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// src/menu/MenuPopup.tsx
|
|
139
205
|
function MenuPopup({
|
|
140
|
-
side
|
|
206
|
+
side: sideProp,
|
|
141
207
|
align = "start",
|
|
142
208
|
className,
|
|
143
209
|
children,
|
|
@@ -152,12 +218,16 @@ function MenuPopup({
|
|
|
152
218
|
triggerId,
|
|
153
219
|
popupId,
|
|
154
220
|
anchorName,
|
|
155
|
-
triggerRef
|
|
221
|
+
triggerRef,
|
|
222
|
+
parent,
|
|
223
|
+
popupRef: elementRef,
|
|
224
|
+
openSubmenuRef
|
|
156
225
|
} = useMenuRootContext("Popup");
|
|
157
|
-
const
|
|
158
|
-
const focusPendingRef =
|
|
226
|
+
const side = sideProp ?? (parent ? "right" : "bottom");
|
|
227
|
+
const focusPendingRef = useRef3(false);
|
|
159
228
|
const [, resync] = useReducer((n) => n + 1, 0);
|
|
160
|
-
|
|
229
|
+
const typeahead = useTypeahead();
|
|
230
|
+
useEffect2(() => {
|
|
161
231
|
const popup = elementRef.current;
|
|
162
232
|
if (!popup || typeof popup.showPopover !== "function") return;
|
|
163
233
|
const shown = popup.matches(":popover-open");
|
|
@@ -171,7 +241,8 @@ function MenuPopup({
|
|
|
171
241
|
}
|
|
172
242
|
if (focusPendingRef.current && navigableItems.length > 0) {
|
|
173
243
|
focusPendingRef.current = false;
|
|
174
|
-
const
|
|
244
|
+
const focus = focusOnOpenRef.current;
|
|
245
|
+
const target = focus === "last" ? navigableItems.at(-1) : focus === "first" ? navigableItems[0] : null;
|
|
175
246
|
target?.ref.current?.focus();
|
|
176
247
|
}
|
|
177
248
|
});
|
|
@@ -196,15 +267,35 @@ function MenuPopup({
|
|
|
196
267
|
}
|
|
197
268
|
const current = navigableItems.findIndex((i) => i.ref.current === event.target);
|
|
198
269
|
if (current < 0 && event.target !== event.currentTarget) return;
|
|
199
|
-
const
|
|
270
|
+
const rtl = getComputedStyle(event.currentTarget).direction === "rtl";
|
|
271
|
+
if (parent && event.key === (rtl ? "ArrowRight" : "ArrowLeft")) {
|
|
272
|
+
event.preventDefault();
|
|
273
|
+
setOpen(false);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const search = typeahead(event);
|
|
277
|
+
const next = search === null ? rovingIndex(event.key, {
|
|
200
278
|
count: navigableItems.length,
|
|
201
279
|
current,
|
|
202
280
|
orientation: "vertical",
|
|
203
281
|
rtl: false
|
|
204
|
-
})
|
|
282
|
+
}) : typeaheadIndex(
|
|
283
|
+
navigableItems.map((i) => i.textValue),
|
|
284
|
+
current,
|
|
285
|
+
search
|
|
286
|
+
);
|
|
287
|
+
if (search !== null) event.preventDefault();
|
|
205
288
|
if (next === null) return;
|
|
206
289
|
event.preventDefault();
|
|
207
|
-
navigableItems[next]?.ref.current
|
|
290
|
+
const target = navigableItems[next]?.ref.current;
|
|
291
|
+
const submenu = openSubmenuRef.current;
|
|
292
|
+
if (submenu && submenu.triggerRef.current !== target) submenu.close();
|
|
293
|
+
target?.focus();
|
|
294
|
+
},
|
|
295
|
+
onBeforeToggle(event) {
|
|
296
|
+
if (event.target !== event.currentTarget || !parent) return;
|
|
297
|
+
if (event.nativeEvent.newState !== "closed") return;
|
|
298
|
+
if (event.currentTarget.contains(document.activeElement)) triggerRef.current?.focus();
|
|
208
299
|
},
|
|
209
300
|
onToggle(event) {
|
|
210
301
|
if (event.target !== event.currentTarget) return;
|
|
@@ -217,34 +308,415 @@ function MenuPopup({
|
|
|
217
308
|
});
|
|
218
309
|
}
|
|
219
310
|
|
|
311
|
+
// src/menu/useMenuItem.ts
|
|
312
|
+
import { createContext as createContext2, useContext as useContext2, useLayoutEffect, useRef as useRef4 } from "react";
|
|
313
|
+
|
|
314
|
+
// src/core/pointerIntent.ts
|
|
315
|
+
function graceArea(exit, popup) {
|
|
316
|
+
const toRight = popup.left >= exit.x;
|
|
317
|
+
const edge = toRight ? popup.left : popup.right;
|
|
318
|
+
const back = toRight ? -5 : 5;
|
|
319
|
+
return [
|
|
320
|
+
{ x: exit.x + back, y: exit.y - 5 },
|
|
321
|
+
{ x: edge, y: popup.top },
|
|
322
|
+
{ x: edge, y: popup.bottom },
|
|
323
|
+
{ x: exit.x + back, y: exit.y + 5 }
|
|
324
|
+
];
|
|
325
|
+
}
|
|
326
|
+
function isPointInPolygon({ x, y }, polygon) {
|
|
327
|
+
let inside = false;
|
|
328
|
+
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i, i += 1) {
|
|
329
|
+
const a = polygon[i];
|
|
330
|
+
const b = polygon[j];
|
|
331
|
+
if (!a || !b) continue;
|
|
332
|
+
if (a.y > y !== b.y > y && x < (b.x - a.x) * (y - a.y) / (b.y - a.y) + a.x) {
|
|
333
|
+
inside = !inside;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return inside;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// src/menu/useMenuItem.ts
|
|
340
|
+
function useMenuItem(part, { disabled, textValue, children }, menu) {
|
|
341
|
+
const own = useMenuRootContext(part);
|
|
342
|
+
const { closeAll, focusableWhenDisabled, register, openSubmenuRef, graceRef } = menu ?? own;
|
|
343
|
+
const ref = useRef4(null);
|
|
344
|
+
const text = textValue ?? textOf(children);
|
|
345
|
+
useLayoutEffect(() => register({ disabled, textValue: text, ref }), [register, disabled, text]);
|
|
346
|
+
return {
|
|
347
|
+
closeAll,
|
|
348
|
+
itemProps: {
|
|
349
|
+
type: "button",
|
|
350
|
+
tabIndex: -1,
|
|
351
|
+
disabled: disabled && !focusableWhenDisabled || void 0,
|
|
352
|
+
"aria-disabled": disabled && focusableWhenDisabled || void 0,
|
|
353
|
+
"data-disabled": disabled ? "" : void 0,
|
|
354
|
+
ref,
|
|
355
|
+
onPointerMove(event) {
|
|
356
|
+
if (event.pointerType !== "mouse" || !openSubmenuRef.current) return;
|
|
357
|
+
if (isInGrace(graceRef.current, event)) return;
|
|
358
|
+
openSubmenuRef.current.close();
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
function isInGrace(grace, event) {
|
|
364
|
+
if (!grace || performance.now() > grace.until) return false;
|
|
365
|
+
return isPointInPolygon({ x: event.clientX, y: event.clientY }, grace.area);
|
|
366
|
+
}
|
|
367
|
+
var MenuItemIndicatorContext = createContext2(null);
|
|
368
|
+
function useMenuItemIndicatorContext() {
|
|
369
|
+
const context = useContext2(MenuItemIndicatorContext);
|
|
370
|
+
if (context === null) {
|
|
371
|
+
throw new Error(
|
|
372
|
+
"<Menu.ItemIndicator> must be rendered inside <Menu.CheckboxItem> or <Menu.RadioItem>."
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
return context;
|
|
376
|
+
}
|
|
377
|
+
function menuCheckableDataAttributes({ checked, disabled }) {
|
|
378
|
+
return {
|
|
379
|
+
"data-checked": checked ? "" : void 0,
|
|
380
|
+
"data-unchecked": checked ? void 0 : "",
|
|
381
|
+
"data-disabled": disabled ? "" : void 0
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
220
385
|
// src/menu/MenuItem.tsx
|
|
221
|
-
import { useLayoutEffect, useRef as useRef3 } from "react";
|
|
222
386
|
function MenuItem({
|
|
223
387
|
disabled = false,
|
|
388
|
+
textValue,
|
|
224
389
|
onClick,
|
|
225
390
|
className,
|
|
226
391
|
children,
|
|
227
392
|
render,
|
|
228
393
|
...rest
|
|
229
394
|
}) {
|
|
230
|
-
const {
|
|
231
|
-
const elementRef = useRef3(null);
|
|
232
|
-
useLayoutEffect(() => register({ disabled, ref: elementRef }), [register, disabled]);
|
|
395
|
+
const { closeAll, itemProps } = useMenuItem("Item", { disabled, textValue, children });
|
|
233
396
|
return useRender({
|
|
234
397
|
render,
|
|
235
398
|
defaultTagName: "button",
|
|
236
399
|
props: {
|
|
237
|
-
|
|
400
|
+
...itemProps,
|
|
238
401
|
role: "menuitem",
|
|
239
|
-
tabIndex: -1,
|
|
240
|
-
disabled: disabled && !focusableWhenDisabled || void 0,
|
|
241
|
-
"aria-disabled": disabled && focusableWhenDisabled || void 0,
|
|
242
|
-
...menuItemDataAttributes(disabled),
|
|
243
402
|
className,
|
|
244
403
|
children,
|
|
245
|
-
ref: elementRef,
|
|
246
404
|
onClick() {
|
|
247
|
-
if (!disabled)
|
|
405
|
+
if (!disabled) closeAll();
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
consumerProps: disabled ? rest : { ...rest, onClick }
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// src/menu/MenuCheckboxItem.tsx
|
|
413
|
+
import { useMemo as useMemo2 } from "react";
|
|
414
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
415
|
+
function MenuCheckboxItem({
|
|
416
|
+
checked: checkedProp,
|
|
417
|
+
defaultChecked,
|
|
418
|
+
onCheckedChange,
|
|
419
|
+
closeOnClick = false,
|
|
420
|
+
disabled = false,
|
|
421
|
+
textValue,
|
|
422
|
+
onClick,
|
|
423
|
+
className,
|
|
424
|
+
children,
|
|
425
|
+
render,
|
|
426
|
+
...rest
|
|
427
|
+
}) {
|
|
428
|
+
const { closeAll, itemProps } = useMenuItem("CheckboxItem", { disabled, textValue, children });
|
|
429
|
+
const [checked, setChecked] = useControlled({
|
|
430
|
+
controlled: checkedProp,
|
|
431
|
+
default: defaultChecked ?? false,
|
|
432
|
+
name: "Menu.CheckboxItem",
|
|
433
|
+
state: "checked"
|
|
434
|
+
});
|
|
435
|
+
const state = useMemo2(() => ({ checked, disabled }), [checked, disabled]);
|
|
436
|
+
const element = useRender({
|
|
437
|
+
render,
|
|
438
|
+
defaultTagName: "button",
|
|
439
|
+
props: {
|
|
440
|
+
...itemProps,
|
|
441
|
+
role: "menuitemcheckbox",
|
|
442
|
+
"aria-checked": checked,
|
|
443
|
+
...menuCheckableDataAttributes(state),
|
|
444
|
+
className,
|
|
445
|
+
children,
|
|
446
|
+
onClick() {
|
|
447
|
+
if (disabled) return;
|
|
448
|
+
setChecked(!checked);
|
|
449
|
+
onCheckedChange?.(!checked);
|
|
450
|
+
if (closeOnClick) closeAll();
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
consumerProps: disabled ? rest : { ...rest, onClick }
|
|
454
|
+
});
|
|
455
|
+
return /* @__PURE__ */ jsx2(MenuItemIndicatorContext.Provider, { value: state, children: element });
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// src/menu/MenuRadioGroup.tsx
|
|
459
|
+
import { createContext as createContext4, useCallback as useCallback4, useContext as useContext4, useMemo as useMemo4 } from "react";
|
|
460
|
+
|
|
461
|
+
// src/menu/MenuGroup.tsx
|
|
462
|
+
import { createContext as createContext3, useCallback as useCallback3, useContext as useContext3, useId as useId2, useMemo as useMemo3, useState as useState2 } from "react";
|
|
463
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
464
|
+
var MenuGroupContext = createContext3(null);
|
|
465
|
+
function useMenuGroupContext() {
|
|
466
|
+
return useContext3(MenuGroupContext);
|
|
467
|
+
}
|
|
468
|
+
function useMenuGroupElement(part, {
|
|
469
|
+
render,
|
|
470
|
+
props,
|
|
471
|
+
consumerProps
|
|
472
|
+
}) {
|
|
473
|
+
useMenuRootContext(part);
|
|
474
|
+
const labelId = `${useId2()}-label`;
|
|
475
|
+
const [hasLabel, setHasLabel] = useState2(false);
|
|
476
|
+
const registerLabel = useCallback3(() => {
|
|
477
|
+
setHasLabel(true);
|
|
478
|
+
return () => setHasLabel(false);
|
|
479
|
+
}, []);
|
|
480
|
+
const context = useMemo3(() => ({ labelId, registerLabel }), [labelId, registerLabel]);
|
|
481
|
+
const element = useRender({
|
|
482
|
+
render,
|
|
483
|
+
defaultTagName: "div",
|
|
484
|
+
props: { role: "group", "aria-labelledby": hasLabel ? labelId : void 0, ...props },
|
|
485
|
+
consumerProps
|
|
486
|
+
});
|
|
487
|
+
return /* @__PURE__ */ jsx3(MenuGroupContext.Provider, { value: context, children: element });
|
|
488
|
+
}
|
|
489
|
+
function MenuGroup({ className, children, render, ...rest }) {
|
|
490
|
+
return useMenuGroupElement("Group", {
|
|
491
|
+
render,
|
|
492
|
+
props: { className, children },
|
|
493
|
+
consumerProps: rest
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// src/menu/MenuRadioGroup.tsx
|
|
498
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
499
|
+
var MenuRadioGroupContext = createContext4(null);
|
|
500
|
+
function useMenuRadioGroupContext() {
|
|
501
|
+
const context = useContext4(MenuRadioGroupContext);
|
|
502
|
+
if (context === null) {
|
|
503
|
+
throw new Error("<Menu.RadioItem> must be rendered inside <Menu.RadioGroup>.");
|
|
504
|
+
}
|
|
505
|
+
return context;
|
|
506
|
+
}
|
|
507
|
+
function MenuRadioGroup({
|
|
508
|
+
value: valueProp,
|
|
509
|
+
defaultValue,
|
|
510
|
+
onValueChange,
|
|
511
|
+
disabled = false,
|
|
512
|
+
className,
|
|
513
|
+
children,
|
|
514
|
+
render,
|
|
515
|
+
...rest
|
|
516
|
+
}) {
|
|
517
|
+
const [value, setValue] = useControlled({
|
|
518
|
+
controlled: valueProp,
|
|
519
|
+
default: defaultValue ?? null,
|
|
520
|
+
name: "Menu.RadioGroup",
|
|
521
|
+
state: "value"
|
|
522
|
+
});
|
|
523
|
+
const select = useCallback4(
|
|
524
|
+
(next) => {
|
|
525
|
+
if (next === value) return;
|
|
526
|
+
setValue(next);
|
|
527
|
+
onValueChange?.(next);
|
|
528
|
+
},
|
|
529
|
+
[value, setValue, onValueChange]
|
|
530
|
+
);
|
|
531
|
+
const context = useMemo4(() => ({ value, disabled, select }), [value, disabled, select]);
|
|
532
|
+
const element = useMenuGroupElement("RadioGroup", {
|
|
533
|
+
render,
|
|
534
|
+
props: { "data-disabled": disabled ? "" : void 0, className, children },
|
|
535
|
+
consumerProps: rest
|
|
536
|
+
});
|
|
537
|
+
return /* @__PURE__ */ jsx4(MenuRadioGroupContext.Provider, { value: context, children: element });
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// src/menu/MenuRadioItem.tsx
|
|
541
|
+
import { useMemo as useMemo5 } from "react";
|
|
542
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
543
|
+
function MenuRadioItem({
|
|
544
|
+
value,
|
|
545
|
+
closeOnClick = false,
|
|
546
|
+
disabled: disabledProp = false,
|
|
547
|
+
textValue,
|
|
548
|
+
onClick,
|
|
549
|
+
className,
|
|
550
|
+
children,
|
|
551
|
+
render,
|
|
552
|
+
...rest
|
|
553
|
+
}) {
|
|
554
|
+
const group = useMenuRadioGroupContext();
|
|
555
|
+
const disabled = disabledProp || group.disabled;
|
|
556
|
+
const { closeAll, itemProps } = useMenuItem("RadioItem", { disabled, textValue, children });
|
|
557
|
+
const checked = group.value === value;
|
|
558
|
+
const state = useMemo5(() => ({ checked, disabled }), [checked, disabled]);
|
|
559
|
+
const element = useRender({
|
|
560
|
+
render,
|
|
561
|
+
defaultTagName: "button",
|
|
562
|
+
props: {
|
|
563
|
+
...itemProps,
|
|
564
|
+
role: "menuitemradio",
|
|
565
|
+
"aria-checked": checked,
|
|
566
|
+
...menuCheckableDataAttributes(state),
|
|
567
|
+
className,
|
|
568
|
+
children,
|
|
569
|
+
onClick() {
|
|
570
|
+
if (disabled) return;
|
|
571
|
+
group.select(value);
|
|
572
|
+
if (closeOnClick) closeAll();
|
|
573
|
+
}
|
|
574
|
+
},
|
|
575
|
+
consumerProps: disabled ? rest : { ...rest, onClick }
|
|
576
|
+
});
|
|
577
|
+
return /* @__PURE__ */ jsx5(MenuItemIndicatorContext.Provider, { value: state, children: element });
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// src/menu/MenuItemIndicator.tsx
|
|
581
|
+
function MenuItemIndicator({
|
|
582
|
+
className,
|
|
583
|
+
children,
|
|
584
|
+
render,
|
|
585
|
+
...rest
|
|
586
|
+
}) {
|
|
587
|
+
const state = useMenuItemIndicatorContext();
|
|
588
|
+
return useRender({
|
|
589
|
+
render,
|
|
590
|
+
defaultTagName: "span",
|
|
591
|
+
props: { "aria-hidden": true, ...menuCheckableDataAttributes(state), className, children },
|
|
592
|
+
consumerProps: rest
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// src/menu/MenuGroupLabel.tsx
|
|
597
|
+
import { useLayoutEffect as useLayoutEffect2 } from "react";
|
|
598
|
+
function MenuGroupLabel({ className, children, render, ...rest }) {
|
|
599
|
+
const group = useMenuGroupContext();
|
|
600
|
+
if (group === null) {
|
|
601
|
+
throw new Error("<Menu.GroupLabel> must be rendered inside <Menu.Group> or <Menu.RadioGroup>.");
|
|
602
|
+
}
|
|
603
|
+
const { labelId, registerLabel } = group;
|
|
604
|
+
useLayoutEffect2(registerLabel, [registerLabel]);
|
|
605
|
+
return useRender({
|
|
606
|
+
render,
|
|
607
|
+
defaultTagName: "div",
|
|
608
|
+
props: { id: labelId, className, children },
|
|
609
|
+
consumerProps: rest
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/menu/MenuSubmenuRoot.tsx
|
|
614
|
+
import { useEffect as useEffect3 } from "react";
|
|
615
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
616
|
+
function MenuSubmenuRoot({
|
|
617
|
+
focusableWhenDisabled,
|
|
618
|
+
children,
|
|
619
|
+
...props
|
|
620
|
+
}) {
|
|
621
|
+
const parent = useMenuRootContext("SubmenuRoot");
|
|
622
|
+
const context = useMenuRootValue(
|
|
623
|
+
"Menu.SubmenuRoot",
|
|
624
|
+
{ ...props, focusableWhenDisabled: focusableWhenDisabled ?? parent.focusableWhenDisabled },
|
|
625
|
+
parent
|
|
626
|
+
);
|
|
627
|
+
const { open, setOpen, triggerRef } = context;
|
|
628
|
+
useEffect3(() => {
|
|
629
|
+
if (!open) return;
|
|
630
|
+
const entry = { close: () => setOpen(false), triggerRef };
|
|
631
|
+
parent.openSubmenuRef.current = entry;
|
|
632
|
+
return () => {
|
|
633
|
+
if (parent.openSubmenuRef.current === entry) parent.openSubmenuRef.current = null;
|
|
634
|
+
};
|
|
635
|
+
}, [open, setOpen, triggerRef, parent]);
|
|
636
|
+
return /* @__PURE__ */ jsx6(MenuRootContext.Provider, { value: context, children });
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// src/menu/MenuSubmenuTrigger.tsx
|
|
640
|
+
import { useCallback as useCallback5, useEffect as useEffect4, useRef as useRef5 } from "react";
|
|
641
|
+
var SUBMENU_OPEN_DELAY = 100;
|
|
642
|
+
var SUBMENU_GRACE_PERIOD = 300;
|
|
643
|
+
function MenuSubmenuTrigger({
|
|
644
|
+
disabled = false,
|
|
645
|
+
textValue,
|
|
646
|
+
onClick,
|
|
647
|
+
className,
|
|
648
|
+
children,
|
|
649
|
+
render,
|
|
650
|
+
...rest
|
|
651
|
+
}) {
|
|
652
|
+
const submenu = useMenuRootContext("SubmenuTrigger");
|
|
653
|
+
const { parent } = submenu;
|
|
654
|
+
if (parent === null) {
|
|
655
|
+
throw new Error("<Menu.SubmenuTrigger> must be rendered inside <Menu.SubmenuRoot>.");
|
|
656
|
+
}
|
|
657
|
+
const { itemProps } = useMenuItem("SubmenuTrigger", { disabled, textValue, children }, parent);
|
|
658
|
+
const { open, setOpen, focusOnOpenRef, navigableItems, triggerRef, popupRef } = submenu;
|
|
659
|
+
const timerRef = useRef5(void 0);
|
|
660
|
+
useEffect4(() => () => clearTimeout(timerRef.current), []);
|
|
661
|
+
const openAt = (focus) => {
|
|
662
|
+
if (disabled) return;
|
|
663
|
+
focusOnOpenRef.current = focus;
|
|
664
|
+
if (!open) setOpen(true);
|
|
665
|
+
else if (focus === "first") navigableItems[0]?.ref.current?.focus();
|
|
666
|
+
};
|
|
667
|
+
const openAtRef = useRef5(openAt);
|
|
668
|
+
openAtRef.current = openAt;
|
|
669
|
+
const itemRef = itemProps.ref;
|
|
670
|
+
const ref = useCallback5(
|
|
671
|
+
(element) => {
|
|
672
|
+
itemRef.current = element;
|
|
673
|
+
triggerRef.current = element;
|
|
674
|
+
},
|
|
675
|
+
[itemRef, triggerRef]
|
|
676
|
+
);
|
|
677
|
+
return useRender({
|
|
678
|
+
render,
|
|
679
|
+
defaultTagName: "button",
|
|
680
|
+
props: {
|
|
681
|
+
...itemProps,
|
|
682
|
+
id: submenu.triggerId,
|
|
683
|
+
role: "menuitem",
|
|
684
|
+
"aria-haspopup": "menu",
|
|
685
|
+
"aria-expanded": open,
|
|
686
|
+
"aria-controls": submenu.popupId,
|
|
687
|
+
...menuDataAttributes({ open }),
|
|
688
|
+
style: { anchorName: submenu.anchorName },
|
|
689
|
+
className,
|
|
690
|
+
children,
|
|
691
|
+
ref,
|
|
692
|
+
onClick(event) {
|
|
693
|
+
openAt(event.detail === 0 ? "first" : "none");
|
|
694
|
+
},
|
|
695
|
+
onKeyDown(event) {
|
|
696
|
+
const rtl = getComputedStyle(event.currentTarget).direction === "rtl";
|
|
697
|
+
if (event.key !== (rtl ? "ArrowLeft" : "ArrowRight")) return;
|
|
698
|
+
event.preventDefault();
|
|
699
|
+
openAt("first");
|
|
700
|
+
},
|
|
701
|
+
onPointerMove(event) {
|
|
702
|
+
if (event.pointerType !== "mouse" || disabled) return;
|
|
703
|
+
const sibling = parent.openSubmenuRef.current;
|
|
704
|
+
if (sibling && sibling.triggerRef !== triggerRef) sibling.close();
|
|
705
|
+
if (open || timerRef.current !== void 0) return;
|
|
706
|
+
timerRef.current = setTimeout(() => {
|
|
707
|
+
timerRef.current = void 0;
|
|
708
|
+
openAtRef.current("none");
|
|
709
|
+
}, SUBMENU_OPEN_DELAY);
|
|
710
|
+
},
|
|
711
|
+
onPointerLeave(event) {
|
|
712
|
+
clearTimeout(timerRef.current);
|
|
713
|
+
timerRef.current = void 0;
|
|
714
|
+
const popup = popupRef.current;
|
|
715
|
+
if (!open || !popup) return;
|
|
716
|
+
parent.graceRef.current = {
|
|
717
|
+
area: graceArea({ x: event.clientX, y: event.clientY }, popup.getBoundingClientRect()),
|
|
718
|
+
until: performance.now() + SUBMENU_GRACE_PERIOD
|
|
719
|
+
};
|
|
248
720
|
}
|
|
249
721
|
},
|
|
250
722
|
consumerProps: disabled ? rest : { ...rest, onClick }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arun-dev/headless",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.16.0",
|
|
4
4
|
"description": "Unstyled React behaviour primitives — render engine, controlled/uncontrolled state, and data-* state projection. Ships no CSS and no class names.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|