@vuu-ui/vuu-popups 0.8.17-debug → 0.8.18-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/esm/index.js CHANGED
@@ -1,59 +1,11 @@
1
1
  // src/dialog/Dialog.tsx
2
- import { Scrim } from "@salt-ds/lab";
3
- import cx2 from "classnames";
4
- import { useCallback, useRef as useRef2 } from "react";
5
-
6
- // src/portal/Portal.tsx
7
- import { useThemeAttributes } from "@vuu-ui/vuu-shell";
8
- import { useLayoutEffect, useRef, useState } from "react";
9
- import { createPortal } from "react-dom";
10
- function getContainer(container) {
11
- return typeof container === "function" ? container() : container;
12
- }
13
- var DEFAULT_ID = "vuu-portal-root";
14
- var Portal = ({
15
- children,
16
- container: containerProp = document.body,
17
- id = DEFAULT_ID,
18
- onRender,
19
- open = true,
20
- themeAttributes
21
- }) => {
22
- var _a;
23
- const [mounted, setMounted] = useState(false);
24
- const portalRef = useRef(null);
25
- const container = (_a = getContainer(containerProp)) != null ? _a : document.body;
26
- const [themeClass, densityClass, dataMode] = useThemeAttributes(themeAttributes);
27
- useLayoutEffect(() => {
28
- const root = document.getElementById(id);
29
- if (root) {
30
- portalRef.current = root;
31
- } else {
32
- portalRef.current = document.createElement("div");
33
- portalRef.current.id = id;
34
- }
35
- const el = portalRef.current;
36
- if (!container.contains(el)) {
37
- container.appendChild(el);
38
- }
39
- el.classList.add(themeClass, densityClass);
40
- el.dataset.mode = dataMode;
41
- setMounted(true);
42
- }, [id, container, themeClass, densityClass, dataMode]);
43
- useLayoutEffect(() => {
44
- requestAnimationFrame(() => {
45
- onRender == null ? void 0 : onRender();
46
- });
47
- }, [onRender]);
48
- if (open && mounted && portalRef.current && children) {
49
- return createPortal(children, portalRef.current);
50
- }
51
- return null;
52
- };
2
+ import cx5 from "clsx";
3
+ import { useThemeAttributes } from "@vuu-ui/vuu-utils";
4
+ import { useCallback as useCallback2, useLayoutEffect as useLayoutEffect2, useRef as useRef2 } from "react";
53
5
 
54
6
  // src/dialog-header/DialogHeader.tsx
55
7
  import { Button, Text } from "@salt-ds/core";
56
- import cx from "classnames";
8
+ import cx from "clsx";
57
9
  import { jsx, jsxs } from "react/jsx-runtime";
58
10
  var classBase = "vuuDialogHeader";
59
11
  var DialogHeader = ({
@@ -77,1302 +29,1408 @@ var DialogHeader = ({
77
29
  ] });
78
30
  };
79
31
 
