@octanejs/radix 0.1.32 → 0.1.33
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/cjs/AccessibleIcon.cjs +56 -0
- package/dist/cjs/Accordion.cjs +272 -0
- package/dist/cjs/AlertDialog.cjs +144 -0
- package/dist/cjs/Arrow.cjs +43 -0
- package/dist/cjs/AspectRatio.cjs +56 -0
- package/dist/cjs/Avatar.cjs +150 -0
- package/dist/cjs/Checkbox.cjs +281 -0
- package/dist/cjs/Collapsible.cjs +176 -0
- package/dist/cjs/ContextMenu.cjs +315 -0
- package/dist/cjs/Dialog.cjs +291 -0
- package/dist/cjs/DismissableLayer.cjs +382 -0
- package/dist/cjs/DropdownMenu.cjs +296 -0
- package/dist/cjs/FocusScope.cjs +257 -0
- package/dist/cjs/Form.cjs +537 -0
- package/dist/cjs/HoverCard.cjs +309 -0
- package/dist/cjs/Label.cjs +42 -0
- package/dist/cjs/Menu.cjs +925 -0
- package/dist/cjs/Menubar.cjs +475 -0
- package/dist/cjs/NavigationMenu.cjs +942 -0
- package/dist/cjs/OneTimePasswordField.cjs +729 -0
- package/dist/cjs/PasswordToggleField.cjs +377 -0
- package/dist/cjs/Popover.cjs +340 -0
- package/dist/cjs/Popper.cjs +368 -0
- package/dist/cjs/Portal.cjs +40 -0
- package/dist/cjs/Presence.cjs +183 -0
- package/dist/cjs/Primitive.cjs +63 -0
- package/dist/cjs/Progress.cjs +111 -0
- package/dist/cjs/Radio.cjs +240 -0
- package/dist/cjs/RadioGroup.cjs +216 -0
- package/dist/cjs/RovingFocusGroup.cjs +253 -0
- package/dist/cjs/ScrollArea.cjs +828 -0
- package/dist/cjs/Select.cjs +1429 -0
- package/dist/cjs/Separator.cjs +51 -0
- package/dist/cjs/Slider.cjs +634 -0
- package/dist/cjs/Slot.cjs +97 -0
- package/dist/cjs/Switch.cjs +253 -0
- package/dist/cjs/Tabs.cjs +195 -0
- package/dist/cjs/Toast.cjs +733 -0
- package/dist/cjs/Toggle.cjs +54 -0
- package/dist/cjs/ToggleGroup.cjs +174 -0
- package/dist/cjs/Toolbar.cjs +165 -0
- package/dist/cjs/Tooltip.cjs +628 -0
- package/dist/cjs/VisuallyHidden.cjs +52 -0
- package/dist/cjs/collection.cjs +95 -0
- package/dist/cjs/compose-event-handlers.cjs +35 -0
- package/dist/cjs/compose-refs.cjs +68 -0
- package/dist/cjs/context.cjs +96 -0
- package/dist/cjs/direction.cjs +41 -0
- package/dist/cjs/focus-guards.cjs +76 -0
- package/dist/cjs/index.cjs +174 -0
- package/dist/cjs/internal.cjs +51 -0
- package/dist/cjs/scroll-lock.cjs +67 -0
- package/dist/cjs/use-callback-ref.cjs +47 -0
- package/dist/cjs/use-effect-event.cjs +52 -0
- package/dist/cjs/use-is-hydrated.cjs +43 -0
- package/dist/cjs/use-previous.cjs +47 -0
- package/dist/cjs/use-size.cjs +69 -0
- package/dist/cjs/useControllableState.cjs +80 -0
- package/dist/cjs/useId.cjs +36 -0
- package/package.json +14 -7
|
@@ -0,0 +1,925 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var Menu_exports = {};
|
|
30
|
+
__export(Menu_exports, {
|
|
31
|
+
Anchor: () => Anchor,
|
|
32
|
+
Arrow: () => Arrow,
|
|
33
|
+
CheckboxItem: () => CheckboxItem,
|
|
34
|
+
Content: () => Content,
|
|
35
|
+
Group: () => Group,
|
|
36
|
+
Item: () => Item,
|
|
37
|
+
ItemIndicator: () => ItemIndicator,
|
|
38
|
+
Label: () => Label,
|
|
39
|
+
Menu: () => Root,
|
|
40
|
+
MenuAnchor: () => Anchor,
|
|
41
|
+
MenuContent: () => Content,
|
|
42
|
+
MenuItem: () => Item,
|
|
43
|
+
Portal: () => Portal,
|
|
44
|
+
RadioGroup: () => RadioGroup,
|
|
45
|
+
RadioItem: () => RadioItem,
|
|
46
|
+
Root: () => Root,
|
|
47
|
+
Separator: () => Separator,
|
|
48
|
+
Sub: () => Sub,
|
|
49
|
+
SubContent: () => SubContent,
|
|
50
|
+
SubTrigger: () => SubTrigger,
|
|
51
|
+
createMenuScope: () => createMenuScope
|
|
52
|
+
});
|
|
53
|
+
module.exports = __toCommonJS(Menu_exports);
|
|
54
|
+
var import_octane = require("octane");
|
|
55
|
+
var import_aria_hidden = require("aria-hidden");
|
|
56
|
+
var import_collection = require("./collection.cjs");
|
|
57
|
+
var import_compose_event_handlers = require("./compose-event-handlers.cjs");
|
|
58
|
+
var import_compose_refs = require("./compose-refs.cjs");
|
|
59
|
+
var import_context = require("./context.cjs");
|
|
60
|
+
var import_direction = require("./direction.cjs");
|
|
61
|
+
var import_DismissableLayer = require("./DismissableLayer.cjs");
|
|
62
|
+
var import_FocusScope = require("./FocusScope.cjs");
|
|
63
|
+
var import_focus_guards = require("./focus-guards.cjs");
|
|
64
|
+
var import_internal = require("./internal.cjs");
|
|
65
|
+
var PopperPrimitive = __toESM(require("./Popper.cjs"), 1);
|
|
66
|
+
var import_Popper = require("./Popper.cjs");
|
|
67
|
+
var import_Portal = require("./Portal.cjs");
|
|
68
|
+
var import_Presence = require("./Presence.cjs");
|
|
69
|
+
var import_Primitive = require("./Primitive.cjs");
|
|
70
|
+
var RovingFocusGroup = __toESM(require("./RovingFocusGroup.cjs"), 1);
|
|
71
|
+
var import_RovingFocusGroup = require("./RovingFocusGroup.cjs");
|
|
72
|
+
var import_scroll_lock = require("./scroll-lock.cjs");
|
|
73
|
+
var import_use_callback_ref = require("./use-callback-ref.cjs");
|
|
74
|
+
var import_useId = require("./useId.cjs");
|
|
75
|
+
const SELECTION_KEYS = ["Enter", " "];
|
|
76
|
+
const FIRST_KEYS = ["ArrowDown", "PageUp", "Home"];
|
|
77
|
+
const LAST_KEYS = ["ArrowUp", "PageDown", "End"];
|
|
78
|
+
const FIRST_LAST_KEYS = [...FIRST_KEYS, ...LAST_KEYS];
|
|
79
|
+
const SUB_OPEN_KEYS = {
|
|
80
|
+
ltr: [...SELECTION_KEYS, "ArrowRight"],
|
|
81
|
+
rtl: [...SELECTION_KEYS, "ArrowLeft"]
|
|
82
|
+
};
|
|
83
|
+
const SUB_CLOSE_KEYS = {
|
|
84
|
+
ltr: ["ArrowLeft"],
|
|
85
|
+
rtl: ["ArrowRight"]
|
|
86
|
+
};
|
|
87
|
+
const MENU_NAME = "Menu";
|
|
88
|
+
const [Collection, useCollection, createCollectionScope] = (0, import_collection.createCollection)(MENU_NAME);
|
|
89
|
+
const [createMenuContext, createMenuScope] = (0, import_context.createContextScope)(MENU_NAME, [
|
|
90
|
+
createCollectionScope,
|
|
91
|
+
import_Popper.createPopperScope,
|
|
92
|
+
import_RovingFocusGroup.createRovingFocusGroupScope
|
|
93
|
+
]);
|
|
94
|
+
const usePopperScope = (0, import_Popper.createPopperScope)();
|
|
95
|
+
const useRovingFocusGroupScope = (0, import_RovingFocusGroup.createRovingFocusGroupScope)();
|
|
96
|
+
const [MenuProvider, useMenuContext] = createMenuContext(MENU_NAME);
|
|
97
|
+
const [MenuRootProvider, useMenuRootContext] = createMenuContext(MENU_NAME);
|
|
98
|
+
function Root(props) {
|
|
99
|
+
const slot = (0, import_internal.S)("Menu.Root");
|
|
100
|
+
const { __scopeMenu, open = false, children, dir, onOpenChange, modal = true } = props ?? {};
|
|
101
|
+
const popperScope = usePopperScope(__scopeMenu, (0, import_internal.subSlot)(slot, "popper"));
|
|
102
|
+
const [content, setContent] = (0, import_octane.useState)(null, (0, import_internal.subSlot)(slot, "content"));
|
|
103
|
+
const isUsingKeyboardRef = (0, import_octane.useRef)(false, (0, import_internal.subSlot)(slot, "keyboard"));
|
|
104
|
+
const handleOpenChange = (0, import_use_callback_ref.useCallbackRef)(onOpenChange, (0, import_internal.subSlot)(slot, "openChange"));
|
|
105
|
+
const direction = (0, import_direction.useDirection)(dir);
|
|
106
|
+
(0, import_octane.useEffect)(
|
|
107
|
+
() => {
|
|
108
|
+
const handleKeyDown = () => {
|
|
109
|
+
isUsingKeyboardRef.current = true;
|
|
110
|
+
document.addEventListener("pointerdown", handlePointer, { capture: true, once: true });
|
|
111
|
+
document.addEventListener("pointermove", handlePointer, { capture: true, once: true });
|
|
112
|
+
};
|
|
113
|
+
const handlePointer = () => isUsingKeyboardRef.current = false;
|
|
114
|
+
document.addEventListener("keydown", handleKeyDown, { capture: true });
|
|
115
|
+
return () => {
|
|
116
|
+
document.removeEventListener("keydown", handleKeyDown, { capture: true });
|
|
117
|
+
document.removeEventListener("pointerdown", handlePointer, { capture: true });
|
|
118
|
+
document.removeEventListener("pointermove", handlePointer, { capture: true });
|
|
119
|
+
};
|
|
120
|
+
},
|
|
121
|
+
[],
|
|
122
|
+
(0, import_internal.subSlot)(slot, "e:keyboard")
|
|
123
|
+
);
|
|
124
|
+
(0, import_octane.useEffect)(
|
|
125
|
+
() => {
|
|
126
|
+
if (!open) return;
|
|
127
|
+
const handleBlur = () => handleOpenChange(false);
|
|
128
|
+
window.addEventListener("blur", handleBlur);
|
|
129
|
+
return () => window.removeEventListener("blur", handleBlur);
|
|
130
|
+
},
|
|
131
|
+
[open, handleOpenChange],
|
|
132
|
+
(0, import_internal.subSlot)(slot, "e:blur")
|
|
133
|
+
);
|
|
134
|
+
return (0, import_octane.createElement)(PopperPrimitive.Root, {
|
|
135
|
+
...popperScope,
|
|
136
|
+
children: (0, import_octane.createElement)(MenuProvider, {
|
|
137
|
+
scope: __scopeMenu,
|
|
138
|
+
open,
|
|
139
|
+
onOpenChange: handleOpenChange,
|
|
140
|
+
content,
|
|
141
|
+
onContentChange: setContent,
|
|
142
|
+
children: (0, import_octane.createElement)(MenuRootProvider, {
|
|
143
|
+
scope: __scopeMenu,
|
|
144
|
+
onClose: (0, import_octane.useCallback)(
|
|
145
|
+
() => handleOpenChange(false),
|
|
146
|
+
[handleOpenChange],
|
|
147
|
+
(0, import_internal.subSlot)(slot, "close")
|
|
148
|
+
),
|
|
149
|
+
isUsingKeyboardRef,
|
|
150
|
+
dir: direction,
|
|
151
|
+
modal,
|
|
152
|
+
children
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
function Anchor(props) {
|
|
158
|
+
const slot = (0, import_internal.S)("Menu.Anchor");
|
|
159
|
+
const { __scopeMenu, ...anchorProps } = props ?? {};
|
|
160
|
+
const popperScope = usePopperScope(__scopeMenu, (0, import_internal.subSlot)(slot, "popper"));
|
|
161
|
+
return (0, import_octane.createElement)(PopperPrimitive.Anchor, { ...popperScope, ...anchorProps });
|
|
162
|
+
}
|
|
163
|
+
const [PortalProvider, usePortalContext] = createMenuContext(
|
|
164
|
+
"MenuPortal",
|
|
165
|
+
{ forceMount: void 0 }
|
|
166
|
+
);
|
|
167
|
+
function Portal(props) {
|
|
168
|
+
const { __scopeMenu, forceMount, children, container } = props ?? {};
|
|
169
|
+
const context = useMenuContext("MenuPortal", __scopeMenu);
|
|
170
|
+
return (0, import_octane.createElement)(PortalProvider, {
|
|
171
|
+
scope: __scopeMenu,
|
|
172
|
+
forceMount,
|
|
173
|
+
children: (0, import_octane.createElement)(import_Presence.Presence, {
|
|
174
|
+
present: forceMount || context.open,
|
|
175
|
+
children: (0, import_octane.createElement)(import_Portal.Portal, {
|
|
176
|
+
asChild: typeof children !== "function",
|
|
177
|
+
container,
|
|
178
|
+
children
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
const CONTENT_NAME = "MenuContent";
|
|
184
|
+
const [MenuContentProvider, useMenuContentContext] = createMenuContext(CONTENT_NAME);
|
|
185
|
+
function Content(props) {
|
|
186
|
+
const portalContext = usePortalContext(CONTENT_NAME, props?.__scopeMenu);
|
|
187
|
+
const { forceMount = portalContext.forceMount, ...contentProps } = props ?? {};
|
|
188
|
+
const context = useMenuContext(CONTENT_NAME, props?.__scopeMenu);
|
|
189
|
+
const rootContext = useMenuRootContext(CONTENT_NAME, props?.__scopeMenu);
|
|
190
|
+
return (0, import_octane.createElement)(Collection.Provider, {
|
|
191
|
+
scope: props?.__scopeMenu,
|
|
192
|
+
children: (0, import_octane.createElement)(import_Presence.Presence, {
|
|
193
|
+
present: forceMount || context.open,
|
|
194
|
+
children: (0, import_octane.createElement)(Collection.Slot, {
|
|
195
|
+
scope: props?.__scopeMenu,
|
|
196
|
+
children: rootContext.modal ? (0, import_octane.createElement)(MenuRootContentModal, contentProps) : (0, import_octane.createElement)(MenuRootContentNonModal, contentProps)
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
function MenuRootContentModal(props) {
|
|
202
|
+
const slot = (0, import_internal.S)("Menu.RootContentModal");
|
|
203
|
+
const { ref: forwardedRef, ...rest } = props;
|
|
204
|
+
const context = useMenuContext(CONTENT_NAME, props.__scopeMenu);
|
|
205
|
+
const ref = (0, import_octane.useRef)(null, (0, import_internal.subSlot)(slot, "ref"));
|
|
206
|
+
const composedRefs = (0, import_compose_refs.useComposedRefs)(forwardedRef, ref, (0, import_internal.subSlot)(slot, "refs"));
|
|
207
|
+
(0, import_octane.useEffect)(
|
|
208
|
+
() => {
|
|
209
|
+
const content = ref.current;
|
|
210
|
+
if (content) return (0, import_aria_hidden.hideOthers)(content);
|
|
211
|
+
},
|
|
212
|
+
[],
|
|
213
|
+
(0, import_internal.subSlot)(slot, "e:hide")
|
|
214
|
+
);
|
|
215
|
+
return (0, import_octane.createElement)(MenuContentImpl, {
|
|
216
|
+
...rest,
|
|
217
|
+
__scopeMenu: props.__scopeMenu,
|
|
218
|
+
ref: composedRefs,
|
|
219
|
+
// we make sure we're not trapping once it's been closed
|
|
220
|
+
// (closed !== unmounted when animating out)
|
|
221
|
+
trapFocus: context.open,
|
|
222
|
+
// make sure to only disable pointer events when open
|
|
223
|
+
// this avoids blocking interactions while animating out
|
|
224
|
+
disableOutsidePointerEvents: context.open,
|
|
225
|
+
disableOutsideScroll: true,
|
|
226
|
+
// When focus is trapped, a `focusout` event may still happen.
|
|
227
|
+
// We make sure we don't trigger our `onDismiss` in such case.
|
|
228
|
+
onFocusOutside: (0, import_compose_event_handlers.composeEventHandlers)(
|
|
229
|
+
props.onFocusOutside,
|
|
230
|
+
(event) => event.preventDefault(),
|
|
231
|
+
{ checkForDefaultPrevented: false }
|
|
232
|
+
),
|
|
233
|
+
onDismiss: () => context.onOpenChange(false)
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
function MenuRootContentNonModal(props) {
|
|
237
|
+
const context = useMenuContext(CONTENT_NAME, props.__scopeMenu);
|
|
238
|
+
return (0, import_octane.createElement)(MenuContentImpl, {
|
|
239
|
+
...props,
|
|
240
|
+
trapFocus: false,
|
|
241
|
+
disableOutsidePointerEvents: false,
|
|
242
|
+
disableOutsideScroll: false,
|
|
243
|
+
onDismiss: () => context.onOpenChange(false)
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
function MenuContentImpl(props) {
|
|
247
|
+
const slot = (0, import_internal.S)("Menu.ContentImpl");
|
|
248
|
+
const {
|
|
249
|
+
__scopeMenu,
|
|
250
|
+
ref: forwardedRef,
|
|
251
|
+
loop = false,
|
|
252
|
+
trapFocus,
|
|
253
|
+
onOpenAutoFocus,
|
|
254
|
+
onCloseAutoFocus,
|
|
255
|
+
disableOutsidePointerEvents,
|
|
256
|
+
onEntryFocus,
|
|
257
|
+
onEscapeKeyDown,
|
|
258
|
+
onPointerDownOutside,
|
|
259
|
+
onFocusOutside,
|
|
260
|
+
onInteractOutside,
|
|
261
|
+
onDismiss,
|
|
262
|
+
disableOutsideScroll,
|
|
263
|
+
...contentProps
|
|
264
|
+
} = props;
|
|
265
|
+
const context = useMenuContext(CONTENT_NAME, __scopeMenu);
|
|
266
|
+
const rootContext = useMenuRootContext(CONTENT_NAME, __scopeMenu);
|
|
267
|
+
const popperScope = usePopperScope(__scopeMenu, (0, import_internal.subSlot)(slot, "popper"));
|
|
268
|
+
const rovingFocusGroupScope = useRovingFocusGroupScope(__scopeMenu, (0, import_internal.subSlot)(slot, "rfs"));
|
|
269
|
+
const getItems = useCollection(__scopeMenu, (0, import_internal.subSlot)(slot, "items"));
|
|
270
|
+
const [currentItemId, setCurrentItemId] = (0, import_octane.useState)(null, (0, import_internal.subSlot)(slot, "item"));
|
|
271
|
+
const contentRef = (0, import_octane.useRef)(null, (0, import_internal.subSlot)(slot, "content"));
|
|
272
|
+
const composedRefs = (0, import_compose_refs.useComposedRefs)(
|
|
273
|
+
forwardedRef,
|
|
274
|
+
contentRef,
|
|
275
|
+
context.onContentChange,
|
|
276
|
+
(0, import_internal.subSlot)(slot, "refs")
|
|
277
|
+
);
|
|
278
|
+
const timerRef = (0, import_octane.useRef)(0, (0, import_internal.subSlot)(slot, "timer"));
|
|
279
|
+
const searchRef = (0, import_octane.useRef)("", (0, import_internal.subSlot)(slot, "search"));
|
|
280
|
+
const pointerGraceTimerRef = (0, import_octane.useRef)(0, (0, import_internal.subSlot)(slot, "graceTimer"));
|
|
281
|
+
const pointerGraceIntentRef = (0, import_octane.useRef)(null, (0, import_internal.subSlot)(slot, "graceIntent"));
|
|
282
|
+
const pointerDirRef = (0, import_octane.useRef)("right", (0, import_internal.subSlot)(slot, "pointerDir"));
|
|
283
|
+
const lastPointerXRef = (0, import_octane.useRef)(0, (0, import_internal.subSlot)(slot, "lastX"));
|
|
284
|
+
(0, import_scroll_lock.useScrollLock)(!!disableOutsideScroll, (0, import_internal.subSlot)(slot, "lock"));
|
|
285
|
+
const handleTypeaheadSearch = (key) => {
|
|
286
|
+
const search = searchRef.current + key;
|
|
287
|
+
const items = getItems().filter((item) => !item.disabled);
|
|
288
|
+
const currentItem = document.activeElement;
|
|
289
|
+
const currentMatch = items.find((item) => item.ref.current === currentItem)?.textValue;
|
|
290
|
+
const values = items.map((item) => item.textValue);
|
|
291
|
+
const nextMatch = getNextMatch(values, search, currentMatch);
|
|
292
|
+
const newItem = items.find((item) => item.textValue === nextMatch)?.ref.current;
|
|
293
|
+
(function updateSearch(value) {
|
|
294
|
+
searchRef.current = value;
|
|
295
|
+
window.clearTimeout(timerRef.current);
|
|
296
|
+
if (value !== "") timerRef.current = window.setTimeout(() => updateSearch(""), 1e3);
|
|
297
|
+
})(search);
|
|
298
|
+
if (newItem) {
|
|
299
|
+
setTimeout(() => newItem.focus());
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
(0, import_octane.useEffect)(
|
|
303
|
+
() => {
|
|
304
|
+
return () => window.clearTimeout(timerRef.current);
|
|
305
|
+
},
|
|
306
|
+
[],
|
|
307
|
+
(0, import_internal.subSlot)(slot, "e:timer")
|
|
308
|
+
);
|
|
309
|
+
(0, import_focus_guards.useFocusGuards)((0, import_internal.subSlot)(slot, "guards"));
|
|
310
|
+
const isPointerMovingToSubmenu = (0, import_octane.useCallback)(
|
|
311
|
+
(event) => {
|
|
312
|
+
const isMovingTowards = pointerDirRef.current === pointerGraceIntentRef.current?.side;
|
|
313
|
+
return isMovingTowards && isPointerInGraceArea(event, pointerGraceIntentRef.current?.area);
|
|
314
|
+
},
|
|
315
|
+
[],
|
|
316
|
+
(0, import_internal.subSlot)(slot, "movingToSub")
|
|
317
|
+
);
|
|
318
|
+
return (0, import_octane.createElement)(MenuContentProvider, {
|
|
319
|
+
scope: __scopeMenu,
|
|
320
|
+
searchRef,
|
|
321
|
+
onItemEnter: (0, import_octane.useCallback)(
|
|
322
|
+
(event) => {
|
|
323
|
+
if (isPointerMovingToSubmenu(event)) event.preventDefault();
|
|
324
|
+
},
|
|
325
|
+
[isPointerMovingToSubmenu],
|
|
326
|
+
(0, import_internal.subSlot)(slot, "itemEnter")
|
|
327
|
+
),
|
|
328
|
+
onItemLeave: (0, import_octane.useCallback)(
|
|
329
|
+
(event) => {
|
|
330
|
+
if (isPointerMovingToSubmenu(event)) return;
|
|
331
|
+
contentRef.current?.focus();
|
|
332
|
+
setCurrentItemId(null);
|
|
333
|
+
},
|
|
334
|
+
[isPointerMovingToSubmenu],
|
|
335
|
+
(0, import_internal.subSlot)(slot, "itemLeave")
|
|
336
|
+
),
|
|
337
|
+
onTriggerLeave: (0, import_octane.useCallback)(
|
|
338
|
+
(event) => {
|
|
339
|
+
if (isPointerMovingToSubmenu(event)) event.preventDefault();
|
|
340
|
+
},
|
|
341
|
+
[isPointerMovingToSubmenu],
|
|
342
|
+
(0, import_internal.subSlot)(slot, "triggerLeave")
|
|
343
|
+
),
|
|
344
|
+
pointerGraceTimerRef,
|
|
345
|
+
onPointerGraceIntentChange: (0, import_octane.useCallback)(
|
|
346
|
+
(intent) => {
|
|
347
|
+
pointerGraceIntentRef.current = intent;
|
|
348
|
+
},
|
|
349
|
+
[],
|
|
350
|
+
(0, import_internal.subSlot)(slot, "graceChange")
|
|
351
|
+
),
|
|
352
|
+
children: (0, import_octane.createElement)(import_FocusScope.FocusScope, {
|
|
353
|
+
asChild: true,
|
|
354
|
+
trapped: trapFocus,
|
|
355
|
+
onMountAutoFocus: (0, import_compose_event_handlers.composeEventHandlers)(onOpenAutoFocus, (event) => {
|
|
356
|
+
event.preventDefault();
|
|
357
|
+
contentRef.current?.focus({ preventScroll: true });
|
|
358
|
+
}),
|
|
359
|
+
onUnmountAutoFocus: onCloseAutoFocus,
|
|
360
|
+
children: (0, import_octane.createElement)(import_DismissableLayer.DismissableLayer, {
|
|
361
|
+
asChild: true,
|
|
362
|
+
disableOutsidePointerEvents,
|
|
363
|
+
onEscapeKeyDown,
|
|
364
|
+
onPointerDownOutside,
|
|
365
|
+
onFocusOutside,
|
|
366
|
+
onInteractOutside,
|
|
367
|
+
onDismiss,
|
|
368
|
+
children: (0, import_octane.createElement)(RovingFocusGroup.Root, {
|
|
369
|
+
asChild: true,
|
|
370
|
+
...rovingFocusGroupScope,
|
|
371
|
+
dir: rootContext.dir,
|
|
372
|
+
orientation: "vertical",
|
|
373
|
+
loop,
|
|
374
|
+
currentTabStopId: currentItemId,
|
|
375
|
+
onCurrentTabStopIdChange: setCurrentItemId,
|
|
376
|
+
onEntryFocus: (0, import_compose_event_handlers.composeEventHandlers)(onEntryFocus, (event) => {
|
|
377
|
+
if (!rootContext.isUsingKeyboardRef.current) event.preventDefault();
|
|
378
|
+
}),
|
|
379
|
+
preventScrollOnEntryFocus: true,
|
|
380
|
+
children: (0, import_octane.createElement)(PopperPrimitive.Content, {
|
|
381
|
+
role: "menu",
|
|
382
|
+
"aria-orientation": "vertical",
|
|
383
|
+
"data-state": getOpenState(context.open),
|
|
384
|
+
"data-radix-menu-content": "",
|
|
385
|
+
dir: rootContext.dir,
|
|
386
|
+
...popperScope,
|
|
387
|
+
...contentProps,
|
|
388
|
+
ref: composedRefs,
|
|
389
|
+
style: { outline: "none", ...contentProps.style },
|
|
390
|
+
onKeyDown: (0, import_compose_event_handlers.composeEventHandlers)(contentProps.onKeyDown, (event) => {
|
|
391
|
+
const target = event.target;
|
|
392
|
+
const isKeyDownInside = target.closest("[data-radix-menu-content]") === event.currentTarget;
|
|
393
|
+
const isModifierKey = event.ctrlKey || event.altKey || event.metaKey;
|
|
394
|
+
const isCharacterKey = event.key.length === 1;
|
|
395
|
+
if (isKeyDownInside) {
|
|
396
|
+
if (event.key === "Tab") event.preventDefault();
|
|
397
|
+
if (!isModifierKey && isCharacterKey) handleTypeaheadSearch(event.key);
|
|
398
|
+
}
|
|
399
|
+
const content = contentRef.current;
|
|
400
|
+
if (event.target !== content) return;
|
|
401
|
+
if (!FIRST_LAST_KEYS.includes(event.key)) return;
|
|
402
|
+
event.preventDefault();
|
|
403
|
+
const items = getItems().filter((item) => !item.disabled);
|
|
404
|
+
const candidateNodes = items.map((item) => item.ref.current);
|
|
405
|
+
if (LAST_KEYS.includes(event.key)) candidateNodes.reverse();
|
|
406
|
+
focusFirst(candidateNodes);
|
|
407
|
+
}),
|
|
408
|
+
onBlur: (0, import_compose_event_handlers.composeEventHandlers)(props.onBlur, (event) => {
|
|
409
|
+
if (!event.currentTarget.contains(event.target)) {
|
|
410
|
+
window.clearTimeout(timerRef.current);
|
|
411
|
+
searchRef.current = "";
|
|
412
|
+
}
|
|
413
|
+
}),
|
|
414
|
+
onPointerMove: (0, import_compose_event_handlers.composeEventHandlers)(
|
|
415
|
+
props.onPointerMove,
|
|
416
|
+
whenMouse((event) => {
|
|
417
|
+
const target = event.target;
|
|
418
|
+
const pointerXHasChanged = lastPointerXRef.current !== event.clientX;
|
|
419
|
+
if (event.currentTarget.contains(target) && pointerXHasChanged) {
|
|
420
|
+
const newDir = event.clientX > lastPointerXRef.current ? "right" : "left";
|
|
421
|
+
pointerDirRef.current = newDir;
|
|
422
|
+
lastPointerXRef.current = event.clientX;
|
|
423
|
+
}
|
|
424
|
+
})
|
|
425
|
+
)
|
|
426
|
+
})
|
|
427
|
+
})
|
|
428
|
+
})
|
|
429
|
+
})
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
function Group(props) {
|
|
433
|
+
const { __scopeMenu, ...groupProps } = props ?? {};
|
|
434
|
+
return (0, import_octane.createElement)(import_Primitive.Primitive.div, { role: "group", ...groupProps });
|
|
435
|
+
}
|
|
436
|
+
function Label(props) {
|
|
437
|
+
const { __scopeMenu, ...labelProps } = props ?? {};
|
|
438
|
+
return (0, import_octane.createElement)(import_Primitive.Primitive.div, { ...labelProps });
|
|
439
|
+
}
|
|
440
|
+
const ITEM_NAME = "MenuItem";
|
|
441
|
+
const ITEM_SELECT = "menu.itemSelect";
|
|
442
|
+
function Item(props) {
|
|
443
|
+
const slot = (0, import_internal.S)("Menu.Item");
|
|
444
|
+
const { disabled = false, onSelect, ref: forwardedRef, ...itemProps } = props ?? {};
|
|
445
|
+
const ref = (0, import_octane.useRef)(null, (0, import_internal.subSlot)(slot, "ref"));
|
|
446
|
+
const rootContext = useMenuRootContext(ITEM_NAME, props?.__scopeMenu);
|
|
447
|
+
const contentContext = useMenuContentContext(ITEM_NAME, props?.__scopeMenu);
|
|
448
|
+
const composedRefs = (0, import_compose_refs.useComposedRefs)(forwardedRef, ref, (0, import_internal.subSlot)(slot, "refs"));
|
|
449
|
+
const isPointerDownRef = (0, import_octane.useRef)(false, (0, import_internal.subSlot)(slot, "pointerDown"));
|
|
450
|
+
const handleSelect = () => {
|
|
451
|
+
const menuItem = ref.current;
|
|
452
|
+
if (!disabled && menuItem) {
|
|
453
|
+
const itemSelectEvent = new CustomEvent(ITEM_SELECT, { bubbles: true, cancelable: true });
|
|
454
|
+
menuItem.addEventListener(ITEM_SELECT, (event) => onSelect?.(event), { once: true });
|
|
455
|
+
(0, import_Primitive.dispatchDiscreteCustomEvent)(menuItem, itemSelectEvent);
|
|
456
|
+
if (itemSelectEvent.defaultPrevented) {
|
|
457
|
+
isPointerDownRef.current = false;
|
|
458
|
+
} else {
|
|
459
|
+
rootContext.onClose();
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
return (0, import_octane.createElement)(MenuItemImpl, {
|
|
464
|
+
...itemProps,
|
|
465
|
+
__scopeMenu: props?.__scopeMenu,
|
|
466
|
+
ref: composedRefs,
|
|
467
|
+
disabled,
|
|
468
|
+
onClick: (0, import_compose_event_handlers.composeEventHandlers)(props?.onClick, handleSelect),
|
|
469
|
+
onPointerDown: (event) => {
|
|
470
|
+
props?.onPointerDown?.(event);
|
|
471
|
+
isPointerDownRef.current = true;
|
|
472
|
+
},
|
|
473
|
+
onPointerUp: (0, import_compose_event_handlers.composeEventHandlers)(props?.onPointerUp, (event) => {
|
|
474
|
+
if (!isPointerDownRef.current) event.currentTarget?.click();
|
|
475
|
+
}),
|
|
476
|
+
onKeyDown: (0, import_compose_event_handlers.composeEventHandlers)(props?.onKeyDown, (event) => {
|
|
477
|
+
const isTypingAhead = contentContext.searchRef.current !== "";
|
|
478
|
+
if (disabled || isTypingAhead && event.key === " ") return;
|
|
479
|
+
if (SELECTION_KEYS.includes(event.key)) {
|
|
480
|
+
event.currentTarget.click();
|
|
481
|
+
event.preventDefault();
|
|
482
|
+
}
|
|
483
|
+
})
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
function MenuItemImpl(props) {
|
|
487
|
+
const slot = (0, import_internal.S)("Menu.ItemImpl");
|
|
488
|
+
const { __scopeMenu, disabled = false, textValue, ref: forwardedRef, ...itemProps } = props;
|
|
489
|
+
const contentContext = useMenuContentContext(ITEM_NAME, __scopeMenu);
|
|
490
|
+
const rovingFocusGroupScope = useRovingFocusGroupScope(__scopeMenu, (0, import_internal.subSlot)(slot, "rfs"));
|
|
491
|
+
const ref = (0, import_octane.useRef)(null, (0, import_internal.subSlot)(slot, "ref"));
|
|
492
|
+
const composedRefs = (0, import_compose_refs.useComposedRefs)(forwardedRef, ref, (0, import_internal.subSlot)(slot, "refs"));
|
|
493
|
+
const [isFocused, setIsFocused] = (0, import_octane.useState)(false, (0, import_internal.subSlot)(slot, "focused"));
|
|
494
|
+
const [textContent, setTextContent] = (0, import_octane.useState)("", (0, import_internal.subSlot)(slot, "text"));
|
|
495
|
+
(0, import_octane.useEffect)(
|
|
496
|
+
() => {
|
|
497
|
+
const menuItem = ref.current;
|
|
498
|
+
if (menuItem) {
|
|
499
|
+
setTextContent((menuItem.textContent ?? "").trim());
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
[itemProps.children],
|
|
503
|
+
(0, import_internal.subSlot)(slot, "e:text")
|
|
504
|
+
);
|
|
505
|
+
return (0, import_octane.createElement)(Collection.ItemSlot, {
|
|
506
|
+
scope: __scopeMenu,
|
|
507
|
+
disabled,
|
|
508
|
+
textValue: textValue ?? textContent,
|
|
509
|
+
children: (0, import_octane.createElement)(RovingFocusGroup.Item, {
|
|
510
|
+
asChild: true,
|
|
511
|
+
...rovingFocusGroupScope,
|
|
512
|
+
focusable: !disabled,
|
|
513
|
+
children: (0, import_octane.createElement)(import_Primitive.Primitive.div, {
|
|
514
|
+
role: "menuitem",
|
|
515
|
+
"data-highlighted": isFocused ? "" : void 0,
|
|
516
|
+
"aria-disabled": disabled || void 0,
|
|
517
|
+
"data-disabled": disabled ? "" : void 0,
|
|
518
|
+
...itemProps,
|
|
519
|
+
ref: composedRefs,
|
|
520
|
+
/**
|
|
521
|
+
* We focus items on `pointerMove` to achieve the following:
|
|
522
|
+
*
|
|
523
|
+
* - Mouse over an item (it focuses)
|
|
524
|
+
* - Leave mouse where it is and use keyboard to focus a different item
|
|
525
|
+
* - Wiggle mouse without it leaving previously focused item
|
|
526
|
+
* - Previously focused item should re-focus
|
|
527
|
+
*
|
|
528
|
+
* If we used `mouseOver`/`mouseEnter` it would not re-focus when the mouse
|
|
529
|
+
* wiggles. This is to match native menu implementation.
|
|
530
|
+
*/
|
|
531
|
+
onPointerMove: (0, import_compose_event_handlers.composeEventHandlers)(
|
|
532
|
+
props.onPointerMove,
|
|
533
|
+
whenMouse((event) => {
|
|
534
|
+
if (disabled) {
|
|
535
|
+
contentContext.onItemLeave(event);
|
|
536
|
+
} else {
|
|
537
|
+
contentContext.onItemEnter(event);
|
|
538
|
+
if (!event.defaultPrevented) {
|
|
539
|
+
const item = event.currentTarget;
|
|
540
|
+
item.focus({ preventScroll: true });
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
})
|
|
544
|
+
),
|
|
545
|
+
onPointerLeave: (0, import_compose_event_handlers.composeEventHandlers)(
|
|
546
|
+
props.onPointerLeave,
|
|
547
|
+
whenMouse((event) => contentContext.onItemLeave(event))
|
|
548
|
+
),
|
|
549
|
+
onFocus: (0, import_compose_event_handlers.composeEventHandlers)(props.onFocus, () => setIsFocused(true)),
|
|
550
|
+
onBlur: (0, import_compose_event_handlers.composeEventHandlers)(props.onBlur, () => setIsFocused(false))
|
|
551
|
+
})
|
|
552
|
+
})
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
function CheckboxItem(props) {
|
|
556
|
+
const { checked = false, onCheckedChange, ...checkboxItemProps } = props ?? {};
|
|
557
|
+
return (0, import_octane.createElement)(ItemIndicatorProvider, {
|
|
558
|
+
scope: props?.__scopeMenu,
|
|
559
|
+
checked,
|
|
560
|
+
children: (0, import_octane.createElement)(Item, {
|
|
561
|
+
role: "menuitemcheckbox",
|
|
562
|
+
"aria-checked": isIndeterminate(checked) ? "mixed" : checked,
|
|
563
|
+
...checkboxItemProps,
|
|
564
|
+
"data-state": getCheckedState(checked),
|
|
565
|
+
onSelect: (0, import_compose_event_handlers.composeEventHandlers)(
|
|
566
|
+
checkboxItemProps.onSelect,
|
|
567
|
+
() => onCheckedChange?.(isIndeterminate(checked) ? true : !checked),
|
|
568
|
+
{ checkForDefaultPrevented: false }
|
|
569
|
+
)
|
|
570
|
+
})
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
const [RadioGroupProvider, useRadioGroupContext] = createMenuContext("MenuRadioGroup", { value: void 0, onValueChange: () => {
|
|
574
|
+
} });
|
|
575
|
+
function RadioGroup(props) {
|
|
576
|
+
const slot = (0, import_internal.S)("Menu.RadioGroup");
|
|
577
|
+
const { value, onValueChange, ...groupProps } = props ?? {};
|
|
578
|
+
const handleValueChange = (0, import_use_callback_ref.useCallbackRef)(onValueChange, (0, import_internal.subSlot)(slot, "change"));
|
|
579
|
+
return (0, import_octane.createElement)(RadioGroupProvider, {
|
|
580
|
+
scope: props?.__scopeMenu,
|
|
581
|
+
value,
|
|
582
|
+
onValueChange: handleValueChange,
|
|
583
|
+
children: (0, import_octane.createElement)(Group, groupProps)
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
function RadioItem(props) {
|
|
587
|
+
const { value, ...radioItemProps } = props ?? {};
|
|
588
|
+
const context = useRadioGroupContext("MenuRadioItem", props?.__scopeMenu);
|
|
589
|
+
const checked = value === context.value;
|
|
590
|
+
return (0, import_octane.createElement)(ItemIndicatorProvider, {
|
|
591
|
+
scope: props?.__scopeMenu,
|
|
592
|
+
checked,
|
|
593
|
+
children: (0, import_octane.createElement)(Item, {
|
|
594
|
+
role: "menuitemradio",
|
|
595
|
+
"aria-checked": checked,
|
|
596
|
+
...radioItemProps,
|
|
597
|
+
"data-state": getCheckedState(checked),
|
|
598
|
+
onSelect: (0, import_compose_event_handlers.composeEventHandlers)(
|
|
599
|
+
radioItemProps.onSelect,
|
|
600
|
+
() => context.onValueChange?.(value),
|
|
601
|
+
{ checkForDefaultPrevented: false }
|
|
602
|
+
)
|
|
603
|
+
})
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
const [ItemIndicatorProvider, useItemIndicatorContext] = createMenuContext("MenuItemIndicator", { checked: false });
|
|
607
|
+
function ItemIndicator(props) {
|
|
608
|
+
const { __scopeMenu, forceMount, ...itemIndicatorProps } = props ?? {};
|
|
609
|
+
const indicatorContext = useItemIndicatorContext("MenuItemIndicator", __scopeMenu);
|
|
610
|
+
return (0, import_octane.createElement)(import_Presence.Presence, {
|
|
611
|
+
present: forceMount || isIndeterminate(indicatorContext.checked) || indicatorContext.checked === true,
|
|
612
|
+
children: (0, import_octane.createElement)(import_Primitive.Primitive.span, {
|
|
613
|
+
...itemIndicatorProps,
|
|
614
|
+
"data-state": getCheckedState(indicatorContext.checked)
|
|
615
|
+
})
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
function Separator(props) {
|
|
619
|
+
const { __scopeMenu, ...separatorProps } = props ?? {};
|
|
620
|
+
return (0, import_octane.createElement)(import_Primitive.Primitive.div, {
|
|
621
|
+
role: "separator",
|
|
622
|
+
"aria-orientation": "horizontal",
|
|
623
|
+
...separatorProps
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
function Arrow(props) {
|
|
627
|
+
const slot = (0, import_internal.S)("Menu.Arrow");
|
|
628
|
+
const { __scopeMenu, ...arrowProps } = props ?? {};
|
|
629
|
+
const popperScope = usePopperScope(__scopeMenu, (0, import_internal.subSlot)(slot, "popper"));
|
|
630
|
+
return (0, import_octane.createElement)(PopperPrimitive.Arrow, { ...popperScope, ...arrowProps });
|
|
631
|
+
}
|
|
632
|
+
const SUB_NAME = "MenuSub";
|
|
633
|
+
const [MenuSubProvider, useMenuSubContext] = createMenuContext(SUB_NAME);
|
|
634
|
+
function Sub(props) {
|
|
635
|
+
const slot = (0, import_internal.S)("Menu.Sub");
|
|
636
|
+
const { __scopeMenu, children, open = false, onOpenChange } = props ?? {};
|
|
637
|
+
const parentMenuContext = useMenuContext(SUB_NAME, __scopeMenu);
|
|
638
|
+
const popperScope = usePopperScope(__scopeMenu, (0, import_internal.subSlot)(slot, "popper"));
|
|
639
|
+
const [trigger, setTrigger] = (0, import_octane.useState)(null, (0, import_internal.subSlot)(slot, "trigger"));
|
|
640
|
+
const [content, setContent] = (0, import_octane.useState)(null, (0, import_internal.subSlot)(slot, "content"));
|
|
641
|
+
const handleOpenChange = (0, import_use_callback_ref.useCallbackRef)(onOpenChange, (0, import_internal.subSlot)(slot, "openChange"));
|
|
642
|
+
(0, import_octane.useEffect)(
|
|
643
|
+
() => {
|
|
644
|
+
if (parentMenuContext.open === false) handleOpenChange(false);
|
|
645
|
+
return () => handleOpenChange(false);
|
|
646
|
+
},
|
|
647
|
+
[parentMenuContext.open, handleOpenChange],
|
|
648
|
+
(0, import_internal.subSlot)(slot, "e:parent")
|
|
649
|
+
);
|
|
650
|
+
return (0, import_octane.createElement)(PopperPrimitive.Root, {
|
|
651
|
+
...popperScope,
|
|
652
|
+
children: (0, import_octane.createElement)(MenuProvider, {
|
|
653
|
+
scope: __scopeMenu,
|
|
654
|
+
open,
|
|
655
|
+
onOpenChange: handleOpenChange,
|
|
656
|
+
content,
|
|
657
|
+
onContentChange: setContent,
|
|
658
|
+
children: (0, import_octane.createElement)(MenuSubProvider, {
|
|
659
|
+
scope: __scopeMenu,
|
|
660
|
+
contentId: (0, import_useId.useId)((0, import_internal.subSlot)(slot, "contentId")),
|
|
661
|
+
triggerId: (0, import_useId.useId)((0, import_internal.subSlot)(slot, "triggerId")),
|
|
662
|
+
trigger,
|
|
663
|
+
onTriggerChange: setTrigger,
|
|
664
|
+
children
|
|
665
|
+
})
|
|
666
|
+
})
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
const SUB_TRIGGER_NAME = "MenuSubTrigger";
|
|
670
|
+
function SubTrigger(props) {
|
|
671
|
+
const slot = (0, import_internal.S)("Menu.SubTrigger");
|
|
672
|
+
const context = useMenuContext(SUB_TRIGGER_NAME, props?.__scopeMenu);
|
|
673
|
+
const rootContext = useMenuRootContext(SUB_TRIGGER_NAME, props?.__scopeMenu);
|
|
674
|
+
const subContext = useMenuSubContext(SUB_TRIGGER_NAME, props?.__scopeMenu);
|
|
675
|
+
const contentContext = useMenuContentContext(SUB_TRIGGER_NAME, props?.__scopeMenu);
|
|
676
|
+
const openTimerRef = (0, import_octane.useRef)(null, (0, import_internal.subSlot)(slot, "openTimer"));
|
|
677
|
+
const { pointerGraceTimerRef, onPointerGraceIntentChange } = contentContext;
|
|
678
|
+
const scope = { __scopeMenu: props?.__scopeMenu };
|
|
679
|
+
const clearOpenTimer = (0, import_octane.useCallback)(
|
|
680
|
+
() => {
|
|
681
|
+
if (openTimerRef.current) window.clearTimeout(openTimerRef.current);
|
|
682
|
+
openTimerRef.current = null;
|
|
683
|
+
},
|
|
684
|
+
[],
|
|
685
|
+
(0, import_internal.subSlot)(slot, "clear")
|
|
686
|
+
);
|
|
687
|
+
(0, import_octane.useEffect)(() => clearOpenTimer, [clearOpenTimer], (0, import_internal.subSlot)(slot, "e:clear"));
|
|
688
|
+
(0, import_octane.useEffect)(
|
|
689
|
+
() => {
|
|
690
|
+
const pointerGraceTimer = pointerGraceTimerRef.current;
|
|
691
|
+
return () => {
|
|
692
|
+
window.clearTimeout(pointerGraceTimer);
|
|
693
|
+
onPointerGraceIntentChange(null);
|
|
694
|
+
};
|
|
695
|
+
},
|
|
696
|
+
[pointerGraceTimerRef, onPointerGraceIntentChange],
|
|
697
|
+
(0, import_internal.subSlot)(slot, "e:grace")
|
|
698
|
+
);
|
|
699
|
+
const composedRefs = (0, import_compose_refs.useComposedRefs)(
|
|
700
|
+
props?.ref,
|
|
701
|
+
subContext.onTriggerChange,
|
|
702
|
+
(0, import_internal.subSlot)(slot, "refs")
|
|
703
|
+
);
|
|
704
|
+
return (0, import_octane.createElement)(Anchor, {
|
|
705
|
+
asChild: true,
|
|
706
|
+
...scope,
|
|
707
|
+
children: (0, import_octane.createElement)(MenuItemImpl, {
|
|
708
|
+
id: subContext.triggerId,
|
|
709
|
+
"aria-haspopup": "menu",
|
|
710
|
+
"aria-expanded": context.open,
|
|
711
|
+
"aria-controls": context.open ? subContext.contentId : void 0,
|
|
712
|
+
"data-state": getOpenState(context.open),
|
|
713
|
+
...props,
|
|
714
|
+
ref: composedRefs,
|
|
715
|
+
// This is redundant for mouse users but we cannot determine pointer type from
|
|
716
|
+
// click event and we cannot use pointerup event (see git history for reasons why)
|
|
717
|
+
onClick: (event) => {
|
|
718
|
+
props?.onClick?.(event);
|
|
719
|
+
if (props?.disabled || event.defaultPrevented) return;
|
|
720
|
+
event.currentTarget.focus();
|
|
721
|
+
if (!context.open) context.onOpenChange(true);
|
|
722
|
+
},
|
|
723
|
+
onPointerMove: (0, import_compose_event_handlers.composeEventHandlers)(
|
|
724
|
+
props?.onPointerMove,
|
|
725
|
+
whenMouse((event) => {
|
|
726
|
+
contentContext.onItemEnter(event);
|
|
727
|
+
if (event.defaultPrevented) return;
|
|
728
|
+
if (!props?.disabled && !context.open && !openTimerRef.current) {
|
|
729
|
+
contentContext.onPointerGraceIntentChange(null);
|
|
730
|
+
openTimerRef.current = window.setTimeout(() => {
|
|
731
|
+
context.onOpenChange(true);
|
|
732
|
+
clearOpenTimer();
|
|
733
|
+
}, 100);
|
|
734
|
+
}
|
|
735
|
+
})
|
|
736
|
+
),
|
|
737
|
+
onPointerLeave: (0, import_compose_event_handlers.composeEventHandlers)(
|
|
738
|
+
props?.onPointerLeave,
|
|
739
|
+
whenMouse((event) => {
|
|
740
|
+
clearOpenTimer();
|
|
741
|
+
const contentRect = context.content?.getBoundingClientRect();
|
|
742
|
+
if (contentRect) {
|
|
743
|
+
const side = context.content?.dataset.side ?? "right";
|
|
744
|
+
const rightSide = side === "right";
|
|
745
|
+
const bleed = rightSide ? -5 : 5;
|
|
746
|
+
const contentNearEdge = contentRect[rightSide ? "left" : "right"];
|
|
747
|
+
const contentFarEdge = contentRect[rightSide ? "right" : "left"];
|
|
748
|
+
contentContext.onPointerGraceIntentChange({
|
|
749
|
+
area: [
|
|
750
|
+
// Apply a bleed on clientX to ensure that our exit point is
|
|
751
|
+
// consistently within polygon bounds
|
|
752
|
+
{ x: event.clientX + bleed, y: event.clientY },
|
|
753
|
+
{ x: contentNearEdge, y: contentRect.top },
|
|
754
|
+
{ x: contentFarEdge, y: contentRect.top },
|
|
755
|
+
{ x: contentFarEdge, y: contentRect.bottom },
|
|
756
|
+
{ x: contentNearEdge, y: contentRect.bottom }
|
|
757
|
+
],
|
|
758
|
+
side
|
|
759
|
+
});
|
|
760
|
+
window.clearTimeout(pointerGraceTimerRef.current);
|
|
761
|
+
pointerGraceTimerRef.current = window.setTimeout(
|
|
762
|
+
() => contentContext.onPointerGraceIntentChange(null),
|
|
763
|
+
300
|
|
764
|
+
);
|
|
765
|
+
} else {
|
|
766
|
+
contentContext.onTriggerLeave(event);
|
|
767
|
+
if (event.defaultPrevented) return;
|
|
768
|
+
contentContext.onPointerGraceIntentChange(null);
|
|
769
|
+
}
|
|
770
|
+
})
|
|
771
|
+
),
|
|
772
|
+
onKeyDown: (0, import_compose_event_handlers.composeEventHandlers)(props?.onKeyDown, (event) => {
|
|
773
|
+
const isTypingAhead = contentContext.searchRef.current !== "";
|
|
774
|
+
if (props?.disabled || isTypingAhead && event.key === " ") return;
|
|
775
|
+
if (SUB_OPEN_KEYS[rootContext.dir].includes(event.key)) {
|
|
776
|
+
context.onOpenChange(true);
|
|
777
|
+
context.content?.focus();
|
|
778
|
+
event.preventDefault();
|
|
779
|
+
}
|
|
780
|
+
})
|
|
781
|
+
})
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
const SUB_CONTENT_NAME = "MenuSubContent";
|
|
785
|
+
function SubContent(props) {
|
|
786
|
+
const slot = (0, import_internal.S)("Menu.SubContent");
|
|
787
|
+
const portalContext = usePortalContext(CONTENT_NAME, props?.__scopeMenu);
|
|
788
|
+
const {
|
|
789
|
+
forceMount = portalContext.forceMount,
|
|
790
|
+
align = "start",
|
|
791
|
+
ref: forwardedRef,
|
|
792
|
+
...subContentProps
|
|
793
|
+
} = props ?? {};
|
|
794
|
+
const context = useMenuContext(CONTENT_NAME, props?.__scopeMenu);
|
|
795
|
+
const rootContext = useMenuRootContext(CONTENT_NAME, props?.__scopeMenu);
|
|
796
|
+
const subContext = useMenuSubContext(SUB_CONTENT_NAME, props?.__scopeMenu);
|
|
797
|
+
const ref = (0, import_octane.useRef)(null, (0, import_internal.subSlot)(slot, "ref"));
|
|
798
|
+
const composedRefs = (0, import_compose_refs.useComposedRefs)(forwardedRef, ref, (0, import_internal.subSlot)(slot, "refs"));
|
|
799
|
+
return (0, import_octane.createElement)(Collection.Provider, {
|
|
800
|
+
scope: props?.__scopeMenu,
|
|
801
|
+
children: (0, import_octane.createElement)(import_Presence.Presence, {
|
|
802
|
+
present: forceMount || context.open,
|
|
803
|
+
children: (0, import_octane.createElement)(Collection.Slot, {
|
|
804
|
+
scope: props?.__scopeMenu,
|
|
805
|
+
children: (0, import_octane.createElement)(MenuContentImpl, {
|
|
806
|
+
id: subContext.contentId,
|
|
807
|
+
"aria-labelledby": subContext.triggerId,
|
|
808
|
+
...subContentProps,
|
|
809
|
+
__scopeMenu: props?.__scopeMenu,
|
|
810
|
+
ref: composedRefs,
|
|
811
|
+
align,
|
|
812
|
+
side: rootContext.dir === "rtl" ? "left" : "right",
|
|
813
|
+
disableOutsidePointerEvents: false,
|
|
814
|
+
disableOutsideScroll: false,
|
|
815
|
+
trapFocus: false,
|
|
816
|
+
onOpenAutoFocus: (event) => {
|
|
817
|
+
if (rootContext.isUsingKeyboardRef.current) ref.current?.focus();
|
|
818
|
+
event.preventDefault();
|
|
819
|
+
},
|
|
820
|
+
// The menu might close because of focusing another menu item in the parent
|
|
821
|
+
// menu. We don't want it to refocus the trigger in that case so we handle
|
|
822
|
+
// trigger focus ourselves.
|
|
823
|
+
onCloseAutoFocus: (event) => event.preventDefault(),
|
|
824
|
+
onFocusOutside: (0, import_compose_event_handlers.composeEventHandlers)(props?.onFocusOutside, (event) => {
|
|
825
|
+
if (event.target !== subContext.trigger) context.onOpenChange(false);
|
|
826
|
+
}),
|
|
827
|
+
onEscapeKeyDown: (0, import_compose_event_handlers.composeEventHandlers)(props?.onEscapeKeyDown, (event) => {
|
|
828
|
+
rootContext.onClose();
|
|
829
|
+
event.preventDefault();
|
|
830
|
+
}),
|
|
831
|
+
onKeyDown: (0, import_compose_event_handlers.composeEventHandlers)(props?.onKeyDown, (event) => {
|
|
832
|
+
const isKeyDownInside = event.currentTarget.contains(
|
|
833
|
+
event.target
|
|
834
|
+
);
|
|
835
|
+
const isCloseKey = SUB_CLOSE_KEYS[rootContext.dir].includes(event.key);
|
|
836
|
+
if (isKeyDownInside && isCloseKey) {
|
|
837
|
+
context.onOpenChange(false);
|
|
838
|
+
subContext.trigger?.focus();
|
|
839
|
+
event.preventDefault();
|
|
840
|
+
}
|
|
841
|
+
})
|
|
842
|
+
})
|
|
843
|
+
})
|
|
844
|
+
})
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
function getOpenState(open) {
|
|
848
|
+
return open ? "open" : "closed";
|
|
849
|
+
}
|
|
850
|
+
function isIndeterminate(checked) {
|
|
851
|
+
return checked === "indeterminate";
|
|
852
|
+
}
|
|
853
|
+
function getCheckedState(checked) {
|
|
854
|
+
return isIndeterminate(checked) ? "indeterminate" : checked ? "checked" : "unchecked";
|
|
855
|
+
}
|
|
856
|
+
function focusFirst(candidates) {
|
|
857
|
+
const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement;
|
|
858
|
+
for (const candidate of candidates) {
|
|
859
|
+
if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return;
|
|
860
|
+
candidate.focus();
|
|
861
|
+
if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
function wrapArray(array, startIndex) {
|
|
865
|
+
return array.map((_, index) => array[(startIndex + index) % array.length]);
|
|
866
|
+
}
|
|
867
|
+
function getNextMatch(values, search, currentMatch) {
|
|
868
|
+
const isRepeated = search.length > 1 && Array.from(search).every((char) => char === search[0]);
|
|
869
|
+
const normalizedSearch = isRepeated ? search[0] : search;
|
|
870
|
+
const currentMatchIndex = currentMatch ? values.indexOf(currentMatch) : -1;
|
|
871
|
+
let wrappedValues = wrapArray(values, Math.max(currentMatchIndex, 0));
|
|
872
|
+
const excludeCurrentMatch = normalizedSearch.length === 1;
|
|
873
|
+
if (excludeCurrentMatch) wrappedValues = wrappedValues.filter((v) => v !== currentMatch);
|
|
874
|
+
const nextMatch = wrappedValues.find(
|
|
875
|
+
(value) => value.toLowerCase().startsWith(normalizedSearch.toLowerCase())
|
|
876
|
+
);
|
|
877
|
+
return nextMatch !== currentMatch ? nextMatch : void 0;
|
|
878
|
+
}
|
|
879
|
+
function isPointInPolygon(point, polygon) {
|
|
880
|
+
const { x, y } = point;
|
|
881
|
+
let inside = false;
|
|
882
|
+
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
|
883
|
+
const ii = polygon[i];
|
|
884
|
+
const jj = polygon[j];
|
|
885
|
+
const xi = ii.x;
|
|
886
|
+
const yi = ii.y;
|
|
887
|
+
const xj = jj.x;
|
|
888
|
+
const yj = jj.y;
|
|
889
|
+
const intersect = yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi;
|
|
890
|
+
if (intersect) inside = !inside;
|
|
891
|
+
}
|
|
892
|
+
return inside;
|
|
893
|
+
}
|
|
894
|
+
function isPointerInGraceArea(event, area) {
|
|
895
|
+
if (!area) return false;
|
|
896
|
+
const cursorPos = { x: event.clientX, y: event.clientY };
|
|
897
|
+
return isPointInPolygon(cursorPos, area);
|
|
898
|
+
}
|
|
899
|
+
function whenMouse(handler) {
|
|
900
|
+
return (event) => event.pointerType === "mouse" ? handler(event) : void 0;
|
|
901
|
+
}
|
|
902
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
903
|
+
0 && (module.exports = {
|
|
904
|
+
Anchor,
|
|
905
|
+
Arrow,
|
|
906
|
+
CheckboxItem,
|
|
907
|
+
Content,
|
|
908
|
+
Group,
|
|
909
|
+
Item,
|
|
910
|
+
ItemIndicator,
|
|
911
|
+
Label,
|
|
912
|
+
Menu,
|
|
913
|
+
MenuAnchor,
|
|
914
|
+
MenuContent,
|
|
915
|
+
MenuItem,
|
|
916
|
+
Portal,
|
|
917
|
+
RadioGroup,
|
|
918
|
+
RadioItem,
|
|
919
|
+
Root,
|
|
920
|
+
Separator,
|
|
921
|
+
Sub,
|
|
922
|
+
SubContent,
|
|
923
|
+
SubTrigger,
|
|
924
|
+
createMenuScope
|
|
925
|
+
});
|