@vuu-ui/vuu-popups 0.6.14-debug → 0.6.15-debug
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/cjs/index.js.map +7 -0
- package/esm/index.js +1297 -0
- package/esm/index.js.map +7 -0
- package/package.json +4 -5
package/esm/index.js
ADDED
|
@@ -0,0 +1,1297 @@
|
|
|
1
|
+
// src/dialog/Dialog.tsx
|
|
2
|
+
import { Scrim, Toolbar, ToolbarButton } from "@heswell/salt-lab";
|
|
3
|
+
import { Text } from "@salt-ds/core";
|
|
4
|
+
import cx from "classnames";
|
|
5
|
+
import { useCallback, useRef, useState } from "react";
|
|
6
|
+
|
|
7
|
+
// src/portal/Portal.tsx
|
|
8
|
+
import { useLayoutEffect, useMemo } from "react";
|
|
9
|
+
import * as ReactDOM2 from "react-dom";
|
|
10
|
+
|
|
11
|
+
// src/portal/render-portal.tsx
|
|
12
|
+
import * as ReactDOM from "react-dom";
|
|
13
|
+
import { SaltProvider } from "@salt-ds/core";
|
|
14
|
+
import { jsx } from "react/jsx-runtime";
|
|
15
|
+
var containerId = 1;
|
|
16
|
+
var getPortalContainer = (x = 0, y = 0, win = window) => {
|
|
17
|
+
const el = win.document.createElement("div");
|
|
18
|
+
el.className = "vuuPopup " + containerId++;
|
|
19
|
+
el.style.cssText = `left:${x}px; top:${y}px;`;
|
|
20
|
+
win.document.body.appendChild(el);
|
|
21
|
+
return el;
|
|
22
|
+
};
|
|
23
|
+
var createDOMContainer = (x, y) => getPortalContainer(x, y);
|
|
24
|
+
var renderPortal = (component, container, x, y, onRender) => {
|
|
25
|
+
container.style.cssText = `left:${x}px; top:${y}px;position: absolute;`;
|
|
26
|
+
ReactDOM.render(
|
|
27
|
+
/* @__PURE__ */ jsx(SaltProvider, { applyClassesTo: "child", children: component }),
|
|
28
|
+
container,
|
|
29
|
+
onRender
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
var createContainer = createDOMContainer;
|
|
33
|
+
|
|
34
|
+
// src/portal/Portal.tsx
|
|
35
|
+
var Portal = function Portal2({
|
|
36
|
+
children,
|
|
37
|
+
x = 0,
|
|
38
|
+
y = 0,
|
|
39
|
+
onRender
|
|
40
|
+
}) {
|
|
41
|
+
const renderContainer = useMemo(() => {
|
|
42
|
+
return createContainer();
|
|
43
|
+
}, []);
|
|
44
|
+
useLayoutEffect(() => {
|
|
45
|
+
renderPortal(children, renderContainer, x, y, onRender);
|
|
46
|
+
}, [children, onRender, renderContainer, x, y]);
|
|
47
|
+
useLayoutEffect(() => {
|
|
48
|
+
return () => {
|
|
49
|
+
var _a;
|
|
50
|
+
if (renderContainer) {
|
|
51
|
+
ReactDOM2.unmountComponentAtNode(renderContainer);
|
|
52
|
+
if (renderContainer.classList.contains("vuuPopup")) {
|
|
53
|
+
(_a = renderContainer.parentElement) == null ? void 0 : _a.removeChild(renderContainer);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}, [renderContainer]);
|
|
58
|
+
return null;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// src/portal/portal-utils.ts
|
|
62
|
+
var installTheme = (themeId) => {
|
|
63
|
+
const installedThemes = getComputedStyle(document.body).getPropertyValue(
|
|
64
|
+
"--installed-themes"
|
|
65
|
+
);
|
|
66
|
+
document.body.style.setProperty(
|
|
67
|
+
"--installed-themes",
|
|
68
|
+
`${installedThemes} ${themeId}`
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// src/dialog/Dialog.tsx
|
|
73
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
74
|
+
var classBase = "vuuDialog";
|
|
75
|
+
var Dialog = ({
|
|
76
|
+
children,
|
|
77
|
+
className,
|
|
78
|
+
isOpen = false,
|
|
79
|
+
onClose,
|
|
80
|
+
title,
|
|
81
|
+
...props
|
|
82
|
+
}) => {
|
|
83
|
+
const root = useRef(null);
|
|
84
|
+
const [posX] = useState(0);
|
|
85
|
+
const [posY] = useState(0);
|
|
86
|
+
const close = useCallback(() => {
|
|
87
|
+
onClose == null ? void 0 : onClose();
|
|
88
|
+
}, [onClose]);
|
|
89
|
+
const handleRender = useCallback(() => {
|
|
90
|
+
}, []);
|
|
91
|
+
if (!isOpen) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
return /* @__PURE__ */ jsx2(Portal, { onRender: handleRender, x: posX, y: posY, children: /* @__PURE__ */ jsx2(Scrim, { className: `${classBase}-scrim`, open: isOpen, children: /* @__PURE__ */ jsxs("div", { ...props, className: cx(classBase, className), ref: root, children: [
|
|
95
|
+
/* @__PURE__ */ jsxs(Toolbar, { className: `${classBase}-header`, children: [
|
|
96
|
+
/* @__PURE__ */ jsx2(Text, { children: title }),
|
|
97
|
+
/* @__PURE__ */ jsx2(
|
|
98
|
+
ToolbarButton,
|
|
99
|
+
{
|
|
100
|
+
onClick: close,
|
|
101
|
+
"data-align-end": true,
|
|
102
|
+
"data-icon": "close"
|
|
103
|
+
},
|
|
104
|
+
"close"
|
|
105
|
+
)
|
|
106
|
+
] }),
|
|
107
|
+
children
|
|
108
|
+
] }) }) });
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// src/menu/ContextMenu.tsx
|
|
112
|
+
import { useIdMemo as useId2 } from "@salt-ds/core";
|
|
113
|
+
import { useCallback as useCallback5, useRef as useRef5 } from "react";
|
|
114
|
+
|
|
115
|
+
// src/menu/MenuList.tsx
|
|
116
|
+
import React2, {
|
|
117
|
+
useLayoutEffect as useLayoutEffect2,
|
|
118
|
+
useMemo as useMemo4,
|
|
119
|
+
useRef as useRef3
|
|
120
|
+
} from "react";
|
|
121
|
+
import cx2 from "classnames";
|
|
122
|
+
import { useIdMemo as useId } from "@salt-ds/core";
|
|
123
|
+
|
|
124
|
+
// src/menu/use-keyboard-navigation.ts
|
|
125
|
+
import {
|
|
126
|
+
useCallback as useCallback2,
|
|
127
|
+
useMemo as useMemo2,
|
|
128
|
+
useRef as useRef2,
|
|
129
|
+
useState as useState2
|
|
130
|
+
} from "react";
|
|
131
|
+
|
|
132
|
+
// src/menu/utils.ts
|
|
133
|
+
var isRoot = (el) => el.closest(`[data-root='true']`) !== null;
|
|
134
|
+
var hasPopup = (el, idx) => {
|
|
135
|
+
var _a;
|
|
136
|
+
return el.ariaHasPopup === "true" && ((_a = el.dataset) == null ? void 0 : _a.idx) === `${idx}` || el.querySelector(`:scope > [data-idx='${idx}'][aria-haspopup='true']`) !== null;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// src/menu/key-code.ts
|
|
140
|
+
function union(set1, ...sets) {
|
|
141
|
+
const result = new Set(set1);
|
|
142
|
+
for (const set of sets) {
|
|
143
|
+
for (const element of set) {
|
|
144
|
+
result.add(element);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
var Enter = "Enter";
|
|
150
|
+
var Delete = "Delete";
|
|
151
|
+
var actionKeys = /* @__PURE__ */ new Set([Enter, Delete]);
|
|
152
|
+
var focusKeys = /* @__PURE__ */ new Set(["Tab"]);
|
|
153
|
+
var arrowLeftRightKeys = /* @__PURE__ */ new Set(["ArrowRight", "ArrowLeft"]);
|
|
154
|
+
var verticalNavigationKeys = /* @__PURE__ */ new Set(["Home", "End", "ArrowDown", "ArrowUp"]);
|
|
155
|
+
var horizontalNavigationKeys = /* @__PURE__ */ new Set([
|
|
156
|
+
"Home",
|
|
157
|
+
"End",
|
|
158
|
+
"ArrowRight",
|
|
159
|
+
"ArrowLeft"
|
|
160
|
+
]);
|
|
161
|
+
var functionKeys = /* @__PURE__ */ new Set([
|
|
162
|
+
"F1",
|
|
163
|
+
"F2",
|
|
164
|
+
"F3",
|
|
165
|
+
"F4",
|
|
166
|
+
"F5",
|
|
167
|
+
"F6",
|
|
168
|
+
"F7",
|
|
169
|
+
"F8",
|
|
170
|
+
"F9",
|
|
171
|
+
"F10",
|
|
172
|
+
"F11",
|
|
173
|
+
"F12"
|
|
174
|
+
]);
|
|
175
|
+
var specialKeys = union(
|
|
176
|
+
actionKeys,
|
|
177
|
+
horizontalNavigationKeys,
|
|
178
|
+
verticalNavigationKeys,
|
|
179
|
+
arrowLeftRightKeys,
|
|
180
|
+
functionKeys,
|
|
181
|
+
focusKeys
|
|
182
|
+
);
|
|
183
|
+
var isNavigationKey = ({ key }, orientation = "vertical") => {
|
|
184
|
+
const navigationKeys = orientation === "vertical" ? verticalNavigationKeys : horizontalNavigationKeys;
|
|
185
|
+
return navigationKeys.has(key);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// src/menu/use-keyboard-navigation.ts
|
|
189
|
+
var useKeyboardNavigation = ({
|
|
190
|
+
autoHighlightFirstItem = false,
|
|
191
|
+
count,
|
|
192
|
+
highlightedIndex: highlightedIndexProp,
|
|
193
|
+
onActivate,
|
|
194
|
+
onHighlight,
|
|
195
|
+
// onKeyDown,
|
|
196
|
+
onCloseMenu,
|
|
197
|
+
onOpenMenu
|
|
198
|
+
}) => {
|
|
199
|
+
const highlightedIndexRef = useRef2(
|
|
200
|
+
(highlightedIndexProp != null ? highlightedIndexProp : autoHighlightFirstItem) ? 0 : -1
|
|
201
|
+
);
|
|
202
|
+
const [, forceRender] = useState2(null);
|
|
203
|
+
const controlledHighlighting = highlightedIndexProp !== void 0;
|
|
204
|
+
const setHighlightedIdx = useCallback2(
|
|
205
|
+
(idx) => {
|
|
206
|
+
highlightedIndexRef.current = idx;
|
|
207
|
+
onHighlight == null ? void 0 : onHighlight(idx);
|
|
208
|
+
forceRender({});
|
|
209
|
+
},
|
|
210
|
+
[onHighlight]
|
|
211
|
+
);
|
|
212
|
+
const setHighlightedIndex = useCallback2(
|
|
213
|
+
(idx) => {
|
|
214
|
+
if (idx !== highlightedIndexRef.current) {
|
|
215
|
+
if (!controlledHighlighting) {
|
|
216
|
+
setHighlightedIdx(idx);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
[controlledHighlighting, setHighlightedIdx]
|
|
221
|
+
);
|
|
222
|
+
const keyBoardNavigation = useRef2(true);
|
|
223
|
+
const ignoreFocus = useRef2(false);
|
|
224
|
+
const setIgnoreFocus = (value) => ignoreFocus.current = value;
|
|
225
|
+
const highlightedIndex = controlledHighlighting ? highlightedIndexProp : highlightedIndexRef.current;
|
|
226
|
+
const navigateChildldItems = useCallback2(
|
|
227
|
+
(e) => {
|
|
228
|
+
const nextIdx = nextItemIdx(count, e.key, highlightedIndexRef.current);
|
|
229
|
+
if (nextIdx !== highlightedIndexRef.current) {
|
|
230
|
+
setHighlightedIndex(nextIdx);
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
[count, setHighlightedIndex]
|
|
234
|
+
);
|
|
235
|
+
const handleKeyDown = useCallback2(
|
|
236
|
+
(e) => {
|
|
237
|
+
if (isNavigationKey(e)) {
|
|
238
|
+
e.preventDefault();
|
|
239
|
+
e.stopPropagation();
|
|
240
|
+
keyBoardNavigation.current = true;
|
|
241
|
+
navigateChildldItems(e);
|
|
242
|
+
} else if ((e.key === "ArrowRight" || e.key === "Enter") && hasPopup(e.target, highlightedIndex)) {
|
|
243
|
+
onOpenMenu(highlightedIndex);
|
|
244
|
+
} else if (e.key === "ArrowLeft" && !isRoot(e.target)) {
|
|
245
|
+
onCloseMenu(highlightedIndex);
|
|
246
|
+
} else if (e.key === "Enter") {
|
|
247
|
+
onActivate && onActivate(highlightedIndex);
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
[
|
|
251
|
+
highlightedIndex,
|
|
252
|
+
navigateChildldItems,
|
|
253
|
+
onActivate,
|
|
254
|
+
onCloseMenu,
|
|
255
|
+
onOpenMenu
|
|
256
|
+
]
|
|
257
|
+
);
|
|
258
|
+
const listProps = useMemo2(
|
|
259
|
+
() => ({
|
|
260
|
+
onFocus: () => {
|
|
261
|
+
if (highlightedIndex === -1) {
|
|
262
|
+
setHighlightedIdx(0);
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
onKeyDown: handleKeyDown,
|
|
266
|
+
onMouseDownCapture: () => {
|
|
267
|
+
keyBoardNavigation.current = false;
|
|
268
|
+
setIgnoreFocus(true);
|
|
269
|
+
},
|
|
270
|
+
// onMouseEnter would seem less expensive but it misses some cases
|
|
271
|
+
onMouseMove: () => {
|
|
272
|
+
if (keyBoardNavigation.current) {
|
|
273
|
+
keyBoardNavigation.current = false;
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
onMouseLeave: () => {
|
|
277
|
+
keyBoardNavigation.current = true;
|
|
278
|
+
setIgnoreFocus(false);
|
|
279
|
+
setHighlightedIndex(-1);
|
|
280
|
+
}
|
|
281
|
+
}),
|
|
282
|
+
[
|
|
283
|
+
highlightedIndex,
|
|
284
|
+
setHighlightedIndex,
|
|
285
|
+
navigateChildldItems,
|
|
286
|
+
onActivate,
|
|
287
|
+
onCloseMenu,
|
|
288
|
+
onOpenMenu,
|
|
289
|
+
setHighlightedIdx
|
|
290
|
+
]
|
|
291
|
+
);
|
|
292
|
+
return {
|
|
293
|
+
focusVisible: keyBoardNavigation.current ? highlightedIndex : -1,
|
|
294
|
+
controlledHighlighting,
|
|
295
|
+
highlightedIndex,
|
|
296
|
+
setHighlightedIndex,
|
|
297
|
+
// keyBoardNavigation,
|
|
298
|
+
listProps,
|
|
299
|
+
setIgnoreFocus
|
|
300
|
+
};
|
|
301
|
+
};
|
|
302
|
+
function nextItemIdx(count, key, idx) {
|
|
303
|
+
if (key === "ArrowUp") {
|
|
304
|
+
if (idx > 0) {
|
|
305
|
+
return idx - 1;
|
|
306
|
+
} else {
|
|
307
|
+
return idx;
|
|
308
|
+
}
|
|
309
|
+
} else {
|
|
310
|
+
if (idx === null) {
|
|
311
|
+
return 0;
|
|
312
|
+
} else if (idx === count - 1) {
|
|
313
|
+
return idx;
|
|
314
|
+
} else {
|
|
315
|
+
return idx + 1;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/menu/use-items-with-ids.ts
|
|
321
|
+
import React, { useCallback as useCallback3, useMemo as useMemo3 } from "react";
|
|
322
|
+
var isMenuItemGroup = (child) => child.type === MenuItemGroup || !!child.props["data-group"];
|
|
323
|
+
var useItemsWithIds = (childrenProp) => {
|
|
324
|
+
const normalizeChildren = useCallback3(() => {
|
|
325
|
+
const collectChildren = (children, path = "root", menus2 = {}, actions2 = {}) => {
|
|
326
|
+
const list = menus2[path] = [];
|
|
327
|
+
let idx = 0;
|
|
328
|
+
let hasSeparator = false;
|
|
329
|
+
React.Children.forEach(children, (child) => {
|
|
330
|
+
if (child.type === Separator) {
|
|
331
|
+
hasSeparator = true;
|
|
332
|
+
} else {
|
|
333
|
+
const group = isMenuItemGroup(child);
|
|
334
|
+
const childPath = path === "root" ? `${idx}` : `${path}.${idx}`;
|
|
335
|
+
const {
|
|
336
|
+
props: { action, options }
|
|
337
|
+
} = child;
|
|
338
|
+
const { childWithId, grandChildren } = assignId(
|
|
339
|
+
child,
|
|
340
|
+
childPath,
|
|
341
|
+
group,
|
|
342
|
+
hasSeparator
|
|
343
|
+
);
|
|
344
|
+
list.push(childWithId);
|
|
345
|
+
if (grandChildren) {
|
|
346
|
+
collectChildren(grandChildren, childPath, menus2, actions2);
|
|
347
|
+
} else {
|
|
348
|
+
actions2[childPath] = { action, options };
|
|
349
|
+
}
|
|
350
|
+
idx += 1;
|
|
351
|
+
hasSeparator = false;
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
return [menus2, actions2];
|
|
355
|
+
};
|
|
356
|
+
const assignId = (child, path, group, hasSeparator = false) => {
|
|
357
|
+
const {
|
|
358
|
+
props: { children }
|
|
359
|
+
} = child;
|
|
360
|
+
return {
|
|
361
|
+
childWithId: React.cloneElement(child, {
|
|
362
|
+
hasSeparator,
|
|
363
|
+
id: `${path}`,
|
|
364
|
+
key: path,
|
|
365
|
+
children: group ? void 0 : children
|
|
366
|
+
}),
|
|
367
|
+
grandChildren: group ? children : void 0
|
|
368
|
+
};
|
|
369
|
+
};
|
|
370
|
+
return collectChildren(childrenProp);
|
|
371
|
+
}, [childrenProp]);
|
|
372
|
+
const [menus, actions] = useMemo3(
|
|
373
|
+
() => normalizeChildren(),
|
|
374
|
+
[normalizeChildren]
|
|
375
|
+
);
|
|
376
|
+
return [menus, actions];
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
// src/menu/MenuList.tsx
|
|
380
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
381
|
+
var classBase2 = "vuuMenuList";
|
|
382
|
+
var Separator = () => /* @__PURE__ */ jsx3("li", { className: "vuuMenuItem-divider" });
|
|
383
|
+
var MenuItemGroup = () => null;
|
|
384
|
+
var MenuItem = ({ children, idx, ...props }) => {
|
|
385
|
+
return /* @__PURE__ */ jsx3("div", { ...props, children });
|
|
386
|
+
};
|
|
387
|
+
var hasIcon = (child) => child.props["data-icon"];
|
|
388
|
+
var MenuList = ({
|
|
389
|
+
activatedByKeyboard,
|
|
390
|
+
childMenuShowing = -1,
|
|
391
|
+
children,
|
|
392
|
+
className,
|
|
393
|
+
highlightedIdx: highlightedIdxProp,
|
|
394
|
+
id: idProp,
|
|
395
|
+
isRoot: isRoot2,
|
|
396
|
+
listItemProps,
|
|
397
|
+
menuId,
|
|
398
|
+
onHighlightMenuItem,
|
|
399
|
+
onActivate,
|
|
400
|
+
onCloseMenu,
|
|
401
|
+
onOpenMenu,
|
|
402
|
+
...props
|
|
403
|
+
}) => {
|
|
404
|
+
const id = useId(idProp);
|
|
405
|
+
const root = useRef3(null);
|
|
406
|
+
const mapIdxToId = useMemo4(() => /* @__PURE__ */ new Map(), []);
|
|
407
|
+
const handleOpenMenu = (idx) => {
|
|
408
|
+
var _a;
|
|
409
|
+
const el = (_a = root.current) == null ? void 0 : _a.querySelector(`:scope > [data-idx='${idx}']`);
|
|
410
|
+
(el == null ? void 0 : el.id) && (onOpenMenu == null ? void 0 : onOpenMenu(el.id));
|
|
411
|
+
};
|
|
412
|
+
const handleActivate = (idx) => {
|
|
413
|
+
var _a;
|
|
414
|
+
const el = (_a = root.current) == null ? void 0 : _a.querySelector(`:scope > [data-idx='${idx}']`);
|
|
415
|
+
(el == null ? void 0 : el.id) && (onActivate == null ? void 0 : onActivate(el.id));
|
|
416
|
+
};
|
|
417
|
+
const { focusVisible, highlightedIndex, listProps } = useKeyboardNavigation({
|
|
418
|
+
count: React2.Children.count(children),
|
|
419
|
+
highlightedIndex: highlightedIdxProp,
|
|
420
|
+
onActivate: handleActivate,
|
|
421
|
+
onHighlight: onHighlightMenuItem,
|
|
422
|
+
onOpenMenu: handleOpenMenu,
|
|
423
|
+
onCloseMenu
|
|
424
|
+
});
|
|
425
|
+
const appliedFocusVisible = childMenuShowing == -1 ? focusVisible : -1;
|
|
426
|
+
useLayoutEffect2(() => {
|
|
427
|
+
var _a;
|
|
428
|
+
if (childMenuShowing === -1 && activatedByKeyboard) {
|
|
429
|
+
(_a = root.current) == null ? void 0 : _a.focus();
|
|
430
|
+
}
|
|
431
|
+
}, [activatedByKeyboard, childMenuShowing]);
|
|
432
|
+
const getActiveDescendant = () => highlightedIndex === void 0 || highlightedIndex === -1 ? void 0 : mapIdxToId.get(highlightedIndex);
|
|
433
|
+
return /* @__PURE__ */ jsx3(
|
|
434
|
+
"div",
|
|
435
|
+
{
|
|
436
|
+
...props,
|
|
437
|
+
...listProps,
|
|
438
|
+
"aria-activedescendant": getActiveDescendant(),
|
|
439
|
+
className: cx2(classBase2, className, {
|
|
440
|
+
[`${classBase2}-childMenuShowing`]: childMenuShowing !== -1
|
|
441
|
+
}),
|
|
442
|
+
"data-root": isRoot2 || void 0,
|
|
443
|
+
id: `${id}-${menuId}`,
|
|
444
|
+
ref: root,
|
|
445
|
+
role: "menu",
|
|
446
|
+
tabIndex: 0,
|
|
447
|
+
children: renderContent()
|
|
448
|
+
}
|
|
449
|
+
);
|
|
450
|
+
function renderContent() {
|
|
451
|
+
const propsCommonToAllListItems = {
|
|
452
|
+
...listItemProps,
|
|
453
|
+
role: "menuitem"
|
|
454
|
+
};
|
|
455
|
+
const maybeIcon = (childElement, withIcon, iconName) => withIcon ? [
|
|
456
|
+
/* @__PURE__ */ jsx3(
|
|
457
|
+
"span",
|
|
458
|
+
{
|
|
459
|
+
className: "vuuIconContainer",
|
|
460
|
+
"data-icon": iconName
|
|
461
|
+
},
|
|
462
|
+
"icon"
|
|
463
|
+
)
|
|
464
|
+
].concat(childElement) : childElement;
|
|
465
|
+
function addClonedChild(list, child, idx, withIcon) {
|
|
466
|
+
var _a;
|
|
467
|
+
const {
|
|
468
|
+
children: children2,
|
|
469
|
+
className: className2,
|
|
470
|
+
"data-icon": iconName,
|
|
471
|
+
id: itemId,
|
|
472
|
+
hasSeparator,
|
|
473
|
+
label,
|
|
474
|
+
...props2
|
|
475
|
+
} = child.props;
|
|
476
|
+
const hasSubMenu = isMenuItemGroup(child);
|
|
477
|
+
const subMenuShowing = hasSubMenu && childMenuShowing === idx;
|
|
478
|
+
const ariaControls = subMenuShowing ? `${id}-${itemId}` : void 0;
|
|
479
|
+
list.push(
|
|
480
|
+
/* @__PURE__ */ jsx3(
|
|
481
|
+
MenuItem,
|
|
482
|
+
{
|
|
483
|
+
...props2,
|
|
484
|
+
...propsCommonToAllListItems,
|
|
485
|
+
...getMenuItemProps(
|
|
486
|
+
`${id}-${menuId}`,
|
|
487
|
+
itemId,
|
|
488
|
+
idx,
|
|
489
|
+
(_a = child.key) != null ? _a : itemId,
|
|
490
|
+
highlightedIndex,
|
|
491
|
+
appliedFocusVisible,
|
|
492
|
+
className2,
|
|
493
|
+
hasSeparator
|
|
494
|
+
),
|
|
495
|
+
"aria-controls": ariaControls,
|
|
496
|
+
"aria-haspopup": hasSubMenu || void 0,
|
|
497
|
+
"aria-expanded": subMenuShowing || void 0,
|
|
498
|
+
children: hasSubMenu ? maybeIcon(label, withIcon, iconName) : maybeIcon(children2, withIcon, iconName)
|
|
499
|
+
}
|
|
500
|
+
)
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
const listItems = [];
|
|
504
|
+
if (children.length > 0) {
|
|
505
|
+
const withIcon = children.some(hasIcon);
|
|
506
|
+
children.forEach((child, idx) => {
|
|
507
|
+
addClonedChild(listItems, child, idx, withIcon);
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
return listItems;
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
var getMenuItemProps = (baseId, itemId, idx, key, highlightedIdx, focusVisible, className, hasSeparator) => ({
|
|
514
|
+
id: `${baseId}-${itemId}`,
|
|
515
|
+
key: key != null ? key : idx,
|
|
516
|
+
"data-idx": idx,
|
|
517
|
+
"data-highlighted": idx === highlightedIdx || void 0,
|
|
518
|
+
className: cx2("vuuMenuItem", className, {
|
|
519
|
+
"vuuMenuItem-separator": hasSeparator,
|
|
520
|
+
focusVisible: focusVisible === idx
|
|
521
|
+
})
|
|
522
|
+
});
|
|
523
|
+
MenuList.displayName = "MenuList";
|
|
524
|
+
var MenuList_default = MenuList;
|
|
525
|
+
|
|
526
|
+
// src/menu/use-cascade.ts
|
|
527
|
+
import {
|
|
528
|
+
useCallback as useCallback4,
|
|
529
|
+
useMemo as useMemo5,
|
|
530
|
+
useRef as useRef4,
|
|
531
|
+
useState as useState3
|
|
532
|
+
} from "react";
|
|
533
|
+
|
|
534
|
+
// src/menu/list-dom-utils.ts
|
|
535
|
+
function listItemIndex(listItemEl) {
|
|
536
|
+
if (listItemEl) {
|
|
537
|
+
const idx = listItemEl.dataset.idx;
|
|
538
|
+
if (idx) {
|
|
539
|
+
return parseInt(idx, 10);
|
|
540
|
+
} else if (listItemEl.ariaPosInSet) {
|
|
541
|
+
return parseInt(listItemEl.ariaPosInSet, 10) - 1;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
var closestListItem = (el) => el == null ? void 0 : el.closest("[data-idx],[aria-posinset]");
|
|
546
|
+
|
|
547
|
+
// src/menu/use-cascade.ts
|
|
548
|
+
var nudge = (menus, distance, pos) => {
|
|
549
|
+
return menus.map(
|
|
550
|
+
(m, i) => i === menus.length - 1 ? {
|
|
551
|
+
...m,
|
|
552
|
+
[pos]: m[pos] - distance
|
|
553
|
+
} : m
|
|
554
|
+
);
|
|
555
|
+
};
|
|
556
|
+
var nudgeLeft = (menus, distance) => nudge(menus, distance, "left");
|
|
557
|
+
var nudgeUp = (menus, distance) => nudge(menus, distance, "top");
|
|
558
|
+
var flipSides = (id, menus) => {
|
|
559
|
+
const [parentMenu, menu] = menus.slice(-2);
|
|
560
|
+
const el = document.getElementById(`${id}-${menu.id}`);
|
|
561
|
+
if (el === null) {
|
|
562
|
+
throw Error(`useCascade.flipSides element with id ${menu.id} not found`);
|
|
563
|
+
}
|
|
564
|
+
const { width } = el.getBoundingClientRect();
|
|
565
|
+
return menus.map(
|
|
566
|
+
(m) => m === menu ? {
|
|
567
|
+
...m,
|
|
568
|
+
left: parentMenu.left - (width - 2)
|
|
569
|
+
} : m
|
|
570
|
+
);
|
|
571
|
+
};
|
|
572
|
+
var getPosition = (el, openMenus) => {
|
|
573
|
+
const [{ left, top: menuTop }] = openMenus.slice(-1);
|
|
574
|
+
const { offsetWidth: width, offsetTop: top } = el;
|
|
575
|
+
return { left: left + width, top: top + menuTop };
|
|
576
|
+
};
|
|
577
|
+
var getItemId = (id) => {
|
|
578
|
+
const pos = id.lastIndexOf("-");
|
|
579
|
+
return pos === -1 ? id : id.slice(pos + 1);
|
|
580
|
+
};
|
|
581
|
+
var getMenuId = (id) => {
|
|
582
|
+
const itemId = getItemId(id);
|
|
583
|
+
const pos = itemId.lastIndexOf(".");
|
|
584
|
+
return pos > -1 ? itemId.slice(0, pos) : "root";
|
|
585
|
+
};
|
|
586
|
+
var getMenuDepth = (id) => {
|
|
587
|
+
let count = 0, pos = id.indexOf(".", 0);
|
|
588
|
+
while (pos !== -1) {
|
|
589
|
+
count += 1;
|
|
590
|
+
pos = id.indexOf(".", pos + 1);
|
|
591
|
+
}
|
|
592
|
+
return count;
|
|
593
|
+
};
|
|
594
|
+
var identifyItem = (el) => ({
|
|
595
|
+
menuId: getMenuId(el.id),
|
|
596
|
+
itemId: getItemId(el.id),
|
|
597
|
+
isGroup: el.ariaHasPopup === "true",
|
|
598
|
+
isOpen: el.ariaExpanded === "true",
|
|
599
|
+
level: getMenuDepth(el.id)
|
|
600
|
+
});
|
|
601
|
+
var useCascade = ({
|
|
602
|
+
id,
|
|
603
|
+
onActivate,
|
|
604
|
+
onMouseEnterItem,
|
|
605
|
+
position: { x: posX, y: posY }
|
|
606
|
+
}) => {
|
|
607
|
+
const [, forceRefresh] = useState3({});
|
|
608
|
+
const openMenus = useRef4([
|
|
609
|
+
{ id: "root", left: posX, top: posY }
|
|
610
|
+
]);
|
|
611
|
+
const setOpenMenus = useCallback4((menus) => {
|
|
612
|
+
openMenus.current = menus;
|
|
613
|
+
forceRefresh({});
|
|
614
|
+
}, []);
|
|
615
|
+
const menuOpenPendingTimeout = useRef4();
|
|
616
|
+
const menuClosePendingTimeout = useRef4();
|
|
617
|
+
const menuState = useRef4({ root: "no-popup" });
|
|
618
|
+
const prevLevel = useRef4(0);
|
|
619
|
+
const openMenu = useCallback4(
|
|
620
|
+
(menuId = "root", itemId = null, listItemEl = null) => {
|
|
621
|
+
if (menuId === "root" && itemId === null) {
|
|
622
|
+
setOpenMenus([{ id: "root", left: posX, top: posY }]);
|
|
623
|
+
} else {
|
|
624
|
+
menuState.current[menuId] = "popup-open";
|
|
625
|
+
const doc = listItemEl ? listItemEl.ownerDocument : document;
|
|
626
|
+
const el = doc.getElementById(`${id}-${menuId}-${itemId}`);
|
|
627
|
+
const { left, top } = getPosition(el, openMenus.current);
|
|
628
|
+
setOpenMenus(openMenus.current.concat({ id: itemId, left, top }));
|
|
629
|
+
}
|
|
630
|
+
},
|
|
631
|
+
[id, posX, posY, setOpenMenus]
|
|
632
|
+
);
|
|
633
|
+
const closeMenu = useCallback4(
|
|
634
|
+
(menuId) => {
|
|
635
|
+
if (menuId === "root") {
|
|
636
|
+
setOpenMenus([]);
|
|
637
|
+
} else {
|
|
638
|
+
setOpenMenus(openMenus.current.slice(0, -1));
|
|
639
|
+
}
|
|
640
|
+
},
|
|
641
|
+
[setOpenMenus]
|
|
642
|
+
);
|
|
643
|
+
const closeMenus = useCallback4(
|
|
644
|
+
(menuId, itemId) => {
|
|
645
|
+
const menus = openMenus.current.slice();
|
|
646
|
+
let { id: lastMenuId } = menus[menus.length - 1];
|
|
647
|
+
while (menus.length > 1 && !itemId.startsWith(lastMenuId)) {
|
|
648
|
+
const parentMenuId = getMenuId(lastMenuId);
|
|
649
|
+
menus.pop();
|
|
650
|
+
menuState.current[lastMenuId] = "no-popup";
|
|
651
|
+
menuState.current[parentMenuId] = "no-popup";
|
|
652
|
+
({ id: lastMenuId } = menus[menus.length - 1]);
|
|
653
|
+
}
|
|
654
|
+
if (menus.length < openMenus.current.length) {
|
|
655
|
+
setOpenMenus(menus);
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
[setOpenMenus]
|
|
659
|
+
);
|
|
660
|
+
const scheduleOpen = useCallback4(
|
|
661
|
+
(menuId, itemId, listItemEl) => {
|
|
662
|
+
if (menuOpenPendingTimeout.current) {
|
|
663
|
+
clearTimeout(menuOpenPendingTimeout.current);
|
|
664
|
+
}
|
|
665
|
+
menuOpenPendingTimeout.current = window.setTimeout(() => {
|
|
666
|
+
console.log(`scheduleOpen timed out opening ${itemId}`);
|
|
667
|
+
closeMenus(menuId, itemId);
|
|
668
|
+
menuState.current[menuId] = "popup-open";
|
|
669
|
+
menuState.current[itemId] = "no-popup";
|
|
670
|
+
openMenu(menuId, itemId, listItemEl);
|
|
671
|
+
}, 400);
|
|
672
|
+
},
|
|
673
|
+
[closeMenus, openMenu]
|
|
674
|
+
);
|
|
675
|
+
const scheduleClose = useCallback4(
|
|
676
|
+
(openMenuId, menuId, itemId) => {
|
|
677
|
+
console.log(
|
|
678
|
+
`scheduleClose openMenuId ${openMenuId} menuId ${menuId} itemId ${itemId}`
|
|
679
|
+
);
|
|
680
|
+
menuState.current[openMenuId] = "pending-close";
|
|
681
|
+
menuClosePendingTimeout.current = window.setTimeout(() => {
|
|
682
|
+
closeMenus(menuId, itemId);
|
|
683
|
+
}, 400);
|
|
684
|
+
},
|
|
685
|
+
[closeMenus]
|
|
686
|
+
);
|
|
687
|
+
const handleRender = useCallback4(() => {
|
|
688
|
+
const { current: menus } = openMenus;
|
|
689
|
+
const [menu] = menus.slice(-1);
|
|
690
|
+
const el = document.getElementById(`${id}-${menu.id}`);
|
|
691
|
+
if (el) {
|
|
692
|
+
const { right, bottom } = el.getBoundingClientRect();
|
|
693
|
+
const { clientHeight, clientWidth } = document.body;
|
|
694
|
+
if (right > clientWidth) {
|
|
695
|
+
const newMenus = menus.length > 1 ? flipSides(id, menus) : nudgeLeft(menus, right - clientWidth);
|
|
696
|
+
setOpenMenus(newMenus);
|
|
697
|
+
} else if (bottom > clientHeight) {
|
|
698
|
+
const newMenus = nudgeUp(menus, bottom - clientHeight);
|
|
699
|
+
setOpenMenus(newMenus);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}, [id, setOpenMenus]);
|
|
703
|
+
const listItemProps = useMemo5(
|
|
704
|
+
() => ({
|
|
705
|
+
onMouseEnter: (evt) => {
|
|
706
|
+
const listItemEl = closestListItem(evt.target);
|
|
707
|
+
const { menuId, itemId, isGroup, isOpen, level } = identifyItem(listItemEl);
|
|
708
|
+
const sameLevel = prevLevel.current === level;
|
|
709
|
+
const {
|
|
710
|
+
current: { [menuId]: state }
|
|
711
|
+
} = menuState;
|
|
712
|
+
prevLevel.current = level;
|
|
713
|
+
if (state === "no-popup" && isGroup) {
|
|
714
|
+
menuState.current[menuId] = "popup-pending";
|
|
715
|
+
scheduleOpen(menuId, itemId, listItemEl);
|
|
716
|
+
} else if (state === "popup-pending" && !isGroup) {
|
|
717
|
+
menuState.current[menuId] = "no-popup";
|
|
718
|
+
clearTimeout(menuOpenPendingTimeout.current);
|
|
719
|
+
menuOpenPendingTimeout.current = void 0;
|
|
720
|
+
} else if (state === "popup-pending" && isGroup) {
|
|
721
|
+
clearTimeout(menuOpenPendingTimeout.current);
|
|
722
|
+
scheduleOpen(menuId, itemId, listItemEl);
|
|
723
|
+
} else if (state === "popup-open") {
|
|
724
|
+
const [{ id: parentMenuId }, { id: openMenuId }] = openMenus.current.slice(-2);
|
|
725
|
+
if (parentMenuId === menuId && menuState.current[openMenuId] !== "pending-close" && sameLevel) {
|
|
726
|
+
scheduleClose(openMenuId, menuId, itemId);
|
|
727
|
+
if (isGroup && !isOpen) {
|
|
728
|
+
scheduleOpen(menuId, itemId, listItemEl);
|
|
729
|
+
}
|
|
730
|
+
} else if (parentMenuId === menuId && isGroup && itemId !== openMenuId && menuState.current[openMenuId] === "pending-close") {
|
|
731
|
+
scheduleOpen(menuId, itemId, listItemEl);
|
|
732
|
+
} else if (isGroup) {
|
|
733
|
+
closeMenus(menuId, itemId);
|
|
734
|
+
scheduleOpen(menuId, itemId, listItemEl);
|
|
735
|
+
} else if (!(menuState.current[openMenuId] === "pending-close" && sameLevel)) {
|
|
736
|
+
closeMenus(menuId, itemId);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
if (state === "pending-close") {
|
|
740
|
+
if (menuOpenPendingTimeout.current) {
|
|
741
|
+
clearTimeout(menuOpenPendingTimeout.current);
|
|
742
|
+
menuOpenPendingTimeout.current = void 0;
|
|
743
|
+
}
|
|
744
|
+
clearTimeout(menuClosePendingTimeout.current);
|
|
745
|
+
menuClosePendingTimeout.current = void 0;
|
|
746
|
+
menuState.current[menuId] = "popup-open";
|
|
747
|
+
}
|
|
748
|
+
onMouseEnterItem(evt, itemId);
|
|
749
|
+
},
|
|
750
|
+
onClick: (evt) => {
|
|
751
|
+
const targetElement = evt.target;
|
|
752
|
+
const listItemEl = closestListItem(targetElement);
|
|
753
|
+
const idx = listItemIndex(listItemEl);
|
|
754
|
+
console.log(
|
|
755
|
+
`list item click [${idx}] hasPopup ${listItemEl.ariaHasPopup}`
|
|
756
|
+
);
|
|
757
|
+
if (listItemEl.ariaHasPopup === "true") {
|
|
758
|
+
if (listItemEl.ariaExpanded !== "true") {
|
|
759
|
+
openMenu(idx);
|
|
760
|
+
} else {
|
|
761
|
+
}
|
|
762
|
+
} else {
|
|
763
|
+
onActivate(getItemId(listItemEl.id));
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}),
|
|
767
|
+
[
|
|
768
|
+
closeMenus,
|
|
769
|
+
onActivate,
|
|
770
|
+
onMouseEnterItem,
|
|
771
|
+
openMenu,
|
|
772
|
+
scheduleClose,
|
|
773
|
+
scheduleOpen
|
|
774
|
+
]
|
|
775
|
+
);
|
|
776
|
+
return {
|
|
777
|
+
closeMenu,
|
|
778
|
+
handleRender,
|
|
779
|
+
listItemProps,
|
|
780
|
+
openMenu,
|
|
781
|
+
openMenus: openMenus.current
|
|
782
|
+
};
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
// src/menu/use-click-away.ts
|
|
786
|
+
import { useEffect } from "react";
|
|
787
|
+
var useClickAway = ({
|
|
788
|
+
containerClassName,
|
|
789
|
+
isOpen,
|
|
790
|
+
onClose
|
|
791
|
+
}) => {
|
|
792
|
+
useEffect(() => {
|
|
793
|
+
let clickHandler;
|
|
794
|
+
if (isOpen) {
|
|
795
|
+
clickHandler = (evt) => {
|
|
796
|
+
const target = evt.target;
|
|
797
|
+
const container = target.closest(`.${containerClassName}`);
|
|
798
|
+
if (container === null) {
|
|
799
|
+
onClose == null ? void 0 : onClose("root");
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
document.body.addEventListener("click", clickHandler, true);
|
|
803
|
+
}
|
|
804
|
+
return () => {
|
|
805
|
+
if (clickHandler) {
|
|
806
|
+
document.body.removeEventListener("click", clickHandler, true);
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
}, [containerClassName, isOpen, onClose]);
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
// src/menu/ContextMenu.tsx
|
|
813
|
+
import { Fragment, jsx as jsx4 } from "react/jsx-runtime";
|
|
814
|
+
import { createElement } from "react";
|
|
815
|
+
var noop = () => void 0;
|
|
816
|
+
var ContextMenu = ({
|
|
817
|
+
activatedByKeyboard,
|
|
818
|
+
children: childrenProp,
|
|
819
|
+
className,
|
|
820
|
+
id: idProp,
|
|
821
|
+
onClose = () => void 0,
|
|
822
|
+
position = { x: 0, y: 0 },
|
|
823
|
+
style,
|
|
824
|
+
...menuListProps
|
|
825
|
+
}) => {
|
|
826
|
+
const id = useId2(idProp);
|
|
827
|
+
const closeMenuRef = useRef5(noop);
|
|
828
|
+
const [menus, actions] = useItemsWithIds(childrenProp);
|
|
829
|
+
const navigatingWithKeyboard = useRef5(activatedByKeyboard);
|
|
830
|
+
const handleMouseEnterItem = useCallback5(() => {
|
|
831
|
+
navigatingWithKeyboard.current = false;
|
|
832
|
+
}, []);
|
|
833
|
+
const handleActivate = useCallback5(
|
|
834
|
+
(menuId) => {
|
|
835
|
+
const { action, options } = actions[menuId];
|
|
836
|
+
closeMenuRef.current("root");
|
|
837
|
+
onClose(action, options);
|
|
838
|
+
},
|
|
839
|
+
[actions, onClose]
|
|
840
|
+
);
|
|
841
|
+
const { closeMenu, listItemProps, openMenu, openMenus, handleRender } = useCascade({
|
|
842
|
+
id,
|
|
843
|
+
onActivate: handleActivate,
|
|
844
|
+
onMouseEnterItem: handleMouseEnterItem,
|
|
845
|
+
position
|
|
846
|
+
});
|
|
847
|
+
closeMenuRef.current = closeMenu;
|
|
848
|
+
console.log({ openMenus });
|
|
849
|
+
const handleClose = useCallback5(() => {
|
|
850
|
+
closeMenu();
|
|
851
|
+
onClose();
|
|
852
|
+
}, [closeMenu, onClose]);
|
|
853
|
+
useClickAway({
|
|
854
|
+
containerClassName: "vuuMenuList",
|
|
855
|
+
onClose: handleClose,
|
|
856
|
+
isOpen: openMenus.length > 0
|
|
857
|
+
});
|
|
858
|
+
const handleOpenMenu = (id2) => {
|
|
859
|
+
const itemId = getItemId(id2);
|
|
860
|
+
const menuId = getMenuId(itemId);
|
|
861
|
+
navigatingWithKeyboard.current = true;
|
|
862
|
+
openMenu(menuId, itemId);
|
|
863
|
+
};
|
|
864
|
+
const handleCloseMenu = () => {
|
|
865
|
+
navigatingWithKeyboard.current = true;
|
|
866
|
+
closeMenu();
|
|
867
|
+
};
|
|
868
|
+
const handleHighlightMenuItem = () => {
|
|
869
|
+
};
|
|
870
|
+
const lastMenu = openMenus.length - 1;
|
|
871
|
+
const getChildMenuIndex = (i) => {
|
|
872
|
+
if (i >= lastMenu) {
|
|
873
|
+
return -1;
|
|
874
|
+
} else {
|
|
875
|
+
const { id: menuId } = openMenus[i + 1];
|
|
876
|
+
const pos = menuId.lastIndexOf(".");
|
|
877
|
+
const idx = pos === -1 ? parseInt(menuId, 10) : parseInt(menuId.slice(-pos), 10);
|
|
878
|
+
return idx;
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
return /* @__PURE__ */ jsx4(Fragment, { children: openMenus.map(({ id: menuId, left, top }, i) => {
|
|
882
|
+
const childMenuIndex = getChildMenuIndex(i);
|
|
883
|
+
return /* @__PURE__ */ jsx4(Portal, { x: left, y: top, onRender: handleRender, children: /* @__PURE__ */ createElement(
|
|
884
|
+
MenuList_default,
|
|
885
|
+
{
|
|
886
|
+
...menuListProps,
|
|
887
|
+
activatedByKeyboard: navigatingWithKeyboard.current,
|
|
888
|
+
childMenuShowing: childMenuIndex,
|
|
889
|
+
className,
|
|
890
|
+
id,
|
|
891
|
+
menuId,
|
|
892
|
+
isRoot: i === 0,
|
|
893
|
+
key: i,
|
|
894
|
+
listItemProps,
|
|
895
|
+
onActivate: handleActivate,
|
|
896
|
+
onHighlightMenuItem: handleHighlightMenuItem,
|
|
897
|
+
onCloseMenu: handleCloseMenu,
|
|
898
|
+
onOpenMenu: handleOpenMenu,
|
|
899
|
+
style
|
|
900
|
+
},
|
|
901
|
+
menus[menuId]
|
|
902
|
+
) }, i);
|
|
903
|
+
}) });
|
|
904
|
+
};
|
|
905
|
+
ContextMenu.displayName = "ContextMenu";
|
|
906
|
+
|
|
907
|
+
// src/menu/context-menu-provider.tsx
|
|
908
|
+
import { createContext, useCallback as useCallback6, useMemo as useMemo6 } from "react";
|
|
909
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
910
|
+
var ContextMenuContext = createContext(
|
|
911
|
+
null
|
|
912
|
+
);
|
|
913
|
+
var isGroupMenuItemDescriptor = (menuItem) => menuItem !== void 0 && "children" in menuItem;
|
|
914
|
+
var Provider = ({
|
|
915
|
+
children,
|
|
916
|
+
context,
|
|
917
|
+
menuActionHandler,
|
|
918
|
+
menuBuilder
|
|
919
|
+
}) => {
|
|
920
|
+
const menuBuilders = useMemo6(() => {
|
|
921
|
+
if ((context == null ? void 0 : context.menuBuilders) && menuBuilder) {
|
|
922
|
+
return context.menuBuilders.concat(menuBuilder);
|
|
923
|
+
} else if (menuBuilder) {
|
|
924
|
+
return [menuBuilder];
|
|
925
|
+
} else {
|
|
926
|
+
return (context == null ? void 0 : context.menuBuilders) || [];
|
|
927
|
+
}
|
|
928
|
+
}, [context, menuBuilder]);
|
|
929
|
+
const handleMenuAction = useCallback6(
|
|
930
|
+
(type, options) => {
|
|
931
|
+
var _a;
|
|
932
|
+
if (menuActionHandler == null ? void 0 : menuActionHandler(type, options)) {
|
|
933
|
+
return true;
|
|
934
|
+
}
|
|
935
|
+
if ((_a = context == null ? void 0 : context.menuActionHandler) == null ? void 0 : _a.call(context, type, options)) {
|
|
936
|
+
return true;
|
|
937
|
+
}
|
|
938
|
+
},
|
|
939
|
+
[context, menuActionHandler]
|
|
940
|
+
);
|
|
941
|
+
return /* @__PURE__ */ jsx5(
|
|
942
|
+
ContextMenuContext.Provider,
|
|
943
|
+
{
|
|
944
|
+
value: {
|
|
945
|
+
menuActionHandler: handleMenuAction,
|
|
946
|
+
menuBuilders
|
|
947
|
+
},
|
|
948
|
+
children
|
|
949
|
+
}
|
|
950
|
+
);
|
|
951
|
+
};
|
|
952
|
+
var ContextMenuProvider = ({
|
|
953
|
+
children,
|
|
954
|
+
label,
|
|
955
|
+
menuActionHandler,
|
|
956
|
+
menuBuilder
|
|
957
|
+
}) => {
|
|
958
|
+
return /* @__PURE__ */ jsx5(ContextMenuContext.Consumer, { children: (parentContext) => /* @__PURE__ */ jsx5(
|
|
959
|
+
Provider,
|
|
960
|
+
{
|
|
961
|
+
context: parentContext,
|
|
962
|
+
label,
|
|
963
|
+
menuActionHandler,
|
|
964
|
+
menuBuilder,
|
|
965
|
+
children
|
|
966
|
+
}
|
|
967
|
+
) });
|
|
968
|
+
};
|
|
969
|
+
|
|
970
|
+
// src/menu/useContextMenu.tsx
|
|
971
|
+
import { useCallback as useCallback7, useContext } from "react";
|
|
972
|
+
|
|
973
|
+
// src/popup/popup-service.ts
|
|
974
|
+
import cx3 from "classnames";
|
|
975
|
+
import React3, {
|
|
976
|
+
createElement as createElement2,
|
|
977
|
+
useEffect as useEffect2,
|
|
978
|
+
useRef as useRef6
|
|
979
|
+
} from "react";
|
|
980
|
+
import ReactDOM3 from "react-dom";
|
|
981
|
+
var _dialogOpen = false;
|
|
982
|
+
var _popups = [];
|
|
983
|
+
function specialKeyHandler(e) {
|
|
984
|
+
if (e.key === "Esc") {
|
|
985
|
+
if (_popups.length) {
|
|
986
|
+
closeAllPopups();
|
|
987
|
+
} else if (_dialogOpen) {
|
|
988
|
+
const dialogRoot = document.body.querySelector(".vuuDialog");
|
|
989
|
+
if (dialogRoot) {
|
|
990
|
+
ReactDOM3.unmountComponentAtNode(dialogRoot);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
function outsideClickHandler(e) {
|
|
996
|
+
if (_popups.length) {
|
|
997
|
+
const popupContainers = document.body.querySelectorAll(".vuuPopup");
|
|
998
|
+
for (let i = 0; i < popupContainers.length; i++) {
|
|
999
|
+
if (popupContainers[i].contains(e.target)) {
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
closeAllPopups();
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
function closeAllPopups() {
|
|
1007
|
+
if (_popups.length) {
|
|
1008
|
+
const popupContainers = document.body.querySelectorAll(".vuuPopup");
|
|
1009
|
+
for (let i = 0; i < popupContainers.length; i++) {
|
|
1010
|
+
ReactDOM3.unmountComponentAtNode(popupContainers[i]);
|
|
1011
|
+
}
|
|
1012
|
+
popupClosed("*");
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
function dialogOpened() {
|
|
1016
|
+
if (_dialogOpen === false) {
|
|
1017
|
+
_dialogOpen = true;
|
|
1018
|
+
window.addEventListener("keydown", specialKeyHandler, true);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
function dialogClosed() {
|
|
1022
|
+
if (_dialogOpen) {
|
|
1023
|
+
_dialogOpen = false;
|
|
1024
|
+
window.removeEventListener("keydown", specialKeyHandler, true);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
function popupOpened(name) {
|
|
1028
|
+
if (_popups.indexOf(name) === -1) {
|
|
1029
|
+
_popups.push(name);
|
|
1030
|
+
if (_dialogOpen === false) {
|
|
1031
|
+
window.addEventListener("keydown", specialKeyHandler, true);
|
|
1032
|
+
window.addEventListener("click", outsideClickHandler, true);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
function popupClosed(name) {
|
|
1037
|
+
if (_popups.length) {
|
|
1038
|
+
if (name === "*") {
|
|
1039
|
+
_popups.length = 0;
|
|
1040
|
+
} else {
|
|
1041
|
+
const pos = _popups.indexOf(name);
|
|
1042
|
+
if (pos !== -1) {
|
|
1043
|
+
_popups.splice(pos, 1);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
if (_popups.length === 0 && _dialogOpen === false) {
|
|
1047
|
+
window.removeEventListener("keydown", specialKeyHandler, true);
|
|
1048
|
+
window.removeEventListener("click", outsideClickHandler, true);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
var PopupComponent = ({
|
|
1053
|
+
children,
|
|
1054
|
+
position,
|
|
1055
|
+
style
|
|
1056
|
+
}) => {
|
|
1057
|
+
const className = cx3("hwPopup", "hwPopupContainer", position);
|
|
1058
|
+
return createElement2("div", { className, style }, children);
|
|
1059
|
+
};
|
|
1060
|
+
var incrementingKey = 1;
|
|
1061
|
+
var PopupService = class {
|
|
1062
|
+
static showPopup({
|
|
1063
|
+
name = "anon",
|
|
1064
|
+
group = "all",
|
|
1065
|
+
position = "",
|
|
1066
|
+
left = 0,
|
|
1067
|
+
right = "auto",
|
|
1068
|
+
top = 0,
|
|
1069
|
+
width = "auto",
|
|
1070
|
+
component
|
|
1071
|
+
}) {
|
|
1072
|
+
if (!component) {
|
|
1073
|
+
throw Error(`PopupService showPopup, no component supplied`);
|
|
1074
|
+
}
|
|
1075
|
+
popupOpened(name);
|
|
1076
|
+
let el = document.body.querySelector(".vuuPopup." + group);
|
|
1077
|
+
if (el === null) {
|
|
1078
|
+
el = document.createElement("div");
|
|
1079
|
+
el.className = "vuuPopup " + group;
|
|
1080
|
+
document.body.appendChild(el);
|
|
1081
|
+
}
|
|
1082
|
+
const style = { width };
|
|
1083
|
+
renderPortal(
|
|
1084
|
+
createElement2(
|
|
1085
|
+
PopupComponent,
|
|
1086
|
+
{ key: incrementingKey++, position, style },
|
|
1087
|
+
component
|
|
1088
|
+
),
|
|
1089
|
+
el,
|
|
1090
|
+
left,
|
|
1091
|
+
top,
|
|
1092
|
+
() => {
|
|
1093
|
+
PopupService.keepWithinThePage(el, right);
|
|
1094
|
+
}
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
static hidePopup(name = "anon", group = "all") {
|
|
1098
|
+
if (_popups.indexOf(name) !== -1) {
|
|
1099
|
+
popupClosed(name);
|
|
1100
|
+
const popupRoot = document.body.querySelector(`.vuuPopup.${group}`);
|
|
1101
|
+
if (popupRoot) {
|
|
1102
|
+
ReactDOM3.unmountComponentAtNode(popupRoot);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
static keepWithinThePage(el, right = "auto") {
|
|
1107
|
+
const target = el.querySelector(".vuuPopupContainer > *");
|
|
1108
|
+
if (target) {
|
|
1109
|
+
const {
|
|
1110
|
+
top,
|
|
1111
|
+
left,
|
|
1112
|
+
width,
|
|
1113
|
+
height,
|
|
1114
|
+
right: currentRight
|
|
1115
|
+
} = target.getBoundingClientRect();
|
|
1116
|
+
const w = window.innerWidth;
|
|
1117
|
+
const h = window.innerHeight;
|
|
1118
|
+
const overflowH = h - (top + height);
|
|
1119
|
+
if (overflowH < 0) {
|
|
1120
|
+
target.style.top = Math.round(top) + overflowH + "px";
|
|
1121
|
+
}
|
|
1122
|
+
const overflowW = w - (left + width);
|
|
1123
|
+
if (overflowW < 0) {
|
|
1124
|
+
target.style.left = Math.round(left) + overflowW + "px";
|
|
1125
|
+
}
|
|
1126
|
+
if (typeof right === "number" && right !== currentRight) {
|
|
1127
|
+
const adjustment = right - currentRight;
|
|
1128
|
+
target.style.left = left + adjustment + "px";
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
var DialogService = class {
|
|
1134
|
+
static showDialog(dialog) {
|
|
1135
|
+
const containerEl = ".vuuDialog";
|
|
1136
|
+
const onClose = dialog.props.onClose;
|
|
1137
|
+
dialogOpened();
|
|
1138
|
+
ReactDOM3.render(
|
|
1139
|
+
React3.cloneElement(dialog, {
|
|
1140
|
+
container: containerEl,
|
|
1141
|
+
onClose: () => {
|
|
1142
|
+
DialogService.closeDialog();
|
|
1143
|
+
if (onClose) {
|
|
1144
|
+
onClose();
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
}),
|
|
1148
|
+
document.body.querySelector(containerEl)
|
|
1149
|
+
);
|
|
1150
|
+
}
|
|
1151
|
+
static closeDialog() {
|
|
1152
|
+
dialogClosed();
|
|
1153
|
+
const dialogRoot = document.body.querySelector(".vuuDialog");
|
|
1154
|
+
if (dialogRoot) {
|
|
1155
|
+
ReactDOM3.unmountComponentAtNode(dialogRoot);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
};
|
|
1159
|
+
var Popup = (props) => {
|
|
1160
|
+
const pendingTask = useRef6();
|
|
1161
|
+
const ref = useRef6(null);
|
|
1162
|
+
const show = (props2, boundingClientRect) => {
|
|
1163
|
+
const { name, group, depth, width } = props2;
|
|
1164
|
+
let left;
|
|
1165
|
+
let top;
|
|
1166
|
+
if (pendingTask.current) {
|
|
1167
|
+
window.clearTimeout(pendingTask.current);
|
|
1168
|
+
pendingTask.current = void 0;
|
|
1169
|
+
}
|
|
1170
|
+
if (props2.close === true) {
|
|
1171
|
+
PopupService.hidePopup(name, group);
|
|
1172
|
+
} else {
|
|
1173
|
+
const { position, children: component } = props2;
|
|
1174
|
+
const {
|
|
1175
|
+
left: targetLeft,
|
|
1176
|
+
top: targetTop,
|
|
1177
|
+
width: clientWidth,
|
|
1178
|
+
bottom: targetBottom
|
|
1179
|
+
} = boundingClientRect;
|
|
1180
|
+
if (position === "below") {
|
|
1181
|
+
left = targetLeft;
|
|
1182
|
+
top = targetBottom;
|
|
1183
|
+
} else if (position === "above") {
|
|
1184
|
+
left = targetLeft;
|
|
1185
|
+
top = targetTop;
|
|
1186
|
+
}
|
|
1187
|
+
pendingTask.current = window.setTimeout(() => {
|
|
1188
|
+
PopupService.showPopup({
|
|
1189
|
+
name,
|
|
1190
|
+
group,
|
|
1191
|
+
depth,
|
|
1192
|
+
position,
|
|
1193
|
+
left,
|
|
1194
|
+
top,
|
|
1195
|
+
width: width || clientWidth,
|
|
1196
|
+
component
|
|
1197
|
+
});
|
|
1198
|
+
}, 10);
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
useEffect2(() => {
|
|
1202
|
+
if (ref.current) {
|
|
1203
|
+
const el = ref.current.parentElement;
|
|
1204
|
+
const boundingClientRect = el == null ? void 0 : el.getBoundingClientRect();
|
|
1205
|
+
if (boundingClientRect) {
|
|
1206
|
+
show(props, boundingClientRect);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
return () => {
|
|
1210
|
+
PopupService.hidePopup(props.name, props.group);
|
|
1211
|
+
};
|
|
1212
|
+
}, [props]);
|
|
1213
|
+
return React3.createElement("div", { className: "popup-proxy", ref });
|
|
1214
|
+
};
|
|
1215
|
+
|
|
1216
|
+
// src/menu/useContextMenu.tsx
|
|
1217
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
1218
|
+
var useContextMenu = () => {
|
|
1219
|
+
const ctx = useContext(ContextMenuContext);
|
|
1220
|
+
const buildMenuOptions = useCallback7(
|
|
1221
|
+
(menuBuilders, location, options) => {
|
|
1222
|
+
let results = [];
|
|
1223
|
+
for (const menuBuilder of menuBuilders) {
|
|
1224
|
+
results = results.concat(menuBuilder(location, options));
|
|
1225
|
+
}
|
|
1226
|
+
return results;
|
|
1227
|
+
},
|
|
1228
|
+
[]
|
|
1229
|
+
);
|
|
1230
|
+
const handleShowContextMenu = useCallback7(
|
|
1231
|
+
(e, location, options) => {
|
|
1232
|
+
var _a;
|
|
1233
|
+
e.stopPropagation();
|
|
1234
|
+
e.preventDefault();
|
|
1235
|
+
const menuBuilders = (_a = ctx == null ? void 0 : ctx.menuBuilders) != null ? _a : [];
|
|
1236
|
+
const menuItemDescriptors = buildMenuOptions(
|
|
1237
|
+
menuBuilders,
|
|
1238
|
+
location,
|
|
1239
|
+
options
|
|
1240
|
+
);
|
|
1241
|
+
console.log({
|
|
1242
|
+
menuItemDescriptors
|
|
1243
|
+
});
|
|
1244
|
+
if (menuItemDescriptors.length && (ctx == null ? void 0 : ctx.menuActionHandler)) {
|
|
1245
|
+
console.log(`showContextMenu ${location}`, {
|
|
1246
|
+
options
|
|
1247
|
+
});
|
|
1248
|
+
showContextMenu(e, menuItemDescriptors, ctx.menuActionHandler);
|
|
1249
|
+
}
|
|
1250
|
+
},
|
|
1251
|
+
[buildMenuOptions, ctx]
|
|
1252
|
+
);
|
|
1253
|
+
return handleShowContextMenu;
|
|
1254
|
+
};
|
|
1255
|
+
var showContextMenu = (e, menuDescriptors, handleContextMenuAction) => {
|
|
1256
|
+
const { clientX: left, clientY: top } = e;
|
|
1257
|
+
const menuItems = (menuDescriptors2) => {
|
|
1258
|
+
const fromDescriptor = (menuItem, i) => isGroupMenuItemDescriptor(menuItem) ? /* @__PURE__ */ jsx6(MenuItemGroup, { label: menuItem.label, children: menuItem.children.map(fromDescriptor) }, i) : /* @__PURE__ */ jsx6(
|
|
1259
|
+
MenuItem,
|
|
1260
|
+
{
|
|
1261
|
+
action: menuItem.action,
|
|
1262
|
+
"data-icon": menuItem.icon,
|
|
1263
|
+
options: menuItem.options,
|
|
1264
|
+
children: menuItem.label
|
|
1265
|
+
},
|
|
1266
|
+
i
|
|
1267
|
+
);
|
|
1268
|
+
return menuDescriptors2.map(fromDescriptor);
|
|
1269
|
+
};
|
|
1270
|
+
const handleClose = (menuId, options) => {
|
|
1271
|
+
if (menuId) {
|
|
1272
|
+
handleContextMenuAction(menuId, options);
|
|
1273
|
+
PopupService.hidePopup();
|
|
1274
|
+
}
|
|
1275
|
+
};
|
|
1276
|
+
const component = /* @__PURE__ */ jsx6(ContextMenu, { onClose: handleClose, position: { x: left, y: top }, children: menuItems(menuDescriptors) });
|
|
1277
|
+
PopupService.showPopup({ left: 0, top: 0, component });
|
|
1278
|
+
};
|
|
1279
|
+
export {
|
|
1280
|
+
ContextMenu,
|
|
1281
|
+
ContextMenuContext,
|
|
1282
|
+
ContextMenuProvider,
|
|
1283
|
+
Dialog,
|
|
1284
|
+
DialogService,
|
|
1285
|
+
MenuItem,
|
|
1286
|
+
MenuItemGroup,
|
|
1287
|
+
Popup,
|
|
1288
|
+
PopupService,
|
|
1289
|
+
Portal,
|
|
1290
|
+
Separator,
|
|
1291
|
+
createContainer,
|
|
1292
|
+
installTheme,
|
|
1293
|
+
isGroupMenuItemDescriptor,
|
|
1294
|
+
renderPortal,
|
|
1295
|
+
useContextMenu
|
|
1296
|
+
};
|
|
1297
|
+
//# sourceMappingURL=index.js.map
|