80
- // src/dialog/Dialog.tsx
81
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
82
- var classBase2 = "vuuDialog";
83
- var Dialog = ({
84
- children,
32
+ // src/popup/popup-service.ts
33
+ import cx3 from "clsx";
34
+ import React, {
35
+ createElement
36
+ } from "react";
37
+ import ReactDOM2 from "react-dom";
38
+
39
+ // src/portal-deprecated/render-portal.tsx
40
+ import * as ReactDOM from "react-dom";
41
+ import cx2 from "clsx";
42
+ var containerId = 1;
43
+ var getPortalContainer = ({
85
44
  className,
86
- isOpen = false,
87
- onClose,
88
- title,
89
- hideCloseButton = false,
90
- ...props
45
+ dataMode,
46
+ x = 0,
47
+ y = 0,
48
+ win = window
91
49
  }) => {
92
- const root = useRef2(null);
93
- const close = useCallback(() => {
94
- onClose == null ? void 0 : onClose();
95
- }, [onClose]);
96
- if (!isOpen) {
97
- return null;
50
+ const el = win.document.createElement("div");
51
+ el.className = cx2(`vuuPopup ${containerId++}`, className);
52
+ el.style.cssText = `left:${x}px; top:${y}px;`;
53
+ if (dataMode) {
54
+ el.dataset.mode = dataMode;
98
55
  }
99
- return /* @__PURE__ */ jsx2(Portal, { children: /* @__PURE__ */ jsx2(Scrim, { className: `${classBase2}-scrim`, open: isOpen, autoFocusRef: root, children: /* @__PURE__ */ jsxs2("div", { ...props, className: cx2(classBase2, className), ref: root, children: [
100
- /* @__PURE__ */ jsx2(
101
- DialogHeader,
102
- {
103
- hideCloseButton,
104
- onClose: close,
105
- title
106
- }
107
- ),
108
- /* @__PURE__ */ jsx2("div", { className: `${classBase2}-body`, children })
109
- ] }) }) });
110
- };
111
-
112
- // src/dialog/useDialog.tsx
113
- import { useCallback as useCallback2, useState as useState2 } from "react";
114
- import { jsx as jsx3 } from "react/jsx-runtime";
115
- var useDialog = () => {
116
- const [dialogState, setDialogState] = useState2();
117
- const handleClose = useCallback2(() => {
118
- setDialogState(void 0);
119
- }, []);
120
- const dialog = dialogState ? /* @__PURE__ */ jsx3(
121
- Dialog,
122
- {
123
- className: "vuDialog",
124
- isOpen: true,
125
- onClose: handleClose,
126
- style: { maxHeight: 500 },
127
- title: dialogState.title,
128
- hideCloseButton: dialogState.hideCloseButton,
129
- children: dialogState.content
130
- }
131
- ) : null;
132
- return {
133
- dialog,
134
- setDialogState
135
- };
56
+ win.document.body.appendChild(el);
57
+ return el;
136
58
  };
137
-
138
- // src/menu/ContextMenu.tsx
139
- import { useCallback as useCallback6, useRef as useRef6 } from "react";
140
-
141
- // src/menu/MenuList.tsx
142
- import React2, {
143
- useLayoutEffect as useLayoutEffect2,
144
- useMemo as useMemo3,
145
- useRef as useRef4
146
- } from "react";
147
- import cx3 from "classnames";
148
- import { useId } from "@vuu-ui/vuu-layout";
149
-
150
- // src/menu/use-keyboard-navigation.ts
151
- import {
152
- useCallback as useCallback3,
153
- useMemo,
154
- useRef as useRef3,
155
- useState as useState3
156
- } from "react";
157
-
158
- // src/menu/utils.ts
159
- var isRoot = (el) => el.closest(`[data-root='true']`) !== null;
160
- var hasPopup = (el, idx) => {
161
- var _a;
162
- return el.ariaHasPopup === "true" && ((_a = el.dataset) == null ? void 0 : _a.idx) === `${idx}` || el.querySelector(`:scope > [data-index='${idx}'][aria-haspopup='true']`) !== null;
59
+ var createContainer = (props) => getPortalContainer(props);
60
+ var renderPortal = (component, container, x, y, onRender) => {
61
+ container.style.cssText = `left:${x}px; top:${y}px;position: absolute;`;
62
+ ReactDOM.render(component, container, onRender);
163
63
  };
164
64
 
165
- // src/menu/key-code.ts
166
- function union(set1, ...sets) {
167
- const result = new Set(set1);
168
- for (const set of sets) {
169
- for (const element of set) {
170
- result.add(element);
65
+ // src/popup/popup-service.ts
66
+ var _dialogOpen = false;
67
+ var _popups = [];
68
+ var reasonIsMenuAction = (reason) => (reason == null ? void 0 : reason.type) === "menu-action";
69
+ var reasonIsClickAway = (reason) => (reason == null ? void 0 : reason.type) === "click-away";
70
+ function specialKeyHandler(e) {
71
+ if (e.key === "Esc") {
72
+ if (_popups.length) {
73
+ closeAllPopups();
74
+ } else if (_dialogOpen) {
75
+ const dialogRoot = document.body.querySelector(".vuuDialog");
76
+ if (dialogRoot) {
77
+ ReactDOM2.unmountComponentAtNode(dialogRoot);
78
+ }
171
79
  }
172
80
  }
173
- return result;
174
81
  }
175
- var Enter = "Enter";
176
- var Delete = "Delete";
177
- var actionKeys = /* @__PURE__ */ new Set([Enter, Delete]);
178
- var focusKeys = /* @__PURE__ */ new Set(["Tab"]);
179
- var arrowLeftRightKeys = /* @__PURE__ */ new Set(["ArrowRight", "ArrowLeft"]);
180
- var verticalNavigationKeys = /* @__PURE__ */ new Set(["Home", "End", "ArrowDown", "ArrowUp"]);
181
- var horizontalNavigationKeys = /* @__PURE__ */ new Set([
182
- "Home",
183
- "End",
184
- "ArrowRight",
185
- "ArrowLeft"
186
- ]);
187
- var functionKeys = /* @__PURE__ */ new Set([
188
- "F1",
189
- "F2",
190
- "F3",
191
- "F4",
192
- "F5",
193
- "F6",
194
- "F7",
195
- "F8",
196
- "F9",
197
- "F10",
198
- "F11",
199
- "F12"
200
- ]);
201
- var specialKeys = union(
202
- actionKeys,
203
- horizontalNavigationKeys,
204
- verticalNavigationKeys,
205
- arrowLeftRightKeys,
206
- functionKeys,
207
- focusKeys
208
- );
209
- var isNavigationKey = ({ key }, orientation = "vertical") => {
210
- const navigationKeys = orientation === "vertical" ? verticalNavigationKeys : horizontalNavigationKeys;
211
- return navigationKeys.has(key);
212
- };
213
-
214
- // src/menu/use-keyboard-navigation.ts
215
- import { isValidNumber } from "@vuu-ui/vuu-utils";
216
- var useKeyboardNavigation = ({
217
- autoHighlightFirstItem = false,
218
- count,
219
- defaultHighlightedIdx,
220
- highlightedIndex: highlightedIndexProp,
221
- onActivate,
222
- onHighlight,
223
- // onKeyDown,
224
- onCloseMenu,
225
- onOpenMenu
226
- }) => {
227
- var _a;
228
- if (isValidNumber(highlightedIndexProp) && isValidNumber(defaultHighlightedIdx)) {
229
- throw Error(
230
- "useKeyboardNavigation do not pass values for both highlightedIndex and defaultHighlightedIdx"
82
+ function outsideClickHandler(e) {
83
+ if (_popups.length) {
84
+ const popupContainers = document.body.querySelectorAll(
85
+ ".vuuPopup,#vuu-portal-root"
231
86
  );
232
- }
233
- const controlledHighlighting = isValidNumber(highlightedIndexProp);
234
- const highlightedIndexRef = useRef3(
235
- (_a = defaultHighlightedIdx != null ? defaultHighlightedIdx : highlightedIndexProp) != null ? _a : autoHighlightFirstItem ? 0 : -1
236
- );
237
- const [, forceRender] = useState3(null);
238
- const setHighlightedIdx = useCallback3(
239
- (idx) => {
240
- highlightedIndexRef.current = idx;
241
- onHighlight == null ? void 0 : onHighlight(idx);
242
- forceRender({});
243
- },
244
- [onHighlight]
245
- );
246
- const setHighlightedIndex = useCallback3(
247
- (idx) => {
248
- if (idx !== highlightedIndexRef.current) {
249
- if (!controlledHighlighting) {
250
- setHighlightedIdx(idx);
251
- }
252
- }
253
- },
254
- [controlledHighlighting, setHighlightedIdx]
255
- );
256
- const keyBoardNavigation = useRef3(true);
257
- const ignoreFocus = useRef3(false);
258
- const setIgnoreFocus = (value) => ignoreFocus.current = value;
259
- const highlightedIndex = controlledHighlighting ? highlightedIndexProp : highlightedIndexRef.current;
260
- const navigateChildldItems = useCallback3(
261
- (e) => {
262
- const nextIdx = nextItemIdx(count, e.key, highlightedIndexRef.current);
263
- if (nextIdx !== highlightedIndexRef.current) {
264
- setHighlightedIndex(nextIdx);
265
- }
266
- },
267
- [count, setHighlightedIndex]
268
- );
269
- const handleKeyDown = useCallback3(
270
- (e) => {
271
- if (isNavigationKey(e)) {
272
- e.preventDefault();
273
- e.stopPropagation();
274
- keyBoardNavigation.current = true;
275
- navigateChildldItems(e);
276
- } else if ((e.key === "ArrowRight" || e.key === "Enter") && hasPopup(e.target, highlightedIndex)) {
277
- const menuEl = e.target;
278
- const menuItemEl = menuEl.querySelector(
279
- `:scope > [data-index='${highlightedIndex}']`
280
- );
281
- if (menuItemEl) {
282
- onOpenMenu == null ? void 0 : onOpenMenu(menuItemEl, true);
283
- }
284
- } else if (e.key === "ArrowLeft" && !isRoot(e.target)) {
285
- onCloseMenu(highlightedIndex);
286
- } else if (e.key === "Enter") {
287
- e.preventDefault();
288
- e.stopPropagation();
289
- onActivate && onActivate(highlightedIndex);
290
- } else if (e.key === "Tab") {
291
- onCloseMenu(-1);
87
+ for (let i = 0; i < popupContainers.length; i++) {
88
+ if (popupContainers[i].contains(e.target)) {
89
+ return;
292
90
  }
293
- },
294
- [
295
- highlightedIndex,
296
- navigateChildldItems,
297
- onActivate,
298
- onCloseMenu,
299
- onOpenMenu
300
- ]
301
- );
302
- const listProps = useMemo(
303
- () => ({
304
- onFocus: () => {
305
- if (highlightedIndex === -1) {
306
- setHighlightedIdx(0);
307
- }
308
- },
309
- onKeyDown: handleKeyDown,
310
- onMouseDownCapture: () => {
311
- keyBoardNavigation.current = false;
312
- setIgnoreFocus(true);
313
- },
314
- // onMouseEnter would seem less expensive but it misses some cases
315
- onMouseMove: () => {
316
- if (keyBoardNavigation.current) {
317
- keyBoardNavigation.current = false;
318
- }
319
- },
320
- onMouseLeave: () => {
321
- keyBoardNavigation.current = true;
322
- setIgnoreFocus(false);
323
- setHighlightedIndex(-1);
324
- }
325
- }),
326
- [handleKeyDown, highlightedIndex, setHighlightedIdx, setHighlightedIndex]
327
- );
328
- return {
329
- focusVisible: keyBoardNavigation.current ? highlightedIndex : -1,
330
- controlledHighlighting,
331
- highlightedIndex,
332
- setHighlightedIndex,
333
- // keyBoardNavigation,
334
- listProps,
335
- setIgnoreFocus
336
- };
337
- };
338
- function nextItemIdx(count, key, idx) {
339
- if (key === "ArrowUp") {
340
- if (idx > 0) {
341
- return idx - 1;
342
- } else {
343
- return idx;
344
91
  }
345
- } else {
346
- if (idx === null) {
347
- return 0;
348
- } else if (idx === count - 1) {
349
- return idx;
350
- } else {
351
- return idx + 1;
92
+ closeAllPopups({ mouseEvt: e, type: "click-away" });
93
+ }
94
+ }
95
+ function closeAllPopups(reason) {
96
+ if (_popups.length === 1) {
97
+ PopupService.hidePopup(reason, "anon", "all");
98
+ } else if (_popups.length) {
99
+ const popupContainers = document.body.querySelectorAll(".vuuPopup");
100
+ for (let i = 0; i < popupContainers.length; i++) {
101
+ ReactDOM2.unmountComponentAtNode(popupContainers[i]);
352
102
  }
103
+ popupClosed("*");
353
104
  }
354
105
  }
355
-
356
- // src/menu/use-items-with-ids-next.ts
357
- import React, { useCallback as useCallback4, useMemo as useMemo2 } from "react";
358
- var isMenuItemGroup = (child) => child.type === MenuItemGroup || !!child.props["data-group"];
359
- var getLabelFromChildren = (children) => {
360
- if (Array.isArray(children) && isMenuItemLabel(children[0])) {
361
- return children[0];
106
+ function dialogOpened() {
107
+ if (_dialogOpen === false) {
108
+ _dialogOpen = true;
109
+ window.addEventListener("keydown", specialKeyHandler, true);
362
110
  }
363
- };
364
- var assignId = (child, path, group, hasSeparator = false) => {
365
- const {
366
- props: { children }
367
- } = child;
368
- return {
369
- childWithId: React.cloneElement(child, {
370
- hasSeparator,
371
- id: `${path}`,
372
- key: path,
373
- children: group ? getLabelFromChildren(children) : children
374
- }),
375
- grandChildren: group ? children : void 0
376
- };
377
- };
378
- var useItemsWithIdsNext = (childrenProp, rootId) => {
379
- const normalizeChildren = useCallback4(() => {
380
- const collectChildren = (children, path = rootId, menus2 = {}, actions2 = {}) => {
381
- const list = menus2[path] = [];
382
- let idx = 0;
383
- let hasSeparator = false;
384
- React.Children.forEach(children, (child) => {
385
- if (isMenuItemLabel(child)) {
386
- } else if (child.type === Separator) {
387
- hasSeparator = true;
388
- } else {
389
- const hasChildItems = isMenuItemGroup(child);
390
- const childPath = `${path}-${idx}`;
391
- const {
392
- props: { action, options }
393
- } = child;
394
- const { childWithId, grandChildren } = assignId(
395
- child,
396
- childPath,
397
- hasChildItems,
398
- hasSeparator
399
- );
400
- list.push(childWithId);
401
- if (grandChildren) {
402
- collectChildren(grandChildren, childPath, menus2, actions2);
403
- } else {
404
- actions2[childPath] = { action, options };
405
- }
406
- idx += 1;
407
- hasSeparator = false;
408
- }
409
- });
410
- return [menus2, actions2];
411
- };
412
- return collectChildren(childrenProp);
413
- }, [rootId, childrenProp]);
414
- const [menus, actions] = useMemo2(
415
- () => normalizeChildren(),
416
- [normalizeChildren]
417
- );
418
- return [menus, actions];
419
- };
420
-
421
- // src/menu/MenuList.tsx
422
- import { Fragment, jsx as jsx4 } from "react/jsx-runtime";
423
- var classBase3 = "vuuMenuList";
424
- var Separator = () => /* @__PURE__ */ jsx4("li", { className: "vuuMenuItem-divider" });
425
- var MenuItemGroup = () => null;
426
- var MenuItem = ({
111
+ }
112
+ function dialogClosed() {
113
+ if (_dialogOpen) {
114
+ _dialogOpen = false;
115
+ window.removeEventListener("keydown", specialKeyHandler, true);
116
+ }
117
+ }
118
+ function popupOpened(name) {
119
+ if (_popups.indexOf(name) === -1) {
120
+ _popups.push(name);
121
+ if (_dialogOpen === false) {
122
+ window.addEventListener("keydown", specialKeyHandler, true);
123
+ window.addEventListener("click", outsideClickHandler, true);
124
+ }
125
+ }
126
+ }
127
+ function popupClosed(name) {
128
+ if (_popups.length) {
129
+ if (name === "*") {
130
+ _popups.length = 0;
131
+ } else {
132
+ const pos = _popups.indexOf(name);
133
+ if (pos !== -1) {
134
+ _popups.splice(pos, 1);
135
+ }
136
+ }
137
+ if (_popups.length === 0 && _dialogOpen === false) {
138
+ window.removeEventListener("keydown", specialKeyHandler, true);
139
+ window.removeEventListener("click", outsideClickHandler, true);
140
+ }
141
+ }
142
+ }
143
+ var PopupComponent = ({
427
144
  children,
428
- idx,
429
- options,
430
- ...props
145
+ position,
146
+ style
431
147
  }) => {
432
- return /* @__PURE__ */ jsx4("div", { ...props, children });
148
+ const className = cx3("hwPopup", "hwPopupContainer", position);
149
+ return createElement("div", { className, style }, children);
433
150
  };
434
- var MenuItemLabel = ({ children }) => /* @__PURE__ */ jsx4(Fragment, { children });
435
- MenuItemLabel.displayName = "MenuItemLabel";
436
- MenuItem.Label = MenuItemLabel;
437
- var getDisplayName = (item) => React2.isValidElement(item) && typeof item.type !== "string" && "displayName" in item.type ? item.type.displayName : void 0;
438
- var isMenuItemLabel = (item) => getDisplayName(item) === "MenuItemLabel";
439
- var hasIcon = (child) => child.props["data-icon"];
440
- var MenuList = ({
441
- activatedByKeyboard,
442
- childMenuShowing,
443
- children,
444
- className,
445
- defaultHighlightedIdx,
446
- highlightedIdx: highlightedIdxProp,
447
- id: idProp,
448
- isRoot: isRoot2,
449
- listItemProps,
450
- onHighlightMenuItem,
451
- onActivate,
452
- onCloseMenu,
453
- openMenu: onOpenMenu,
454
- ...props
455
- }) => {
456
- const id = useId(idProp);
457
- const root = useRef4(null);
458
- const mapIdxToId = useMemo3(() => /* @__PURE__ */ new Map(), []);
459
- const handleActivate = (idx) => {
460
- var _a;
461
- const el = (_a = root.current) == null ? void 0 : _a.querySelector(`:scope > [data-index='${idx}']`);
462
- (el == null ? void 0 : el.id) && (onActivate == null ? void 0 : onActivate(el.id));
463
- };
464
- const { focusVisible, highlightedIndex, listProps } = useKeyboardNavigation({
465
- count: React2.Children.count(children),
466
- defaultHighlightedIdx,
467
- highlightedIndex: highlightedIdxProp,
468
- onActivate: handleActivate,
469
- onHighlight: onHighlightMenuItem,
470
- onOpenMenu,
471
- onCloseMenu
472
- });
473
- const appliedFocusVisible = childMenuShowing == void 0 ? focusVisible : -1;
474
- useLayoutEffect2(() => {
151
+ var incrementingKey = 1;
152
+ var PopupService = class _PopupService {
153
+ static showPopup({
154
+ group = "all",
155
+ name = "anon",
156
+ left = 0,
157
+ position = "",
158
+ right = "auto",
159
+ top = 0,
160
+ width = "auto",
161
+ component
162
+ }) {
163
+ if (!component) {
164
+ throw Error(`PopupService showPopup, no component supplied`);
165
+ }
166
+ if (typeof component.props.onClose === "function") {
167
+ _PopupService.onClose = component.props.onClose;
168
+ } else {
169
+ _PopupService.onClose = void 0;
170
+ }
171
+ popupOpened(name);
172
+ document.addEventListener("keydown", _PopupService.escapeKeyListener, true);
173
+ let el = document.body.querySelector(".vuuPopup." + group);
174
+ if (el === null) {
175
+ el = document.createElement("div");
176
+ el.className = "vuuPopup " + group;
177
+ document.body.appendChild(el);
178
+ }
179
+ const style = { width };
180
+ renderPortal(
181
+ createElement(
182
+ PopupComponent,
183
+ { key: incrementingKey++, position, style },
184
+ component
185
+ ),
186
+ el,
187
+ left,
188
+ top,
189
+ () => {
190
+ _PopupService.keepWithinThePage(el, right);
191
+ }
192
+ );
193
+ }
194
+ static escapeKeyListener(evt) {
195
+ if (evt.key === "Escape") {
196
+ _PopupService.hidePopup({ type: "escape", event: evt });
197
+ }
198
+ }
199
+ static hidePopup(reason, name = "anon", group = "all") {
475
200
  var _a;
476
- if (childMenuShowing === void 0 && activatedByKeyboard) {
477
- (_a = root.current) == null ? void 0 : _a.focus();
201
+ if (_popups.indexOf(name) !== -1) {
202
+ popupClosed(name);
203
+ const popupRoot = document.body.querySelector(`.vuuPopup.${group}`);
204
+ if (popupRoot) {
205
+ ReactDOM2.unmountComponentAtNode(popupRoot);
206
+ }
478
207
  }
479
- }, [activatedByKeyboard, childMenuShowing]);
480
- const getActiveDescendant = () => highlightedIndex === void 0 || highlightedIndex === -1 ? void 0 : mapIdxToId.get(highlightedIndex);
481
- function renderContent() {
482
- const propsCommonToAllListItems = {
483
- ...listItemProps,
484
- role: "menuitem"
485
- };
486
- const maybeIcon = (childElement, withIcon, iconName) => withIcon ? [
487
- /* @__PURE__ */ jsx4(
488
- "span",
489
- {
490
- className: "vuuIconContainer",
491
- "data-icon": iconName
492
- },
493
- "icon"
494
- )
495
- ].concat(childElement) : childElement;
496
- function addClonedChild(list, child, idx, withIcon) {
497
- var _a;
208
+ document.removeEventListener(
209
+ "keydown",
210
+ _PopupService.escapeKeyListener,
211
+ true
212
+ );
213
+ (_a = _PopupService == null ? void 0 : _PopupService.onClose) == null ? void 0 : _a.call(_PopupService, reason);
214
+ }
215
+ static keepWithinThePage(el, right = "auto") {
216
+ const target = el.querySelector(".vuuPopupContainer > *");
217
+ if (target) {
498
218
  const {
499
- children: children2,
500
- className: className2,
501
- "data-icon": iconName,
502
- id: itemId,
503
- hasSeparator,
504
- label,
505
- ...props2
506
- } = child.props;
507
- const hasSubMenu = isMenuItemGroup(child);
508
- const subMenuShowing = hasSubMenu && childMenuShowing === itemId;
509
- const ariaControls = subMenuShowing ? `${id}-${itemId}` : void 0;
510
- list.push(
511
- /* @__PURE__ */ jsx4(
512
- MenuItem,
513
- {
514
- ...props2,
515
- ...propsCommonToAllListItems,
516
- ...getMenuItemProps(
517
- itemId,
518
- idx,
519
- (_a = child.key) != null ? _a : itemId,
520
- highlightedIndex,
521
- appliedFocusVisible,
522
- className2,
523
- hasSeparator
524
- ),
525
- "aria-controls": ariaControls,
526
- "aria-haspopup": hasSubMenu || void 0,
527
- "aria-expanded": subMenuShowing || void 0,
528
- children: hasSubMenu ? maybeIcon(label != null ? label : children2, withIcon, iconName) : maybeIcon(children2, withIcon, iconName)
529
- }
530
- )
531
- );
532
- }
533
- const listItems = [];
534
- if (children.length > 0) {
535
- const withIcon = children.some(hasIcon);
536
- children.forEach((child, idx) => {
537
- addClonedChild(listItems, child, idx, withIcon);
538
- });
219
+ top,
220
+ left,
221
+ width,
222
+ height,
223
+ right: currentRight
224
+ } = target.getBoundingClientRect();
225
+ const w = window.innerWidth;
226
+ const h = window.innerHeight;
227
+ const overflowH = h - (top + height);
228
+ if (overflowH < 0) {
229
+ target.style.top = Math.round(top) + overflowH + "px";
230
+ }
231
+ const overflowW = w - (left + width);
232
+ if (overflowW < 0) {
233
+ target.style.left = Math.round(left) + overflowW + "px";
234
+ }
235
+ if (typeof right === "number" && right !== currentRight) {
236
+ const adjustment = right - currentRight;
237
+ target.style.left = left + adjustment + "px";
238
+ }
539
239
  }
540
- return listItems;
541
240
  }
542
- return /* @__PURE__ */ jsx4(
543
- "div",
544
- {
545
- ...props,
546
- ...listProps,
547
- "aria-activedescendant": getActiveDescendant(),
548
- className: cx3(classBase3, className, {
549
- [`${classBase3}-childMenuShowing`]: childMenuShowing !== void 0
241
+ };
242
+ var DialogService = class _DialogService {
243
+ static showDialog(dialog) {
244
+ const containerEl = ".vuuDialog";
245
+ const onClose = dialog.props.onClose;
246
+ dialogOpened();
247
+ ReactDOM2.render(
248
+ React.cloneElement(dialog, {
249
+ container: containerEl,
250
+ onClose: () => {
251
+ _DialogService.closeDialog();
252
+ if (onClose) {
253
+ onClose();
254
+ }
255
+ }
550
256
  }),
551
- "data-root": isRoot2 || void 0,
552
- id,
553
- ref: root,
554
- role: "menu",
555
- children: renderContent()
257
+ document.body.querySelector(containerEl)
258
+ );
259
+ }
260
+ static closeDialog() {
261
+ dialogClosed();
262
+ const dialogRoot = document.body.querySelector(".vuuDialog");
263
+ if (dialogRoot) {
264
+ ReactDOM2.unmountComponentAtNode(dialogRoot);
556
265
  }
557
- );
266
+ }
558
267
  };
559
- var getMenuItemProps = (itemId, idx, key, highlightedIdx, focusVisible, className, hasSeparator) => ({
560
- id: `menuitem-${itemId}`,
561
- key: key != null ? key : idx,
562
- "data-index": idx,
563
- "data-highlighted": idx === highlightedIdx || void 0,
564
- className: cx3("vuuMenuItem", className, {
565
- "vuuMenuItem-separator": hasSeparator,
566
- focusVisible: focusVisible === idx
567
- })
568
- });
569
- MenuList.displayName = "MenuList";
570
-
571
- // src/menu/use-cascade.ts
572
- import {
573
- useCallback as useCallback5,
574
- useMemo as useMemo4,
575
- useRef as useRef5,
576
- useState as useState4
577
- } from "react";
578
268
 
579
- // src/menu/list-dom-utils.ts
580
- var closestListItem = (el) => el == null ? void 0 : el.closest("[data-index],[aria-posinset]");
269
+ // src/popup/Popup.tsx
270
+ import cx4 from "clsx";
581
271
 
582
- // src/menu/use-cascade.ts
583
- var nudge = (menus, distance, pos) => {
584
- return menus.map(
585
- (m, i) => i === menus.length - 1 ? {
586
- ...m,
587
- [pos]: m[pos] - distance
588
- } : m
589
- );
590
- };
591
- var nudgeLeft = (menus, distance) => nudge(menus, distance, "left");
592
- var nudgeUp = (menus, distance) => nudge(menus, distance, "top");
593
- var flipSides = (id, menus) => {
594
- const [parentMenu, menu] = menus.slice(-2);
595
- const el = document.getElementById(`${id}-${menu.id}`);
596
- if (el === null) {
597
- throw Error(`useCascade.flipSides element with id ${menu.id} not found`);
598
- }
599
- const { width } = el.getBoundingClientRect();
600
- return menus.map(
601
- (m) => m === menu ? {
602
- ...m,
603
- left: parentMenu.left - (width - 2)
604
- } : m
605
- );
606
- };
607
- var getPosition = (el, openMenus) => {
608
- const [{ left, top: menuTop }] = openMenus.slice(-1);
609
- const { offsetWidth: width, offsetTop: top } = el;
610
- return { left: left + width, top: top + menuTop };
611
- };
612
- var getHostMenuId = (id, rootId) => {
613
- const pos = id.lastIndexOf("-");
614
- if (id.startsWith("menuitem")) {
615
- return pos > -1 ? id.slice(9, pos) : rootId;
616
- } else {
617
- return pos > -1 ? id.slice(0, pos) : rootId;
618
- }
619
- };
620
- var getTargetMenuId = (id) => id.slice(9);
621
- var getMenuItemDetails = ({ ariaExpanded, ariaHasPopup, id }, rootId) => {
622
- if (id.startsWith("menuitem")) {
623
- return {
624
- hostMenuId: getHostMenuId(id, rootId),
625
- targetMenuId: getTargetMenuId(id),
626
- menuItemId: id,
627
- isGroup: ariaHasPopup === "true",
628
- isOpen: ariaExpanded === "true"
629
- };
630
- } else {
631
- throw Error(`getMenuItemDetails #${id} is not a menuitem`);
272
+ // src/popup/useAnchoredPosition.ts
273
+ import { useCallback, useLayoutEffect, useRef, useState } from "react";
274
+ var getPositionRelativeToAnchor = (anchorElement, placement, offsetLeft, offsetTop, minWidth, dimensions) => {
275
+ const { bottom, height, left, right, top, width } = anchorElement.getBoundingClientRect();
276
+ switch (placement) {
277
+ case "below":
278
+ return { left: left + offsetLeft, top: bottom + offsetTop };
279
+ case "right":
280
+ return { left: right + offsetLeft, top: top + offsetTop };
281
+ case "below-center":
282
+ return { left: left + width / 2 + offsetLeft, top: bottom + offsetTop };
283
+ case "below-right":
284
+ return { left, minWidth, top: bottom + offsetTop };
285
+ case "below-full-width":
286
+ return {
287
+ left: left + offsetLeft,
288
+ minWidth,
289
+ top: bottom + offsetTop,
290
+ width
291
+ };
292
+ case "center":
293
+ if (dimensions) {
294
+ return {
295
+ left: width / 2 - dimensions.width / 2 + offsetLeft,
296
+ top: height / 2 - dimensions.height / 2 + offsetTop,
297
+ visibility: "visible"
298
+ };
299
+ } else {
300
+ return {
301
+ left: width / 2 + offsetLeft,
302
+ top: height / 2 + offsetTop,
303
+ visibility: "hidden"
304
+ };
305
+ }
306
+ default:
307
+ throw Error(
308
+ "Popup getPositionRelativeToAnchor only supported placement values are below and right"
309
+ );
632
310
  }
633
311
  };
634
- var useCascade = ({
635
- id: rootId,
636
- onActivate,
637
- onMouseEnterItem,
638
- position: { x: posX, y: posY }
312
+ var useAnchoredPosition = ({
313
+ anchorElement,
314
+ minWidth,
315
+ offsetLeft = 0,
316
+ offsetTop = 0,
317
+ placement,
318
+ position: positionProp
639
319
  }) => {
640
- const [, forceRefresh] = useState4({});
641
- const openMenus = useRef5([
642
- { id: rootId, left: posX, top: posY }
643
- ]);
644
- const menuIsOpen = useCallback5(
645
- (menuId) => openMenus.current.findIndex((menu) => menu.id === menuId) !== -1,
646
- []
647
- );
648
- const getOpenMenuStatus = useCallback5((menuId) => {
649
- const state = menuState.current[menuId];
650
- if (state === void 0) {
651
- throw Error(`getOpenMenuState no entry for menu ${menuId}`);
652
- }
653
- return state;
654
- }, []);
655
- const setOpenMenus = useCallback5((menus) => {
656
- openMenus.current = menus;
657
- forceRefresh({});
658
- }, []);
659
- const menuOpenPendingTimeout = useRef5();
660
- const menuClosePendingTimeout = useRef5();
661
- const menuState = useRef5({ [rootId]: "no-popup" });
662
- const openMenu = useCallback5(
663
- (hostMenuId = rootId, targetMenuId, itemId = null) => {
664
- if (hostMenuId === rootId && itemId === null) {
665
- setOpenMenus([{ id: rootId, left: posX, top: posY }]);
666
- } else {
667
- menuState.current[hostMenuId] = "popup-open";
668
- const el = document.getElementById(itemId);
669
- if (el !== null) {
670
- const { left, top } = getPosition(el, openMenus.current);
671
- setOpenMenus(
672
- openMenus.current.concat({ id: targetMenuId, left, top })
673
- );
674
- } else {
675
- throw Error(`openMenu no menuItem ${itemId}`);
676
- }
677
- }
678
- },
679
- [rootId, posX, posY, setOpenMenus]
680
- );
681
- const closeMenu = useCallback5(
682
- (menuId) => {
683
- if (menuId === rootId) {
684
- setOpenMenus([]);
685
- } else {
686
- const menus = openMenus.current.slice();
687
- const lastMenu = menus.pop();
688
- menuState.current[lastMenu.id] = "no-popup";
689
- const parentMenu = menus.at(-1);
690
- if (parentMenu) {
691
- menuState.current[parentMenu.id] = "no-popup";
692
- }
693
- setOpenMenus(menus);
694
- }
695
- },
696
- [rootId, setOpenMenus]
697
- );
698
- const closeMenus = useCallback5(
699
- (menuItemId) => {
700
- const menus = openMenus.current.slice();
701
- const menuItemMenuId = menuItemId.slice(9);
702
- let { id: lastMenuId } = menus.at(-1);
703
- while (menus.length > 1 && !menuItemMenuId.startsWith(lastMenuId)) {
704
- const parentMenuId = getHostMenuId(lastMenuId, rootId);
705
- menus.pop();
706
- menuState.current[lastMenuId] = "no-popup";
707
- menuState.current[parentMenuId] = "no-popup";
708
- ({ id: lastMenuId } = menus[menus.length - 1]);
709
- }
710
- if (menus.length < openMenus.current.length) {
711
- setOpenMenus(menus);
712
- }
713
- },
714
- [rootId, setOpenMenus]
715
- );
716
- const clearAnyScheduledOpenTasks = useCallback5(() => {
717
- if (menuOpenPendingTimeout.current) {
718
- clearTimeout(menuOpenPendingTimeout.current);
719
- menuOpenPendingTimeout.current = void 0;
720
- }
721
- }, []);
722
- const scheduleOpen = useCallback5(
723
- (hostMenuId, targetMenuId, menuItemId, delay = 300) => {
724
- clearAnyScheduledOpenTasks();
725
- menuOpenPendingTimeout.current = window.setTimeout(() => {
726
- closeMenus(menuItemId);
727
- menuState.current[hostMenuId] = "popup-open";
728
- menuState.current[targetMenuId] = "no-popup";
729
- openMenu(hostMenuId, targetMenuId, menuItemId);
730
- }, delay);
731
- },
732
- [clearAnyScheduledOpenTasks, closeMenus, openMenu]
733
- );
734
- const scheduleClose = useCallback5(
735
- (hostMenuId, openMenuId, itemId) => {
736
- menuState.current[openMenuId] = "pending-close";
737
- menuClosePendingTimeout.current = window.setTimeout(() => {
738
- closeMenus(itemId);
739
- }, 400);
740
- },
741
- [closeMenus]
742
- );
743
- const handleRender = useCallback5(() => {
744
- const { current: menus } = openMenus;
745
- const menu = menus.at(-1);
746
- const el = menu ? document.getElementById(menu.id) : void 0;
747
- if (el) {
748
- const { right, bottom } = el.getBoundingClientRect();
749
- const { clientHeight, clientWidth } = document.body;
750
- if (right > clientWidth) {
751
- const newMenus = menus.length > 1 ? flipSides(rootId, menus) : nudgeLeft(menus, right - clientWidth);
752
- setOpenMenus(newMenus);
753
- } else if (bottom > clientHeight) {
754
- const newMenus = nudgeUp(menus, bottom - clientHeight);
755
- setOpenMenus(newMenus);
756
- }
757
- if (typeof el.tabIndex === "number") {
758
- el.focus();
759
- }
320
+ const popupRef = useRef(null);
321
+ const [position, setPosition] = useState(positionProp);
322
+ useLayoutEffect(() => {
323
+ if (placement === "absolute" && positionProp) {
324
+ setPosition(positionProp);
325
+ } else if (anchorElement.current && placement !== "auto") {
326
+ const dimensions = popupRef.current === null ? void 0 : popupRef.current.getBoundingClientRect();
327
+ const position2 = getPositionRelativeToAnchor(
328
+ anchorElement.current,
329
+ placement,
330
+ offsetLeft,
331
+ offsetTop,
332
+ minWidth,
333
+ dimensions
334
+ );
335
+ setPosition(position2);
760
336
  }
761
- }, [rootId, setOpenMenus]);
762
- const triggerChildMenu = useCallback5(
763
- (menuItemEl, immediate = false) => {
764
- const { hostMenuId, targetMenuId, menuItemId, isGroup, isOpen } = getMenuItemDetails(menuItemEl, rootId);
765
- const {
766
- current: { [hostMenuId]: state }
767
- } = menuState;
768
- const delay = immediate ? 0 : void 0;
769
- if (state === "no-popup" && isGroup) {
770
- menuState.current[hostMenuId] = "popup-pending";
771
- scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
772
- } else if (state === "popup-pending" && !isGroup) {
773
- menuState.current[hostMenuId] = "no-popup";
774
- clearTimeout(menuOpenPendingTimeout.current);
775
- menuOpenPendingTimeout.current = void 0;
776
- } else if (state === "popup-pending" && isGroup) {
777
- clearTimeout(menuOpenPendingTimeout.current);
778
- scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
779
- } else if (state === "popup-open") {
780
- if (menuIsOpen(targetMenuId)) {
781
- const menuStatus = getOpenMenuStatus(targetMenuId);
782
- closeMenus(menuItemId);
783
- switch (menuStatus) {
784
- case "pending-close":
785
- clearTimeout(menuClosePendingTimeout.current);
786
- menuClosePendingTimeout.current = void 0;
787
- menuState.current[targetMenuId] = "no-popup";
788
- clearAnyScheduledOpenTasks();
789
- break;
790
- default:
791
- }
792
- } else {
793
- const [parentOfLastOpenedMenu, lastOpenedMenu] = openMenus.current.slice(-2);
794
- if (parentOfLastOpenedMenu.id === hostMenuId && menuState.current[lastOpenedMenu.id] !== "pending-close") {
795
- scheduleClose(hostMenuId, lastOpenedMenu.id, menuItemId);
796
- if (isGroup && !isOpen) {
797
- scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
798
- }
799
- } else if (parentOfLastOpenedMenu.id === hostMenuId && isGroup && menuItemId !== lastOpenedMenu.id && menuState.current[lastOpenedMenu.id] === "pending-close") {
800
- scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
801
- } else if (isGroup) {
802
- scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
803
- } else if (!(menuState.current[lastOpenedMenu.id] === "pending-close")) {
804
- closeMenus(menuItemId);
805
- }
806
- }
807
- }
808
- if (state === "pending-close") {
809
- clearAnyScheduledOpenTasks();
810
- clearTimeout(menuClosePendingTimeout.current);
811
- menuClosePendingTimeout.current = void 0;
812
- menuState.current[hostMenuId] = "popup-open";
337
+ }, [anchorElement, minWidth, offsetLeft, offsetTop, placement, positionProp]);
338
+ const popupCallbackRef = useCallback(
339
+ (el) => {
340
+ popupRef.current = el;
341
+ if (el && placement === "center" && anchorElement.current) {
342
+ const { height, width } = el.getBoundingClientRect();
343
+ setPosition(
344
+ getPositionRelativeToAnchor(
345
+ anchorElement.current,
346
+ placement,
347
+ offsetLeft,
348
+ offsetTop,
349
+ void 0,
350
+ { height, width }
351
+ )
352
+ );
813
353
  }
814
354
  },
815
- [
816
- clearAnyScheduledOpenTasks,
817
- closeMenus,
818
- getOpenMenuStatus,
819
- menuIsOpen,
820
- rootId,
821
- scheduleClose,
822
- scheduleOpen
823
- ]
824
- );
825
- const listItemProps = useMemo4(
826
- () => ({
827
- onMouseEnter: (evt) => {
828
- const menuItemEl = closestListItem(evt.target);
829
- triggerChildMenu(menuItemEl);
830
- onMouseEnterItem(evt, menuItemEl.id);
831
- },
832
- onClick: (evt) => {
833
- const listItemEl = closestListItem(evt.target);
834
- const { isGroup, menuItemId } = getMenuItemDetails(listItemEl, rootId);
835
- if (isGroup) {
836
- triggerChildMenu(listItemEl);
837
- } else {
838
- onActivate(menuItemId);
839
- }
840
- }
841
- }),
842
- [onActivate, onMouseEnterItem, rootId, triggerChildMenu]
355
+ [anchorElement, offsetLeft, offsetTop, placement]
843
356
  );
844
357
  return {
845
- closeMenu,
846
- handleRender,
847
- listItemProps,
848
- openMenu: triggerChildMenu,
849
- openMenus: openMenus.current
358
+ position,
359
+ popupRef: placement === "center" ? popupCallbackRef : void 0
850
360
  };
851
361
  };
852
362
 
853
- // src/menu/ContextMenu.tsx
854
- import { useId as useId2 } from "@vuu-ui/vuu-layout";
855
- import { Fragment as Fragment2, jsx as jsx5 } from "react/jsx-runtime";
856
- import { createElement } from "react";
857
- var noop = () => void 0;
858
- var ContextMenu = ({
859
- PortalProps: PortalProps2,
860
- activatedByKeyboard,
861
- children: childrenProp,
363
+ // src/popup/Popup.tsx
364
+ import { jsx as jsx2 } from "react/jsx-runtime";
365
+ var PopupComponent2 = ({
366
+ children,
862
367
  className,
863
- id: idProp,
864
- onClose = () => void 0,
865
- position = { x: 0, y: 0 },
866
- style,
867
- ...menuListProps
368
+ anchorElement,
369
+ minWidth,
370
+ placement,
371
+ position: positionProp
868
372
  }) => {
869
- const closeHandlerRef = useRef6(onClose);
870
- closeHandlerRef.current = onClose;
871
- const id = useId2(idProp);
872
- const closeMenuRef = useRef6(noop);
873
- const [menus, actions] = useItemsWithIdsNext(childrenProp, id);
874
- const navigatingWithKeyboard = useRef6(activatedByKeyboard);
875
- const handleMouseEnterItem = useCallback6(() => {
876
- navigatingWithKeyboard.current = false;
877
- }, []);
878
- const handleActivate = useCallback6(
879
- (menuItemId) => {
880
- const actionId = menuItemId.slice(9);
881
- const { action, options } = actions[actionId];
882
- closeMenuRef.current(id);
883
- onClose({
884
- type: "menu-action",
885
- menuId: action,
886
- options
887
- });
888
- },
889
- [actions, id, onClose]
890
- );
891
- const {
892
- closeMenu,
893
- listItemProps,
894
- openMenu: onOpenMenu,
895
- openMenus,
896
- handleRender
897
- } = useCascade({
898
- // FIXME
899
- id: `${id}`,
900
- onActivate: handleActivate,
901
- onMouseEnterItem: handleMouseEnterItem,
902
- position
373
+ const { popupRef, position } = useAnchoredPosition({
374
+ anchorElement,
375
+ minWidth,
376
+ placement,
377
+ position: positionProp
903
378
  });
904
- closeMenuRef.current = closeMenu;
905
- const handleCloseMenu = () => {
906
- navigatingWithKeyboard.current = true;
907
- closeMenu();
908
- };
909
- const handleHighlightMenuItem = () => {
910
- };
911
- const lastMenu = openMenus.length - 1;
912
- const getChildMenuId = (i) => {
913
- if (i >= lastMenu) {
914
- return void 0;
915
- } else {
916
- const { id: id2 } = openMenus[i + 1];
917
- return id2;
918
- }
919
- };
920
- return /* @__PURE__ */ jsx5(Fragment2, { children: openMenus.map(({ id: menuId, left, top }, i, all) => {
921
- const childMenuId = getChildMenuId(i);
922
- return /* @__PURE__ */ createElement(Portal, { ...PortalProps2, key: i, onRender: handleRender }, /* @__PURE__ */ jsx5(
923
- PopupComponent,
924
- {
925
- anchorElement: { current: document.body },
926
- placement: "absolute",
927
- position: { left, top },
928
- children: /* @__PURE__ */ createElement(
929
- MenuList,
930
- {
931
- ...menuListProps,
932
- activatedByKeyboard: navigatingWithKeyboard.current,
933
- childMenuShowing: childMenuId,
934
- className,
935
- id: menuId,
936
- isRoot: i === 0,
937
- key: i,
938
- listItemProps,
939
- onActivate: handleActivate,
940
- onHighlightMenuItem: handleHighlightMenuItem,
941
- onCloseMenu: handleCloseMenu,
942
- openMenu: onOpenMenu,
943
- style,
944
- tabIndex: i === all.length - 1 ? 0 : void 0
945
- },
946
- menus[menuId]
947
- )
948
- }
949
- ));
950
- }) });
379
+ return position === void 0 ? null : /* @__PURE__ */ jsx2("div", { className: cx4(`vuuPortal`, className), ref: popupRef, style: position, children });
951
380
  };
952
- ContextMenu.displayName = "ContextMenu";
953
381
 
954
- // src/menu/context-menu-provider.tsx
955
- import { createContext, useCallback as useCallback7, useMemo as useMemo5 } from "react";
956
- import { jsx as jsx6 } from "react/jsx-runtime";
957
- var ContextMenuContext = createContext(
958
- null
959
- );
960
- var Provider = ({
382
+ // src/dialog/Dialog.tsx
383
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
384
+ var classBase2 = "vuuDialog";
385
+ var AnchorBody = { current: document.body };
386
+ var EMPTY_PROPS = {};
387
+ var Dialog = ({
388
+ PopupProps = EMPTY_PROPS,
961
389
  children,
962
- context,
963
- menuActionHandler,
964
- menuBuilder
390
+ className,
391
+ isOpen = false,
392
+ onClose,
393
+ style,
394
+ title,
395
+ hideCloseButton = false,
396
+ ...htmlAttributes
965
397
  }) => {
966
- const menuBuilders = useMemo5(() => {
967
- if ((context == null ? void 0 : context.menuBuilders) && menuBuilder) {
968
- return context.menuBuilders.concat(menuBuilder);
969
- } else if (menuBuilder) {
970
- return [menuBuilder];
971
- } else {
972
- return (context == null ? void 0 : context.menuBuilders) || [];
973
- }
974
- }, [context, menuBuilder]);
975
- const handleMenuAction = useCallback7(
976
- (reason) => {
977
- var _a;
978
- if (menuActionHandler == null ? void 0 : menuActionHandler(reason)) {
979
- return true;
398
+ const {
399
+ anchorElement = AnchorBody,
400
+ offsetLeft = 0,
401
+ offsetTop = 0,
402
+ placement = "auto"
403
+ } = PopupProps;
404
+ const rootRef = useRef2(null);
405
+ const portalRef = useRef2(null);
406
+ const [themeClass, densityClass, dataMode] = useThemeAttributes();
407
+ const { position } = useAnchoredPosition({
408
+ anchorElement,
409
+ offsetLeft,
410
+ offsetTop,
411
+ placement
412
+ });
413
+ const close = useCallback2(() => {
414
+ onClose == null ? void 0 : onClose();
415
+ }, [onClose]);
416
+ useLayoutEffect2(() => {
417
+ if (rootRef.current) {
418
+ if (isOpen) {
419
+ rootRef.current.showModal();
420
+ const { left, top } = rootRef.current.getBoundingClientRect();
421
+ if (portalRef.current) {
422
+ portalRef.current.style.cssText = `left:-${left}px;position:absolute;top:-${top}px;`;
423
+ }
424
+ } else {
425
+ rootRef.current.close();
980
426
  }
981
- if ((_a = context == null ? void 0 : context.menuActionHandler) == null ? void 0 : _a.call(context, reason)) {
982
- return true;
427
+ if (placement.endsWith("center")) {
428
+ const { width } = rootRef.current.getBoundingClientRect();
429
+ rootRef.current.style.marginLeft = `-${width / 2}px`;
983
430
  }
984
- },
985
- [context, menuActionHandler]
986
- );
987
- return /* @__PURE__ */ jsx6(
988
- ContextMenuContext.Provider,
431
+ }
432
+ }, [isOpen, placement]);
433
+ return /* @__PURE__ */ jsxs2(
434
+ "dialog",
989
435
  {
990
- value: {
991
- menuActionHandler: handleMenuAction,
992
- menuBuilders
993
- },
994
- children
436
+ ...htmlAttributes,
437
+ className: cx5(classBase2, themeClass),
438
+ "data-mode": dataMode,
439
+ onClose: close,
440
+ id: "vuu-dialog",
441
+ ref: rootRef,
442
+ style: { ...style, ...position },
443
+ children: [
444
+ /* @__PURE__ */ jsx3(
445
+ DialogHeader,
446
+ {
447
+ hideCloseButton,
448
+ onClose: close,
449
+ title
450
+ }
451
+ ),
452
+ /* @__PURE__ */ jsx3("div", { className: `${classBase2}-body`, children }),
453
+ /* @__PURE__ */ jsx3("div", { id: "vuu-dialog-portal-root", ref: portalRef })
454
+ ]
995
455
  }
996
456
  );
997
457
  };
998
- var ContextMenuProvider = ({
999
- children,
1000
- label,
1001
- menuActionHandler,
1002
- menuBuilder
1003
- }) => {
1004
- return /* @__PURE__ */ jsx6(ContextMenuContext.Consumer, { children: (parentContext) => /* @__PURE__ */ jsx6(
1005
- Provider,
458
+
459
+ // src/dialog/useDialog.tsx
460
+ import { useCallback as useCallback3, useState as useState2 } from "react";
461
+ import { jsx as jsx4 } from "react/jsx-runtime";
462
+ var useDialog = () => {
463
+ const [dialogState, setDialogState] = useState2();
464
+ const handleClose = useCallback3(() => {
465
+ setDialogState(void 0);
466
+ }, []);
467
+ const dialog = dialogState ? /* @__PURE__ */ jsx4(
468
+ Dialog,
1006
469
  {
1007
- context: parentContext,
1008
- label,
1009
- menuActionHandler,
1010
- menuBuilder,
1011
- children
470
+ className: "vuDialog",
471
+ isOpen: true,
472
+ onClose: handleClose,
473
+ style: { maxHeight: 500 },
474
+ title: dialogState.title,
475
+ hideCloseButton: dialogState.hideCloseButton,
476
+ children: dialogState.content
1012
477
  }
1013
- ) });
478
+ ) : null;
479
+ return {
480
+ dialog,
481
+ setDialogState
482
+ };
1014
483
  };
1015
484
 
1016
- // src/menu/useContextMenu.tsx
1017
- import { isGroupMenuItemDescriptor } from "@vuu-ui/vuu-utils";
1018
- import { cloneElement, useCallback as useCallback9, useContext, useMemo as useMemo6 } from "react";
1019
-
1020
- // src/popup/popup-service.ts
1021
- import cx5 from "classnames";
1022
- import React3, {
1023
- createElement as createElement2
1024
- } from "react";
1025
- import ReactDOM2 from "react-dom";
1026
-
1027
- // src/portal-deprecated/render-portal.tsx
1028
- import * as ReactDOM from "react-dom";
1029
- import cx4 from "classnames";
1030
- var containerId = 1;
1031
- var getPortalContainer = ({
1032
- className,
1033
- dataMode,
1034
- x = 0,
1035
- y = 0,
1036
- win = window
1037
- }) => {
1038
- const el = win.document.createElement("div");
1039
- el.className = cx4(`vuuPopup ${containerId++}`, className);
1040
- el.style.cssText = `left:${x}px; top:${y}px;`;
1041
- if (dataMode) {
1042
- el.dataset.mode = dataMode;
1043
- }
1044
- win.document.body.appendChild(el);
1045
- return el;
1046
- };
1047
- var createContainer = (props) => getPortalContainer(props);
1048
- var renderPortal = (component, container, x, y, onRender) => {
1049
- container.style.cssText = `left:${x}px; top:${y}px;position: absolute;`;
1050
- ReactDOM.render(component, container, onRender);
1051
- };
485
+ // src/menu/ContextMenu.tsx
486
+ import { useId as useId2 } from "@vuu-ui/vuu-utils";
487
+ import { useCallback as useCallback7, useRef as useRef7 } from "react";
1052
488
 
1053
- // src/popup/popup-service.ts
1054
- var _dialogOpen = false;
1055
- var _popups = [];
1056
- var reasonIsMenuAction = (reason) => (reason == null ? void 0 : reason.type) === "menu-action";
1057
- var reasonIsClickAway = (reason) => (reason == null ? void 0 : reason.type) === "click-away";
1058
- function specialKeyHandler(e) {
1059
- if (e.key === "Esc") {
1060
- if (_popups.length) {
1061
- closeAllPopups();
1062
- } else if (_dialogOpen) {
1063
- const dialogRoot = document.body.querySelector(".vuuDialog");
1064
- if (dialogRoot) {
1065
- ReactDOM2.unmountComponentAtNode(dialogRoot);
1066
- }
1067
- }
1068
- }
489
+ // src/portal/Portal.tsx
490
+ import { useThemeAttributes as useThemeAttributes2 } from "@vuu-ui/vuu-utils";
491
+ import { useLayoutEffect as useLayoutEffect3, useRef as useRef3, useState as useState3 } from "react";
492
+ import { createPortal } from "react-dom";
493
+ function getContainer(container) {
494
+ return typeof container === "function" ? container() : container;
1069
495
  }
1070
- function outsideClickHandler(e) {
1071
- if (_popups.length) {
1072
- const popupContainers = document.body.querySelectorAll(
1073
- ".vuuPopup,#vuu-portal-root"
1074
- );
1075
- for (let i = 0; i < popupContainers.length; i++) {
1076
- if (popupContainers[i].contains(e.target)) {
1077
- return;
496
+ var DEFAULT_ID = ["vuu-dialog-portal-root", "vuu-portal-root"];
497
+ var getFirstAvailableElementById = (id) => {
498
+ if (Array.isArray(id)) {
499
+ for (const i of id) {
500
+ const element = document.getElementById(i);
501
+ if (element) {
502
+ return element;
1078
503
  }
1079
504
  }
1080
- closeAllPopups({ mouseEvt: e, type: "click-away" });
505
+ } else {
506
+ return document.getElementById(id);
1081
507
  }
1082
- }
1083
- function closeAllPopups(reason) {
1084
- if (_popups.length === 1) {
1085
- PopupService.hidePopup(reason, "anon", "all");
1086
- } else if (_popups.length) {
1087
- const popupContainers = document.body.querySelectorAll(".vuuPopup");
1088
- for (let i = 0; i < popupContainers.length; i++) {
1089
- ReactDOM2.unmountComponentAtNode(popupContainers[i]);
508
+ return null;
509
+ };
510
+ var Portal = ({
511
+ children,
512
+ container: containerProp = document.body,
513
+ id = DEFAULT_ID,
514
+ onRender,
515
+ open = true,
516
+ themeAttributes
517
+ }) => {
518
+ var _a;
519
+ const [mounted, setMounted] = useState3(false);
520
+ const portalRef = useRef3(null);
521
+ const container = (_a = getContainer(containerProp)) != null ? _a : document.body;
522
+ const [themeClass, densityClass, dataMode] = useThemeAttributes2(themeAttributes);
523
+ useLayoutEffect3(() => {
524
+ const root = getFirstAvailableElementById(id);
525
+ if (root) {
526
+ portalRef.current = root;
527
+ } else {
528
+ portalRef.current = document.createElement("div");
529
+ portalRef.current.id = typeof id === "string" ? id : id.length > 0 ? id.at(-1) : "vuu-portal-root";
1090
530
  }
1091
- popupClosed("*");
531
+ const el = portalRef.current;
532
+ if (!container.contains(el)) {
533
+ container.appendChild(el);
534
+ }
535
+ el.classList.add(themeClass, densityClass);
536
+ el.dataset.mode = dataMode;
537
+ setMounted(true);
538
+ }, [id, container, themeClass, densityClass, dataMode]);
539
+ useLayoutEffect3(() => {
540
+ requestAnimationFrame(() => {
541
+ onRender == null ? void 0 : onRender();
542
+ });
543
+ }, [onRender]);
544
+ if (open && mounted && portalRef.current && children) {
545
+ return createPortal(children, portalRef.current);
1092
546
  }
1093
- }
1094
- function dialogOpened() {
1095
- if (_dialogOpen === false) {
1096
- _dialogOpen = true;
1097
- window.addEventListener("keydown", specialKeyHandler, true);
1098
- }
1099
- }
1100
- function dialogClosed() {
1101
- if (_dialogOpen) {
1102
- _dialogOpen = false;
1103
- window.removeEventListener("keydown", specialKeyHandler, true);
547
+ return null;
548
+ };
549
+
550
+ // src/menu/MenuList.tsx
551
+ import cx6 from "clsx";
552
+ import React3, {
553
+ useLayoutEffect as useLayoutEffect4,
554
+ useMemo as useMemo3,
555
+ useRef as useRef5
556
+ } from "react";
557
+ import { useId } from "@vuu-ui/vuu-utils";
558
+
559
+ // src/menu/use-items-with-ids-next.ts
560
+ import React2, { useCallback as useCallback4, useMemo } from "react";
561
+ var isMenuItemGroup = (child) => child.type === MenuItemGroup || !!child.props["data-group"];
562
+ var getLabelFromChildren = (children) => {
563
+ if (Array.isArray(children) && isMenuItemLabel(children[0])) {
564
+ return children[0];
1104
565
  }
1105
- }
1106
- function popupOpened(name) {
1107
- if (_popups.indexOf(name) === -1) {
1108
- _popups.push(name);
1109
- if (_dialogOpen === false) {
1110
- window.addEventListener("keydown", specialKeyHandler, true);
1111
- window.addEventListener("click", outsideClickHandler, true);
566
+ };
567
+ var assignId = (child, path, group, hasSeparator = false) => {
568
+ const {
569
+ props: { children }
570
+ } = child;
571
+ return {
572
+ childWithId: React2.cloneElement(child, {
573
+ hasSeparator,
574
+ id: `${path}`,
575
+ key: path,
576
+ children: group ? getLabelFromChildren(children) : children
577
+ }),
578
+ grandChildren: group ? children : void 0
579
+ };
580
+ };
581
+ var useItemsWithIdsNext = (childrenProp, rootId) => {
582
+ const normalizeChildren = useCallback4(() => {
583
+ const collectChildren = (children, path = rootId, menus2 = {}, actions2 = {}) => {
584
+ const list = menus2[path] = [];
585
+ let idx = 0;
586
+ let hasSeparator = false;
587
+ React2.Children.forEach(children, (child) => {
588
+ if (isMenuItemLabel(child)) {
589
+ } else if (child.type === Separator) {
590
+ hasSeparator = true;
591
+ } else {
592
+ const hasChildItems = isMenuItemGroup(child);
593
+ const childPath = `${path}-${idx}`;
594
+ const {
595
+ props: { action, options }
596
+ } = child;
597
+ const { childWithId, grandChildren } = assignId(
598
+ child,
599
+ childPath,
600
+ hasChildItems,
601
+ hasSeparator
602
+ );
603
+ list.push(childWithId);
604
+ if (grandChildren) {
605
+ collectChildren(grandChildren, childPath, menus2, actions2);
606
+ } else {
607
+ actions2[childPath] = { action, options };
608
+ }
609
+ idx += 1;
610
+ hasSeparator = false;
611
+ }
612
+ });
613
+ return [menus2, actions2];
614
+ };
615
+ return collectChildren(childrenProp);
616
+ }, [rootId, childrenProp]);
617
+ const [menus, actions] = useMemo(
618
+ () => normalizeChildren(),
619
+ [normalizeChildren]
620
+ );
621
+ return [menus, actions];
622
+ };
623
+
624
+ // src/menu/use-keyboard-navigation.ts
625
+ import {
626
+ useCallback as useCallback5,
627
+ useMemo as useMemo2,
628
+ useRef as useRef4,
629
+ useState as useState4
630
+ } from "react";
631
+
632
+ // src/menu/utils.ts
633
+ var isRoot = (el) => el.closest(`[data-root='true']`) !== null;
634
+ var hasPopup = (el, idx) => {
635
+ var _a;
636
+ return el.ariaHasPopup === "true" && ((_a = el.dataset) == null ? void 0 : _a.idx) === `${idx}` || el.querySelector(`:scope > [data-index='${idx}'][aria-haspopup='true']`) !== null;
637
+ };
638
+
639
+ // src/menu/key-code.ts
640
+ function union(set1, ...sets) {
641
+ const result = new Set(set1);
642
+ for (const set of sets) {
643
+ for (const element of set) {
644
+ result.add(element);
1112
645
  }
1113
646
  }
647
+ return result;
1114
648
  }
1115
- function popupClosed(name) {
1116
- if (_popups.length) {
1117
- if (name === "*") {
1118
- _popups.length = 0;
1119
- } else {
1120
- const pos = _popups.indexOf(name);
1121
- if (pos !== -1) {
1122
- _popups.splice(pos, 1);
649
+ var Enter = "Enter";
650
+ var Delete = "Delete";
651
+ var actionKeys = /* @__PURE__ */ new Set([Enter, Delete]);
652
+ var focusKeys = /* @__PURE__ */ new Set(["Tab"]);
653
+ var arrowLeftRightKeys = /* @__PURE__ */ new Set(["ArrowRight", "ArrowLeft"]);
654
+ var verticalNavigationKeys = /* @__PURE__ */ new Set(["Home", "End", "ArrowDown", "ArrowUp"]);
655
+ var horizontalNavigationKeys = /* @__PURE__ */ new Set([
656
+ "Home",
657
+ "End",
658
+ "ArrowRight",
659
+ "ArrowLeft"
660
+ ]);
661
+ var functionKeys = /* @__PURE__ */ new Set([
662
+ "F1",
663
+ "F2",
664
+ "F3",
665
+ "F4",
666
+ "F5",
667
+ "F6",
668
+ "F7",
669
+ "F8",
670
+ "F9",
671
+ "F10",
672
+ "F11",
673
+ "F12"
674
+ ]);
675
+ var specialKeys = union(
676
+ actionKeys,
677
+ horizontalNavigationKeys,
678
+ verticalNavigationKeys,
679
+ arrowLeftRightKeys,
680
+ functionKeys,
681
+ focusKeys
682
+ );
683
+ var isNavigationKey = ({ key }, orientation = "vertical") => {
684
+ const navigationKeys = orientation === "vertical" ? verticalNavigationKeys : horizontalNavigationKeys;
685
+ return navigationKeys.has(key);
686
+ };
687
+
688
+ // src/menu/use-keyboard-navigation.ts
689
+ import { isValidNumber } from "@vuu-ui/vuu-utils";
690
+ var useKeyboardNavigation = ({
691
+ autoHighlightFirstItem = false,
692
+ count,
693
+ defaultHighlightedIdx,
694
+ highlightedIndex: highlightedIndexProp,
695
+ onActivate,
696
+ onHighlight,
697
+ // onKeyDown,
698
+ onCloseMenu,
699
+ onOpenMenu
700
+ }) => {
701
+ var _a;
702
+ if (isValidNumber(highlightedIndexProp) && isValidNumber(defaultHighlightedIdx)) {
703
+ throw Error(
704
+ "useKeyboardNavigation do not pass values for both highlightedIndex and defaultHighlightedIdx"
705
+ );
706
+ }
707
+ const controlledHighlighting = isValidNumber(highlightedIndexProp);
708
+ const highlightedIndexRef = useRef4(
709
+ (_a = defaultHighlightedIdx != null ? defaultHighlightedIdx : highlightedIndexProp) != null ? _a : autoHighlightFirstItem ? 0 : -1
710
+ );
711
+ const [, forceRender] = useState4(null);
712
+ const setHighlightedIdx = useCallback5(
713
+ (idx) => {
714
+ highlightedIndexRef.current = idx;
715
+ onHighlight == null ? void 0 : onHighlight(idx);
716
+ forceRender({});
717
+ },
718
+ [onHighlight]
719
+ );
720
+ const setHighlightedIndex = useCallback5(
721
+ (idx) => {
722
+ if (idx !== highlightedIndexRef.current) {
723
+ if (!controlledHighlighting) {
724
+ setHighlightedIdx(idx);
725
+ }
1123
726
  }
727
+ },
728
+ [controlledHighlighting, setHighlightedIdx]
729
+ );
730
+ const keyBoardNavigation = useRef4(true);
731
+ const ignoreFocus = useRef4(false);
732
+ const setIgnoreFocus = (value) => ignoreFocus.current = value;
733
+ const highlightedIndex = controlledHighlighting ? highlightedIndexProp : highlightedIndexRef.current;
734
+ const navigateChildldItems = useCallback5(
735
+ (e) => {
736
+ const nextIdx = nextItemIdx(count, e.key, highlightedIndexRef.current);
737
+ if (nextIdx !== highlightedIndexRef.current) {
738
+ setHighlightedIndex(nextIdx);
739
+ }
740
+ },
741
+ [count, setHighlightedIndex]
742
+ );
743
+ const handleKeyDown = useCallback5(
744
+ (e) => {
745
+ if (isNavigationKey(e)) {
746
+ e.preventDefault();
747
+ e.stopPropagation();
748
+ keyBoardNavigation.current = true;
749
+ navigateChildldItems(e);
750
+ } else if ((e.key === "ArrowRight" || e.key === "Enter") && hasPopup(e.target, highlightedIndex)) {
751
+ const menuEl = e.target;
752
+ const menuItemEl = menuEl.querySelector(
753
+ `:scope > [data-index='${highlightedIndex}']`
754
+ );
755
+ if (menuItemEl) {
756
+ onOpenMenu == null ? void 0 : onOpenMenu(menuItemEl, true);
757
+ }
758
+ } else if (e.key === "ArrowLeft" && !isRoot(e.target)) {
759
+ onCloseMenu(highlightedIndex);
760
+ } else if (e.key === "Enter") {
761
+ e.preventDefault();
762
+ e.stopPropagation();
763
+ onActivate && onActivate(highlightedIndex);
764
+ } else if (e.key === "Tab") {
765
+ onCloseMenu(-1);
766
+ }
767
+ },
768
+ [
769
+ highlightedIndex,
770
+ navigateChildldItems,
771
+ onActivate,
772
+ onCloseMenu,
773
+ onOpenMenu
774
+ ]
775
+ );
776
+ const listProps = useMemo2(
777
+ () => ({
778
+ onFocus: () => {
779
+ if (highlightedIndex === -1) {
780
+ setHighlightedIdx(0);
781
+ }
782
+ },
783
+ onKeyDown: handleKeyDown,
784
+ onMouseDownCapture: () => {
785
+ keyBoardNavigation.current = false;
786
+ setIgnoreFocus(true);
787
+ },
788
+ // onMouseEnter would seem less expensive but it misses some cases
789
+ onMouseMove: () => {
790
+ if (keyBoardNavigation.current) {
791
+ keyBoardNavigation.current = false;
792
+ }
793
+ },
794
+ onMouseLeave: () => {
795
+ keyBoardNavigation.current = true;
796
+ setIgnoreFocus(false);
797
+ setHighlightedIndex(-1);
798
+ }
799
+ }),
800
+ [handleKeyDown, highlightedIndex, setHighlightedIdx, setHighlightedIndex]
801
+ );
802
+ return {
803
+ focusVisible: keyBoardNavigation.current ? highlightedIndex : -1,
804
+ controlledHighlighting,
805
+ highlightedIndex,
806
+ setHighlightedIndex,
807
+ // keyBoardNavigation,
808
+ listProps,
809
+ setIgnoreFocus
810
+ };
811
+ };
812
+ function nextItemIdx(count, key, idx) {
813
+ if (key === "ArrowUp") {
814
+ if (idx > 0) {
815
+ return idx - 1;
816
+ } else {
817
+ return idx;
1124
818
  }
1125
- if (_popups.length === 0 && _dialogOpen === false) {
1126
- window.removeEventListener("keydown", specialKeyHandler, true);
1127
- window.removeEventListener("click", outsideClickHandler, true);
819
+ } else {
820
+ if (idx === null) {
821
+ return 0;
822
+ } else if (idx === count - 1) {
823
+ return idx;
824
+ } else {
825
+ return idx + 1;
1128
826
  }
1129
827
  }
1130
828
  }
1131
- var PopupComponent2 = ({
829
+
830
+ // src/menu/MenuList.tsx
831
+ import { Fragment, jsx as jsx5 } from "react/jsx-runtime";
832
+ var classBase3 = "vuuMenuList";
833
+ var Separator = () => /* @__PURE__ */ jsx5("li", { className: "vuuMenuItem-divider" });
834
+ var MenuItemGroup = () => null;
835
+ var MenuItem = ({
1132
836
  children,
1133
- position,
1134
- style
837
+ idx,
838
+ options,
839
+ ...props
1135
840
  }) => {
1136
- const className = cx5("hwPopup", "hwPopupContainer", position);
1137
- return createElement2("div", { className, style }, children);
841
+ return /* @__PURE__ */ jsx5("div", { ...props, children });
1138
842
  };
1139
- var incrementingKey = 1;
1140
- var PopupService = class {
1141
- static showPopup({
1142
- group = "all",
1143
- name = "anon",
1144
- left = 0,
1145
- position = "",
1146
- right = "auto",
1147
- top = 0,
1148
- width = "auto",
1149
- component
1150
- }) {
1151
- if (!component) {
1152
- throw Error(`PopupService showPopup, no component supplied`);
843
+ var MenuItemLabel = ({ children }) => /* @__PURE__ */ jsx5(Fragment, { children });
844
+ MenuItemLabel.displayName = "MenuItemLabel";
845
+ MenuItem.Label = MenuItemLabel;
846
+ var getDisplayName = (item) => React3.isValidElement(item) && typeof item.type !== "string" && "displayName" in item.type ? item.type.displayName : void 0;
847
+ var isMenuItemLabel = (item) => getDisplayName(item) === "MenuItemLabel";
848
+ var hasIcon = (child) => child.props["data-icon"];
849
+ var MenuList = ({
850
+ activatedByKeyboard,
851
+ childMenuShowing,
852
+ children,
853
+ className,
854
+ defaultHighlightedIdx,
855
+ highlightedIdx: highlightedIdxProp,
856
+ id: idProp,
857
+ isRoot: isRoot2,
858
+ listItemProps,
859
+ onHighlightMenuItem,
860
+ onActivate,
861
+ onCloseMenu,
862
+ openMenu: onOpenMenu,
863
+ ...props
864
+ }) => {
865
+ const id = useId(idProp);
866
+ const root = useRef5(null);
867
+ const mapIdxToId = useMemo3(() => /* @__PURE__ */ new Map(), []);
868
+ const handleActivate = (idx) => {
869
+ var _a;
870
+ const el = (_a = root.current) == null ? void 0 : _a.querySelector(`:scope > [data-index='${idx}']`);
871
+ (el == null ? void 0 : el.id) && (onActivate == null ? void 0 : onActivate(el.id));
872
+ };
873
+ const { focusVisible, highlightedIndex, listProps } = useKeyboardNavigation({
874
+ count: React3.Children.count(children),
875
+ defaultHighlightedIdx,
876
+ highlightedIndex: highlightedIdxProp,
877
+ onActivate: handleActivate,
878
+ onHighlight: onHighlightMenuItem,
879
+ onOpenMenu,
880
+ onCloseMenu
881
+ });
882
+ const appliedFocusVisible = childMenuShowing == void 0 ? focusVisible : -1;
883
+ useLayoutEffect4(() => {
884
+ var _a;
885
+ if (childMenuShowing === void 0 && activatedByKeyboard) {
886
+ (_a = root.current) == null ? void 0 : _a.focus();
1153
887
  }
1154
- if (typeof component.props.onClose === "function") {
1155
- PopupService.onClose = component.props.onClose;
1156
- } else {
1157
- PopupService.onClose = void 0;
888
+ }, [activatedByKeyboard, childMenuShowing]);
889
+ const getActiveDescendant = () => highlightedIndex === void 0 || highlightedIndex === -1 ? void 0 : mapIdxToId.get(highlightedIndex);
890
+ function renderContent() {
891
+ const propsCommonToAllListItems = {
892
+ ...listItemProps,
893
+ role: "menuitem"
894
+ };
895
+ const maybeIcon = (childElement, withIcon, iconName) => withIcon ? [
896
+ /* @__PURE__ */ jsx5(
897
+ "span",
898
+ {
899
+ className: "vuuIconContainer",
900
+ "data-icon": iconName
901
+ },
902
+ "icon"
903
+ )
904
+ ].concat(childElement) : childElement;
905
+ function addClonedChild(list, child, idx, withIcon) {
906
+ var _a;
907
+ const {
908
+ children: children2,
909
+ className: className2,
910
+ "data-icon": iconName,
911
+ id: itemId,
912
+ hasSeparator,
913
+ label,
914
+ ...props2
915
+ } = child.props;
916
+ const hasSubMenu = isMenuItemGroup(child);
917
+ const subMenuShowing = hasSubMenu && childMenuShowing === itemId;
918
+ const ariaControls = subMenuShowing ? `${id}-${itemId}` : void 0;
919
+ list.push(
920
+ /* @__PURE__ */ jsx5(
921
+ MenuItem,
922
+ {
923
+ ...props2,
924
+ ...propsCommonToAllListItems,
925
+ ...getMenuItemProps(
926
+ itemId,
927
+ idx,
928
+ (_a = child.key) != null ? _a : itemId,
929
+ highlightedIndex,
930
+ appliedFocusVisible,
931
+ className2,
932
+ hasSeparator
933
+ ),
934
+ "aria-controls": ariaControls,
935
+ "aria-haspopup": hasSubMenu || void 0,
936
+ "aria-expanded": subMenuShowing || void 0,
937
+ children: hasSubMenu ? maybeIcon(label != null ? label : children2, withIcon, iconName) : maybeIcon(children2, withIcon, iconName)
938
+ }
939
+ )
940
+ );
1158
941
  }
1159
- popupOpened(name);
1160
- document.addEventListener("keydown", PopupService.escapeKeyListener, true);
1161
- let el = document.body.querySelector(".vuuPopup." + group);
1162
- if (el === null) {
1163
- el = document.createElement("div");
1164
- el.className = "vuuPopup " + group;
1165
- document.body.appendChild(el);
942
+ const listItems = [];
943
+ if (children.length > 0) {
944
+ const withIcon = children.some(hasIcon);
945
+ children.forEach((child, idx) => {
946
+ addClonedChild(listItems, child, idx, withIcon);
947
+ });
1166
948
  }
1167
- const style = { width };
1168
- renderPortal(
1169
- createElement2(
1170
- PopupComponent2,
1171
- { key: incrementingKey++, position, style },
1172
- component
1173
- ),
1174
- el,
1175
- left,
1176
- top,
1177
- () => {
1178
- PopupService.keepWithinThePage(el, right);
1179
- }
1180
- );
949
+ return listItems;
950
+ }
951
+ return /* @__PURE__ */ jsx5(
952
+ "div",
953
+ {
954
+ ...props,
955
+ ...listProps,
956
+ "aria-activedescendant": getActiveDescendant(),
957
+ className: cx6(classBase3, className, {
958
+ [`${classBase3}-childMenuShowing`]: childMenuShowing !== void 0
959
+ }),
960
+ "data-root": isRoot2 || void 0,
961
+ id,
962
+ ref: root,
963
+ role: "menu",
964
+ children: renderContent()
965
+ }
966
+ );
967
+ };
968
+ var getMenuItemProps = (itemId, idx, key, highlightedIdx, focusVisible, className, hasSeparator) => ({
969
+ id: `menuitem-${itemId}`,
970
+ key: key != null ? key : idx,
971
+ "data-index": idx,
972
+ "data-highlighted": idx === highlightedIdx || void 0,
973
+ className: cx6("vuuMenuItem", className, {
974
+ "vuuMenuItem-separator": hasSeparator,
975
+ focusVisible: focusVisible === idx
976
+ })
977
+ });
978
+ MenuList.displayName = "MenuList";
979
+
980
+ // src/menu/use-cascade.ts
981
+ import {
982
+ useCallback as useCallback6,
983
+ useMemo as useMemo4,
984
+ useRef as useRef6,
985
+ useState as useState5
986
+ } from "react";
987
+
988
+ // src/menu/list-dom-utils.ts
989
+ var closestListItem = (el) => el == null ? void 0 : el.closest("[data-index],[aria-posinset]");
990
+
991
+ // src/menu/use-cascade.ts
992
+ var nudge = (menus, distance, pos) => {
993
+ return menus.map(
994
+ (m, i) => i === menus.length - 1 ? {
995
+ ...m,
996
+ [pos]: m[pos] - distance
997
+ } : m
998
+ );
999
+ };
1000
+ var nudgeLeft = (menus, distance) => nudge(menus, distance, "left");
1001
+ var nudgeUp = (menus, distance) => nudge(menus, distance, "top");
1002
+ var flipSides = (id, menus) => {
1003
+ const [parentMenu, menu] = menus.slice(-2);
1004
+ const el = document.getElementById(`${id}-${menu.id}`);
1005
+ if (el === null) {
1006
+ throw Error(`useCascade.flipSides element with id ${menu.id} not found`);
1007
+ }
1008
+ const { width } = el.getBoundingClientRect();
1009
+ return menus.map(
1010
+ (m) => m === menu ? {
1011
+ ...m,
1012
+ left: parentMenu.left - (width - 2)
1013
+ } : m
1014
+ );
1015
+ };
1016
+ var getPosition = (el, openMenus) => {
1017
+ const [{ left, top: menuTop }] = openMenus.slice(-1);
1018
+ const { offsetWidth: width, offsetTop: top } = el;
1019
+ return { left: left + width, top: top + menuTop };
1020
+ };
1021
+ var getHostMenuId = (id, rootId) => {
1022
+ const pos = id.lastIndexOf("-");
1023
+ if (id.startsWith("menuitem")) {
1024
+ return pos > -1 ? id.slice(9, pos) : rootId;
1025
+ } else {
1026
+ return pos > -1 ? id.slice(0, pos) : rootId;
1181
1027
  }
1182
- static escapeKeyListener(evt) {
1183
- if (evt.key === "Escape") {
1184
- PopupService.hidePopup({ type: "escape", event: evt });
1185
- }
1028
+ };
1029
+ var getTargetMenuId = (id) => id.slice(9);
1030
+ var getMenuItemDetails = ({ ariaExpanded, ariaHasPopup, id }, rootId) => {
1031
+ if (id.startsWith("menuitem")) {
1032
+ return {
1033
+ hostMenuId: getHostMenuId(id, rootId),
1034
+ targetMenuId: getTargetMenuId(id),
1035
+ menuItemId: id,
1036
+ isGroup: ariaHasPopup === "true",
1037
+ isOpen: ariaExpanded === "true"
1038
+ };
1039
+ } else {
1040
+ throw Error(`getMenuItemDetails #${id} is not a menuitem`);
1186
1041
  }
1187
- static hidePopup(reason, name = "anon", group = "all") {
1188
- var _a;
1189
- if (_popups.indexOf(name) !== -1) {
1190
- popupClosed(name);
1191
- const popupRoot = document.body.querySelector(`.vuuPopup.${group}`);
1192
- if (popupRoot) {
1193
- ReactDOM2.unmountComponentAtNode(popupRoot);
1194
- }
1042
+ };
1043
+ var useCascade = ({
1044
+ id: rootId,
1045
+ onActivate,
1046
+ onMouseEnterItem,
1047
+ position: { x: posX, y: posY }
1048
+ }) => {
1049
+ const [, forceRefresh] = useState5({});
1050
+ const openMenus = useRef6([
1051
+ { id: rootId, left: posX, top: posY }
1052
+ ]);
1053
+ const menuIsOpen = useCallback6(
1054
+ (menuId) => openMenus.current.findIndex((menu) => menu.id === menuId) !== -1,
1055
+ []
1056
+ );
1057
+ const getOpenMenuStatus = useCallback6((menuId) => {
1058
+ const state = menuState.current[menuId];
1059
+ if (state === void 0) {
1060
+ throw Error(`getOpenMenuState no entry for menu ${menuId}`);
1195
1061
  }
1196
- document.removeEventListener(
1197
- "keydown",
1198
- PopupService.escapeKeyListener,
1199
- true
1200
- );
1201
- (_a = PopupService == null ? void 0 : PopupService.onClose) == null ? void 0 : _a.call(PopupService, reason);
1202
- }
1203
- static keepWithinThePage(el, right = "auto") {
1204
- const target = el.querySelector(".vuuPopupContainer > *");
1205
- if (target) {
1206
- const {
1207
- top,
1208
- left,
1209
- width,
1210
- height,
1211
- right: currentRight
1212
- } = target.getBoundingClientRect();
1213
- const w = window.innerWidth;
1214
- const h = window.innerHeight;
1215
- const overflowH = h - (top + height);
1216
- if (overflowH < 0) {
1217
- target.style.top = Math.round(top) + overflowH + "px";
1062
+ return state;
1063
+ }, []);
1064
+ const setOpenMenus = useCallback6((menus) => {
1065
+ openMenus.current = menus;
1066
+ forceRefresh({});
1067
+ }, []);
1068
+ const menuOpenPendingTimeout = useRef6();
1069
+ const menuClosePendingTimeout = useRef6();
1070
+ const menuState = useRef6({ [rootId]: "no-popup" });
1071
+ const openMenu = useCallback6(
1072
+ (hostMenuId = rootId, targetMenuId, itemId = null) => {
1073
+ if (hostMenuId === rootId && itemId === null) {
1074
+ setOpenMenus([{ id: rootId, left: posX, top: posY }]);
1075
+ } else {
1076
+ menuState.current[hostMenuId] = "popup-open";
1077
+ const el = document.getElementById(itemId);
1078
+ if (el !== null) {
1079
+ const { left, top } = getPosition(el, openMenus.current);
1080
+ setOpenMenus(
1081
+ openMenus.current.concat({ id: targetMenuId, left, top })
1082
+ );
1083
+ } else {
1084
+ throw Error(`openMenu no menuItem ${itemId}`);
1085
+ }
1218
1086
  }
1219
- const overflowW = w - (left + width);
1220
- if (overflowW < 0) {
1221
- target.style.left = Math.round(left) + overflowW + "px";
1087
+ },
1088
+ [rootId, posX, posY, setOpenMenus]
1089
+ );
1090
+ const closeMenu = useCallback6(
1091
+ (menuId) => {
1092
+ if (menuId === rootId) {
1093
+ setOpenMenus([]);
1094
+ } else {
1095
+ const menus = openMenus.current.slice();
1096
+ const lastMenu = menus.pop();
1097
+ menuState.current[lastMenu.id] = "no-popup";
1098
+ const parentMenu = menus.at(-1);
1099
+ if (parentMenu) {
1100
+ menuState.current[parentMenu.id] = "no-popup";
1101
+ }
1102
+ setOpenMenus(menus);
1222
1103
  }
1223
- if (typeof right === "number" && right !== currentRight) {
1224
- const adjustment = right - currentRight;
1225
- target.style.left = left + adjustment + "px";
1104
+ },
1105
+ [rootId, setOpenMenus]
1106
+ );
1107
+ const closeMenus = useCallback6(
1108
+ (menuItemId) => {
1109
+ const menus = openMenus.current.slice();
1110
+ const menuItemMenuId = menuItemId.slice(9);
1111
+ let { id: lastMenuId } = menus.at(-1);
1112
+ while (menus.length > 1 && !menuItemMenuId.startsWith(lastMenuId)) {
1113
+ const parentMenuId = getHostMenuId(lastMenuId, rootId);
1114
+ menus.pop();
1115
+ menuState.current[lastMenuId] = "no-popup";
1116
+ menuState.current[parentMenuId] = "no-popup";
1117
+ ({ id: lastMenuId } = menus[menus.length - 1]);
1118
+ }
1119
+ if (menus.length < openMenus.current.length) {
1120
+ setOpenMenus(menus);
1226
1121
  }
1122
+ },
1123
+ [rootId, setOpenMenus]
1124
+ );
1125
+ const clearAnyScheduledOpenTasks = useCallback6(() => {
1126
+ if (menuOpenPendingTimeout.current) {
1127
+ clearTimeout(menuOpenPendingTimeout.current);
1128
+ menuOpenPendingTimeout.current = void 0;
1227
1129
  }
1228
- }
1229
- };
1230
- var DialogService = class {
1231
- static showDialog(dialog) {
1232
- const containerEl = ".vuuDialog";
1233
- const onClose = dialog.props.onClose;
1234
- dialogOpened();
1235
- ReactDOM2.render(
1236
- React3.cloneElement(dialog, {
1237
- container: containerEl,
1238
- onClose: () => {
1239
- DialogService.closeDialog();
1240
- if (onClose) {
1241
- onClose();
1130
+ }, []);
1131
+ const scheduleOpen = useCallback6(
1132
+ (hostMenuId, targetMenuId, menuItemId, delay = 300) => {
1133
+ clearAnyScheduledOpenTasks();
1134
+ menuOpenPendingTimeout.current = window.setTimeout(() => {
1135
+ closeMenus(menuItemId);
1136
+ menuState.current[hostMenuId] = "popup-open";
1137
+ menuState.current[targetMenuId] = "no-popup";
1138
+ openMenu(hostMenuId, targetMenuId, menuItemId);
1139
+ }, delay);
1140
+ },
1141
+ [clearAnyScheduledOpenTasks, closeMenus, openMenu]
1142
+ );
1143
+ const scheduleClose = useCallback6(
1144
+ (hostMenuId, openMenuId, itemId) => {
1145
+ menuState.current[openMenuId] = "pending-close";
1146
+ menuClosePendingTimeout.current = window.setTimeout(() => {
1147
+ closeMenus(itemId);
1148
+ }, 400);
1149
+ },
1150
+ [closeMenus]
1151
+ );
1152
+ const handleRender = useCallback6(() => {
1153
+ const { current: menus } = openMenus;
1154
+ const menu = menus.at(-1);
1155
+ const el = menu ? document.getElementById(menu.id) : void 0;
1156
+ if (el) {
1157
+ const { right, bottom } = el.getBoundingClientRect();
1158
+ const { clientHeight, clientWidth } = document.body;
1159
+ if (right > clientWidth) {
1160
+ const newMenus = menus.length > 1 ? flipSides(rootId, menus) : nudgeLeft(menus, right - clientWidth);
1161
+ setOpenMenus(newMenus);
1162
+ } else if (bottom > clientHeight) {
1163
+ const newMenus = nudgeUp(menus, bottom - clientHeight);
1164
+ setOpenMenus(newMenus);
1165
+ }
1166
+ if (typeof el.tabIndex === "number") {
1167
+ el.focus();
1168
+ }
1169
+ }
1170
+ }, [rootId, setOpenMenus]);
1171
+ const triggerChildMenu = useCallback6(
1172
+ (menuItemEl, immediate = false) => {
1173
+ const { hostMenuId, targetMenuId, menuItemId, isGroup, isOpen } = getMenuItemDetails(menuItemEl, rootId);
1174
+ const {
1175
+ current: { [hostMenuId]: state }
1176
+ } = menuState;
1177
+ const delay = immediate ? 0 : void 0;
1178
+ if (state === "no-popup" && isGroup) {
1179
+ menuState.current[hostMenuId] = "popup-pending";
1180
+ scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
1181
+ } else if (state === "popup-pending" && !isGroup) {
1182
+ menuState.current[hostMenuId] = "no-popup";
1183
+ clearTimeout(menuOpenPendingTimeout.current);
1184
+ menuOpenPendingTimeout.current = void 0;
1185
+ } else if (state === "popup-pending" && isGroup) {
1186
+ clearTimeout(menuOpenPendingTimeout.current);
1187
+ scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
1188
+ } else if (state === "popup-open") {
1189
+ if (menuIsOpen(targetMenuId)) {
1190
+ const menuStatus = getOpenMenuStatus(targetMenuId);
1191
+ closeMenus(menuItemId);
1192
+ switch (menuStatus) {
1193
+ case "pending-close":
1194
+ clearTimeout(menuClosePendingTimeout.current);
1195
+ menuClosePendingTimeout.current = void 0;
1196
+ menuState.current[targetMenuId] = "no-popup";
1197
+ clearAnyScheduledOpenTasks();
1198
+ break;
1199
+ default:
1200
+ }
1201
+ } else {
1202
+ const [parentOfLastOpenedMenu, lastOpenedMenu] = openMenus.current.slice(-2);
1203
+ if (parentOfLastOpenedMenu.id === hostMenuId && menuState.current[lastOpenedMenu.id] !== "pending-close") {
1204
+ scheduleClose(hostMenuId, lastOpenedMenu.id, menuItemId);
1205
+ if (isGroup && !isOpen) {
1206
+ scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
1207
+ }
1208
+ } else if (parentOfLastOpenedMenu.id === hostMenuId && isGroup && menuItemId !== lastOpenedMenu.id && menuState.current[lastOpenedMenu.id] === "pending-close") {
1209
+ scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
1210
+ } else if (isGroup) {
1211
+ scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
1212
+ } else if (!(menuState.current[lastOpenedMenu.id] === "pending-close")) {
1213
+ closeMenus(menuItemId);
1242
1214
  }
1243
1215
  }
1244
- }),
1245
- document.body.querySelector(containerEl)
1246
- );
1247
- }
1248
- static closeDialog() {
1249
- dialogClosed();
1250
- const dialogRoot = document.body.querySelector(".vuuDialog");
1251
- if (dialogRoot) {
1252
- ReactDOM2.unmountComponentAtNode(dialogRoot);
1253
- }
1254
- }
1216
+ }
1217
+ if (state === "pending-close") {
1218
+ clearAnyScheduledOpenTasks();
1219
+ clearTimeout(menuClosePendingTimeout.current);
1220
+ menuClosePendingTimeout.current = void 0;
1221
+ menuState.current[hostMenuId] = "popup-open";
1222
+ }
1223
+ },
1224
+ [
1225
+ clearAnyScheduledOpenTasks,
1226
+ closeMenus,
1227
+ getOpenMenuStatus,
1228
+ menuIsOpen,
1229
+ rootId,
1230
+ scheduleClose,
1231
+ scheduleOpen
1232
+ ]
1233
+ );
1234
+ const listItemProps = useMemo4(
1235
+ () => ({
1236
+ onMouseEnter: (evt) => {
1237
+ const menuItemEl = closestListItem(evt.target);
1238
+ triggerChildMenu(menuItemEl);
1239
+ onMouseEnterItem(evt, menuItemEl.id);
1240
+ },
1241
+ onClick: (evt) => {
1242
+ const listItemEl = closestListItem(evt.target);
1243
+ const { isGroup, menuItemId } = getMenuItemDetails(listItemEl, rootId);
1244
+ if (isGroup) {
1245
+ triggerChildMenu(listItemEl);
1246
+ } else {
1247
+ onActivate(menuItemId);
1248
+ }
1249
+ }
1250
+ }),
1251
+ [onActivate, onMouseEnterItem, rootId, triggerChildMenu]
1252
+ );
1253
+ return {
1254
+ closeMenu,
1255
+ handleRender,
1256
+ listItemProps,
1257
+ openMenu: triggerChildMenu,
1258
+ openMenus: openMenus.current
1259
+ };
1255
1260
  };
1256
1261
 
1257
- // src/popup/Popup.tsx
1258
- import cx6 from "classnames";
1259
-
1260
- // src/popup/useAnchoredPosition.ts
1261
- import { useCallback as useCallback8, useLayoutEffect as useLayoutEffect3, useRef as useRef7, useState as useState5 } from "react";
1262
- var getPositionRelativeToAnchor = (anchorElement, placement, offsetLeft, offsetTop, minWidth, dimensions) => {
1263
- const { bottom, height, left, right, top, width } = anchorElement.getBoundingClientRect();
1264
- switch (placement) {
1265
- case "below":
1266
- return { left: left + offsetLeft, top: bottom + offsetTop };
1267
- case "right":
1268
- return { left: right + offsetLeft, top: top + offsetTop };
1269
- case "below-center":
1270
- return { left: left + width / 2 + offsetLeft, top: bottom + offsetTop };
1271
- case "below-right":
1272
- return { left, minWidth, top: bottom + offsetTop };
1273
- case "below-full-width":
1274
- return {
1275
- left: left + offsetLeft,
1276
- minWidth,
1277
- top: bottom + offsetTop,
1278
- width
1279
- };
1280
- case "center":
1281
- if (dimensions) {
1282
- return {
1283
- left: width / 2 - dimensions.width / 2 + offsetLeft,
1284
- top: height / 2 - dimensions.height / 2 + offsetTop,
1285
- visibility: "visible"
1286
- };
1287
- } else {
1288
- return {
1289
- left: width / 2 + offsetLeft,
1290
- top: height / 2 + offsetTop,
1291
- visibility: "hidden"
1292
- };
1262
+ // src/menu/ContextMenu.tsx
1263
+ import { Fragment as Fragment2, jsx as jsx6 } from "react/jsx-runtime";
1264
+ import { createElement as createElement2 } from "react";
1265
+ var noop = () => void 0;
1266
+ var ContextMenu = ({
1267
+ PortalProps: PortalProps2,
1268
+ activatedByKeyboard,
1269
+ children: childrenProp,
1270
+ className,
1271
+ id: idProp,
1272
+ onClose = () => void 0,
1273
+ position = { x: 0, y: 0 },
1274
+ style,
1275
+ ...menuListProps
1276
+ }) => {
1277
+ const closeHandlerRef = useRef7(onClose);
1278
+ closeHandlerRef.current = onClose;
1279
+ const id = useId2(idProp);
1280
+ const closeMenuRef = useRef7(noop);
1281
+ const [menus, actions] = useItemsWithIdsNext(childrenProp, id);
1282
+ const navigatingWithKeyboard = useRef7(activatedByKeyboard);
1283
+ const handleMouseEnterItem = useCallback7(() => {
1284
+ navigatingWithKeyboard.current = false;
1285
+ }, []);
1286
+ const handleActivate = useCallback7(
1287
+ (menuItemId) => {
1288
+ const actionId = menuItemId.slice(9);
1289
+ const { action, options } = actions[actionId];
1290
+ closeMenuRef.current(id);
1291
+ onClose({
1292
+ type: "menu-action",
1293
+ menuId: action,
1294
+ options
1295
+ });
1296
+ },
1297
+ [actions, id, onClose]
1298
+ );
1299
+ const {
1300
+ closeMenu,
1301
+ listItemProps,
1302
+ openMenu: onOpenMenu,
1303
+ openMenus,
1304
+ handleRender
1305
+ } = useCascade({
1306
+ // FIXME
1307
+ id: `${id}`,
1308
+ onActivate: handleActivate,
1309
+ onMouseEnterItem: handleMouseEnterItem,
1310
+ position
1311
+ });
1312
+ closeMenuRef.current = closeMenu;
1313
+ const handleCloseMenu = () => {
1314
+ navigatingWithKeyboard.current = true;
1315
+ closeMenu();
1316
+ };
1317
+ const handleHighlightMenuItem = () => {
1318
+ };
1319
+ const lastMenu = openMenus.length - 1;
1320
+ const getChildMenuId = (i) => {
1321
+ if (i >= lastMenu) {
1322
+ return void 0;
1323
+ } else {
1324
+ const { id: id2 } = openMenus[i + 1];
1325
+ return id2;
1326
+ }
1327
+ };
1328
+ return /* @__PURE__ */ jsx6(Fragment2, { children: openMenus.map(({ id: menuId, left, top }, i, all) => {
1329
+ const childMenuId = getChildMenuId(i);
1330
+ return /* @__PURE__ */ createElement2(Portal, { ...PortalProps2, key: i, onRender: handleRender }, /* @__PURE__ */ jsx6(
1331
+ PopupComponent2,
1332
+ {
1333
+ anchorElement: { current: document.body },
1334
+ placement: "absolute",
1335
+ position: { left, top },
1336
+ children: /* @__PURE__ */ createElement2(
1337
+ MenuList,
1338
+ {
1339
+ ...menuListProps,
1340
+ activatedByKeyboard: navigatingWithKeyboard.current,
1341
+ childMenuShowing: childMenuId,
1342
+ className,
1343
+ id: menuId,
1344
+ isRoot: i === 0,
1345
+ key: i,
1346
+ listItemProps,
1347
+ onActivate: handleActivate,
1348
+ onHighlightMenuItem: handleHighlightMenuItem,
1349
+ onCloseMenu: handleCloseMenu,
1350
+ openMenu: onOpenMenu,
1351
+ style,
1352
+ tabIndex: i === all.length - 1 ? 0 : void 0
1353
+ },
1354
+ menus[menuId]
1355
+ )
1293
1356
  }
1294
- default:
1295
- throw Error(
1296
- "Popup getPositionRelativeToAnchor only supported placement values are below and right"
1297
- );
1298
- }
1357
+ ));
1358
+ }) });
1299
1359
  };
1300
- var useAnchoredPosition = ({
1301
- anchorElement,
1302
- minWidth,
1303
- offsetLeft = 0,
1304
- offsetTop = 0,
1305
- placement,
1306
- position: positionProp
1360
+ ContextMenu.displayName = "ContextMenu";
1361
+
1362
+ // src/menu/context-menu-provider.tsx
1363
+ import { createContext, useCallback as useCallback8, useMemo as useMemo5 } from "react";
1364
+ import { jsx as jsx7 } from "react/jsx-runtime";
1365
+ var ContextMenuContext = createContext(
1366
+ null
1367
+ );
1368
+ var Provider = ({
1369
+ children,
1370
+ context,
1371
+ menuActionHandler,
1372
+ menuBuilder
1307
1373
  }) => {
1308
- const popupRef = useRef7(null);
1309
- const [position, setPosition] = useState5(positionProp);
1310
- useLayoutEffect3(() => {
1311
- if (placement === "absolute" && positionProp) {
1312
- setPosition(positionProp);
1313
- } else if (anchorElement.current) {
1314
- const dimensions = popupRef.current === null ? void 0 : popupRef.current.getBoundingClientRect();
1315
- const position2 = getPositionRelativeToAnchor(
1316
- anchorElement.current,
1317
- placement,
1318
- offsetLeft,
1319
- offsetTop,
1320
- minWidth,
1321
- dimensions
1322
- );
1323
- setPosition(position2);
1374
+ const menuBuilders = useMemo5(() => {
1375
+ if ((context == null ? void 0 : context.menuBuilders) && menuBuilder) {
1376
+ return context.menuBuilders.concat(menuBuilder);
1377
+ } else if (menuBuilder) {
1378
+ return [menuBuilder];
1379
+ } else {
1380
+ return (context == null ? void 0 : context.menuBuilders) || [];
1324
1381
  }
1325
- }, [anchorElement, minWidth, offsetLeft, offsetTop, placement, positionProp]);
1326
- const popupCallbackRef = useCallback8(
1327
- (el) => {
1328
- popupRef.current = el;
1329
- if (el && placement === "center" && anchorElement.current) {
1330
- const { height, width } = el.getBoundingClientRect();
1331
- setPosition(
1332
- getPositionRelativeToAnchor(
1333
- anchorElement.current,
1334
- placement,
1335
- offsetLeft,
1336
- offsetTop,
1337
- void 0,
1338
- { height, width }
1339
- )
1340
- );
1382
+ }, [context, menuBuilder]);
1383
+ const handleMenuAction = useCallback8(
1384
+ (reason) => {
1385
+ var _a;
1386
+ if (menuActionHandler == null ? void 0 : menuActionHandler(reason)) {
1387
+ return true;
1388
+ }
1389
+ if ((_a = context == null ? void 0 : context.menuActionHandler) == null ? void 0 : _a.call(context, reason)) {
1390
+ return true;
1341
1391
  }
1342
1392
  },
1343
- [anchorElement, offsetLeft, offsetTop, placement]
1393
+ [context, menuActionHandler]
1394
+ );
1395
+ return /* @__PURE__ */ jsx7(
1396
+ ContextMenuContext.Provider,
1397
+ {
1398
+ value: {
1399
+ menuActionHandler: handleMenuAction,
1400
+ menuBuilders
1401
+ },
1402
+ children
1403
+ }
1344
1404
  );
1345
- return {
1346
- position,
1347
- popupRef: placement === "center" ? popupCallbackRef : void 0
1348
- };
1349
1405
  };
1350
-
1351
- // src/popup/Popup.tsx
1352
- import { jsx as jsx7 } from "react/jsx-runtime";
1353
- var PopupComponent = ({
1406
+ var ContextMenuProvider = ({
1354
1407
  children,
1355
- className,
1356
- anchorElement,
1357
- minWidth,
1358
- placement,
1359
- position: positionProp
1408
+ label,
1409
+ menuActionHandler,
1410
+ menuBuilder
1360
1411
  }) => {
1361
- const { popupRef, position } = useAnchoredPosition({
1362
- anchorElement,
1363
- minWidth,
1364
- placement,
1365
- position: positionProp
1366
- });
1367
- return position === void 0 ? null : /* @__PURE__ */ jsx7("div", { className: cx6(`vuuPortal`, className), ref: popupRef, style: position, children });
1412
+ return /* @__PURE__ */ jsx7(ContextMenuContext.Consumer, { children: (parentContext) => /* @__PURE__ */ jsx7(
1413
+ Provider,
1414
+ {
1415
+ context: parentContext,
1416
+ label,
1417
+ menuActionHandler,
1418
+ menuBuilder,
1419
+ children
1420
+ }
1421
+ ) });
1368
1422
  };
1369
1423
 
1370
1424
  // src/menu/useContextMenu.tsx
1371
- import { useThemeAttributes as useThemeAttributes2 } from "@vuu-ui/vuu-shell";
1425
+ import {
1426
+ isGroupMenuItemDescriptor,
1427
+ useThemeAttributes as useThemeAttributes3
1428
+ } from "@vuu-ui/vuu-utils";
1429
+ import { cloneElement, useCallback as useCallback9, useContext, useMemo as useMemo6 } from "react";
1372
1430
  import { jsx as jsx8 } from "react/jsx-runtime";
1373
1431
  var useContextMenu = (menuBuilder, menuActionHandler) => {
1374
1432
  const ctx = useContext(ContextMenuContext);
1375
- const [themeClass, densityClass, dataMode] = useThemeAttributes2();
1433
+ const [themeClass, densityClass, dataMode] = useThemeAttributes3();
1376
1434
  const themeAttributes = useMemo6(
1377
1435
  () => ({
1378
1436
  themeClass,
@@ -1498,14 +1556,14 @@ var showContextMenu = (e, menuDescriptors, handleContextMenuAction, {
1498
1556
  };
1499
1557
 
1500
1558
  // src/popup-menu/PopupMenu.tsx
1559
+ import { useId as useId3 } from "@vuu-ui/vuu-utils";
1560
+ import { Button as Button2 } from "@salt-ds/core";
1561
+ import cx7 from "clsx";
1501
1562
  import {
1502
1563
  useCallback as useCallback10,
1503
1564
  useRef as useRef8,
1504
1565
  useState as useState6
1505
1566
  } from "react";
1506
- import cx7 from "classnames";
1507
- import { Button as Button2 } from "@salt-ds/core";
1508
- import { useId as useId3 } from "@vuu-ui/vuu-layout";
1509
1567
  import { jsx as jsx9 } from "react/jsx-runtime";
1510
1568
  var classBase4 = "vuuPopupMenu";
1511
1569
  var getPosition2 = (element) => {
@@ -1606,16 +1664,16 @@ var PopupMenu = ({
1606
1664
  };
1607
1665
 
1608
1666
  // src/prompt/Prompt.tsx
1609
- import { useThemeAttributes as useThemeAttributes3 } from "@vuu-ui/vuu-shell";
1667
+ import { useThemeAttributes as useThemeAttributes4 } from "@vuu-ui/vuu-utils";
1610
1668
  import { Button as Button3 } from "@salt-ds/core";
1611
- import cx8 from "classnames";
1612
- import { useLayoutEffect as useLayoutEffect4, useRef as useRef9 } from "react";
1669
+ import cx8 from "clsx";
1670
+ import { useLayoutEffect as useLayoutEffect5, useRef as useRef9 } from "react";
1613
1671
  import { jsx as jsx10, jsxs as jsxs3 } from "react/jsx-runtime";
1614
1672
  var classBase5 = "vuuPrompt";
1615
- var AnchorBody = { current: document.body };
1616
- var EMPTY_PROPS = {};
1673
+ var AnchorBody2 = { current: document.body };
1674
+ var EMPTY_PROPS2 = {};
1617
1675
  var Prompt = ({
1618
- PopupProps = EMPTY_PROPS,
1676
+ PopupProps = EMPTY_PROPS2,
1619
1677
  cancelButtonLabel = "Cancel",
1620
1678
  confirmButtonLabel = "Confirm",
1621
1679
  icon,
@@ -1628,12 +1686,12 @@ var Prompt = ({
1628
1686
  ...htmlAttributes
1629
1687
  }) => {
1630
1688
  const {
1631
- anchorElement = AnchorBody,
1689
+ anchorElement = AnchorBody2,
1632
1690
  offsetLeft = 0,
1633
1691
  offsetTop = 0,
1634
1692
  placement = "below"
1635
1693
  } = PopupProps;
1636
- const [themeClass, densityClass, dataMode] = useThemeAttributes3();
1694
+ const [themeClass, densityClass, dataMode] = useThemeAttributes4();
1637
1695
  const { position } = useAnchoredPosition({
1638
1696
  anchorElement,
1639
1697
  offsetLeft,
@@ -1642,7 +1700,7 @@ var Prompt = ({
1642
1700
  });
1643
1701
  const rootRef = useRef9(null);
1644
1702
  const confirmRef = useRef9(null);
1645
- useLayoutEffect4(() => {
1703
+ useLayoutEffect5(() => {
1646
1704
  if (rootRef.current) {
1647
1705
  rootRef.current.showModal();
1648
1706
  if (confirmRef.current) {
@@ -1675,7 +1733,7 @@ var Prompt = ({
1675
1733
  };
1676
1734
 
1677
1735
  // src/tooltip/useAnchoredPosition.ts
1678
- import { useLayoutEffect as useLayoutEffect5, useState as useState7 } from "react";
1736
+ import { useLayoutEffect as useLayoutEffect6, useState as useState7 } from "react";
1679
1737
  var getPositionRelativeToAnchor2 = (anchorElement, placement, offsetLeft, offsetTop) => {
1680
1738
  const { bottom, height, left, right, top, width } = anchorElement.getBoundingClientRect();
1681
1739
  const midX = left + width / 2;
@@ -1702,7 +1760,7 @@ var useAnchoredPosition2 = ({
1702
1760
  placement
1703
1761
  }) => {
1704
1762
  const [position, setPosition] = useState7();
1705
- useLayoutEffect5(() => {
1763
+ useLayoutEffect6(() => {
1706
1764
  if (anchorElement.current) {
1707
1765
  const position2 = getPositionRelativeToAnchor2(
1708
1766
  anchorElement.current,
@@ -1717,7 +1775,7 @@ var useAnchoredPosition2 = ({
1717
1775
  };
1718
1776
 
1719
1777
  // src/tooltip/Tooltip.tsx
1720
- import cx9 from "classnames";
1778
+ import cx9 from "clsx";
1721
1779
  import { jsx as jsx11 } from "react/jsx-runtime";
1722
1780
  var classBase6 = "vuuTooltip";
1723
1781
  var Tooltip = ({
@@ -1757,8 +1815,8 @@ var Tooltip = ({
1757
1815
  };
1758
1816
 
1759
1817
  // src/tooltip/useTooltip.ts
1818
+ import { useId as useId4 } from "@vuu-ui/vuu-utils";
1760
1819
  import { useCallback as useCallback11, useRef as useRef10, useState as useState8 } from "react";
1761
- import { useId as useId4 } from "@vuu-ui/vuu-layout";
1762
1820
  var useTooltip = ({
1763
1821
  id: idProp,
1764
1822
  placement = "right",
@@ -1846,7 +1904,7 @@ var useTooltip = ({
1846
1904
 
1847
1905
  // src/notifications/NotificationsProvider.tsx
1848
1906
  import React4, { useState as useState9, useContext as useContext2, useCallback as useCallback12, useEffect } from "react";
1849
- import classNames from "classnames";
1907
+ import classNames from "clsx";
1850
1908
  import { getUniqueId } from "@vuu-ui/vuu-utils";
1851
1909
  import { jsx as jsx12, jsxs as jsxs4 } from "react/jsx-runtime";
1852
1910
  var toastOffsetTop = 60;
@@ -1953,7 +2011,7 @@ export {
1953
2011
  NotificationLevel,
1954
2012
  NotificationsContext,
1955
2013
  NotificationsProvider,
1956
- PopupComponent,
2014
+ PopupComponent2 as PopupComponent,
1957
2015
  PopupMenu,
1958
2016
  PopupService,
1959
2017
  Portal,