@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/cjs/index.js CHANGED
@@ -42,7 +42,7 @@ __export(src_exports, {
42
42
  NotificationLevel: () => NotificationLevel,
43
43
  NotificationsContext: () => NotificationsContext,
44
44
  NotificationsProvider: () => NotificationsProvider,
45
- PopupComponent: () => PopupComponent,
45
+ PopupComponent: () => PopupComponent2,
46
46
  PopupMenu: () => PopupMenu,
47
47
  PopupService: () => PopupService,
48
48
  Portal: () => Portal,
@@ -64,61 +64,13 @@ __export(src_exports, {
64
64
  module.exports = __toCommonJS(src_exports);
65
65
 
66
66
  // src/dialog/Dialog.tsx
67
- var import_lab = require("@salt-ds/lab");
68
- var import_classnames2 = __toESM(require("classnames"));
69
- var import_react2 = require("react");
70
-
71
- // src/portal/Portal.tsx
72
- var import_vuu_shell = require("@vuu-ui/vuu-shell");
73
- var import_react = require("react");
74
- var import_react_dom = require("react-dom");
75
- function getContainer(container) {
76
- return typeof container === "function" ? container() : container;
77
- }
78
- var DEFAULT_ID = "vuu-portal-root";
79
- var Portal = ({
80
- children,
81
- container: containerProp = document.body,
82
- id = DEFAULT_ID,
83
- onRender,
84
- open = true,
85
- themeAttributes
86
- }) => {
87
- var _a;
88
- const [mounted, setMounted] = (0, import_react.useState)(false);
89
- const portalRef = (0, import_react.useRef)(null);
90
- const container = (_a = getContainer(containerProp)) != null ? _a : document.body;
91
- const [themeClass, densityClass, dataMode] = (0, import_vuu_shell.useThemeAttributes)(themeAttributes);
92
- (0, import_react.useLayoutEffect)(() => {
93
- const root = document.getElementById(id);
94
- if (root) {
95
- portalRef.current = root;
96
- } else {
97
- portalRef.current = document.createElement("div");
98
- portalRef.current.id = id;
99
- }
100
- const el = portalRef.current;
101
- if (!container.contains(el)) {
102
- container.appendChild(el);
103
- }
104
- el.classList.add(themeClass, densityClass);
105
- el.dataset.mode = dataMode;
106
- setMounted(true);
107
- }, [id, container, themeClass, densityClass, dataMode]);
108
- (0, import_react.useLayoutEffect)(() => {
109
- requestAnimationFrame(() => {
110
- onRender == null ? void 0 : onRender();
111
- });
112
- }, [onRender]);
113
- if (open && mounted && portalRef.current && children) {
114
- return (0, import_react_dom.createPortal)(children, portalRef.current);
115
- }
116
- return null;
117
- };
67
+ var import_clsx5 = __toESM(require("clsx"));
68
+ var import_vuu_utils = require("@vuu-ui/vuu-utils");
69
+ var import_react3 = require("react");
118
70
 
119
71
  // src/dialog-header/DialogHeader.tsx
120
72
  var import_core = require("@salt-ds/core");
121
- var import_classnames = __toESM(require("classnames"));
73
+ var import_clsx = __toESM(require("clsx"));
122
74
  var import_jsx_runtime = require("react/jsx-runtime");
123
75
  var classBase = "vuuDialogHeader";
124
76
  var DialogHeader = ({
@@ -127,7 +79,7 @@ var DialogHeader = ({
127
79
  onClose,
128
80
  ...htmlAttributes
129
81
  }) => {
130
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { ...htmlAttributes, className: (0, import_classnames.default)(classBase, "vuuToolbarProxy"), children: [
82
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { ...htmlAttributes, className: (0, import_clsx.default)(classBase, "vuuToolbarProxy"), children: [
131
83
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_core.Text, { className: "dialogHeader", children: title }),
132
84
  !hideCloseButton && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
133
85
  import_core.Button,
@@ -142,1286 +94,1389 @@ var DialogHeader = ({
142
94
  ] });
143
95
  };
144
96
 
145
- // src/dialog/Dialog.tsx
146
- var import_jsx_runtime2 = require("react/jsx-runtime");
147
- var classBase2 = "vuuDialog";
148
- var Dialog = ({
149
- children,
97
+ // src/popup/popup-service.ts
98
+ var import_clsx3 = __toESM(require("clsx"));
99
+ var import_react = __toESM(require("react"));
100
+ var import_react_dom = __toESM(require("react-dom"));
101
+
102
+ // src/portal-deprecated/render-portal.tsx
103
+ var ReactDOM = __toESM(require("react-dom"));
104
+ var import_clsx2 = __toESM(require("clsx"));
105
+ var containerId = 1;
106
+ var getPortalContainer = ({
150
107
  className,
151
- isOpen = false,
152
- onClose,
153
- title,
154
- hideCloseButton = false,
155
- ...props
108
+ dataMode,
109
+ x = 0,
110
+ y = 0,
111
+ win = window
156
112
  }) => {
157
- const root = (0, import_react2.useRef)(null);
158
- const close = (0, import_react2.useCallback)(() => {
159
- onClose == null ? void 0 : onClose();
160
- }, [onClose]);
161
- if (!isOpen) {
162
- return null;
113
+ const el = win.document.createElement("div");
114
+ el.className = (0, import_clsx2.default)(`vuuPopup ${containerId++}`, className);
115
+ el.style.cssText = `left:${x}px; top:${y}px;`;
116
+ if (dataMode) {
117
+ el.dataset.mode = dataMode;
163
118
  }
164
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lab.Scrim, { className: `${classBase2}-scrim`, open: isOpen, autoFocusRef: root, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { ...props, className: (0, import_classnames2.default)(classBase2, className), ref: root, children: [
165
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
166
- DialogHeader,
167
- {
168
- hideCloseButton,
169
- onClose: close,
170
- title
171
- }
172
- ),
173
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: `${classBase2}-body`, children })
174
- ] }) }) });
175
- };
176
-
177
- // src/dialog/useDialog.tsx
178
- var import_react3 = require("react");
179
- var import_jsx_runtime3 = require("react/jsx-runtime");
180
- var useDialog = () => {
181
- const [dialogState, setDialogState] = (0, import_react3.useState)();
182
- const handleClose = (0, import_react3.useCallback)(() => {
183
- setDialogState(void 0);
184
- }, []);
185
- const dialog = dialogState ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
186
- Dialog,
187
- {
188
- className: "vuDialog",
189
- isOpen: true,
190
- onClose: handleClose,
191
- style: { maxHeight: 500 },
192
- title: dialogState.title,
193
- hideCloseButton: dialogState.hideCloseButton,
194
- children: dialogState.content
195
- }
196
- ) : null;
197
- return {
198
- dialog,
199
- setDialogState
200
- };
119
+ win.document.body.appendChild(el);
120
+ return el;
201
121
  };
202
-
203
- // src/menu/ContextMenu.tsx
204
- var import_react8 = require("react");
205
-
206
- // src/menu/MenuList.tsx
207
- var import_react6 = __toESM(require("react"));
208
- var import_classnames3 = __toESM(require("classnames"));
209
- var import_vuu_layout = require("@vuu-ui/vuu-layout");
210
-
211
- // src/menu/use-keyboard-navigation.ts
212
- var import_react4 = require("react");
213
-
214
- // src/menu/utils.ts
215
- var isRoot = (el) => el.closest(`[data-root='true']`) !== null;
216
- var hasPopup = (el, idx) => {
217
- var _a;
218
- return el.ariaHasPopup === "true" && ((_a = el.dataset) == null ? void 0 : _a.idx) === `${idx}` || el.querySelector(`:scope > [data-index='${idx}'][aria-haspopup='true']`) !== null;
122
+ var createContainer = (props) => getPortalContainer(props);
123
+ var renderPortal = (component, container, x, y, onRender) => {
124
+ container.style.cssText = `left:${x}px; top:${y}px;position: absolute;`;
125
+ ReactDOM.render(component, container, onRender);
219
126
  };
220
127
 
221
- // src/menu/key-code.ts
222
- function union(set1, ...sets) {
223
- const result = new Set(set1);
224
- for (const set of sets) {
225
- for (const element of set) {
226
- result.add(element);
128
+ // src/popup/popup-service.ts
129
+ var _dialogOpen = false;
130
+ var _popups = [];
131
+ var reasonIsMenuAction = (reason) => (reason == null ? void 0 : reason.type) === "menu-action";
132
+ var reasonIsClickAway = (reason) => (reason == null ? void 0 : reason.type) === "click-away";
133
+ function specialKeyHandler(e) {
134
+ if (e.key === "Esc") {
135
+ if (_popups.length) {
136
+ closeAllPopups();
137
+ } else if (_dialogOpen) {
138
+ const dialogRoot = document.body.querySelector(".vuuDialog");
139
+ if (dialogRoot) {
140
+ import_react_dom.default.unmountComponentAtNode(dialogRoot);
141
+ }
227
142
  }
228
143
  }
229
- return result;
230
144
  }
231
- var Enter = "Enter";
232
- var Delete = "Delete";
233
- var actionKeys = /* @__PURE__ */ new Set([Enter, Delete]);
234
- var focusKeys = /* @__PURE__ */ new Set(["Tab"]);
235
- var arrowLeftRightKeys = /* @__PURE__ */ new Set(["ArrowRight", "ArrowLeft"]);
236
- var verticalNavigationKeys = /* @__PURE__ */ new Set(["Home", "End", "ArrowDown", "ArrowUp"]);
237
- var horizontalNavigationKeys = /* @__PURE__ */ new Set([
238
- "Home",
239
- "End",
240
- "ArrowRight",
241
- "ArrowLeft"
242
- ]);
243
- var functionKeys = /* @__PURE__ */ new Set([
244
- "F1",
245
- "F2",
246
- "F3",
247
- "F4",
248
- "F5",
249
- "F6",
250
- "F7",
251
- "F8",
252
- "F9",
253
- "F10",
254
- "F11",
255
- "F12"
256
- ]);
257
- var specialKeys = union(
258
- actionKeys,
259
- horizontalNavigationKeys,
260
- verticalNavigationKeys,
261
- arrowLeftRightKeys,
262
- functionKeys,
263
- focusKeys
264
- );
265
- var isNavigationKey = ({ key }, orientation = "vertical") => {
266
- const navigationKeys = orientation === "vertical" ? verticalNavigationKeys : horizontalNavigationKeys;
267
- return navigationKeys.has(key);
268
- };
269
-
270
- // src/menu/use-keyboard-navigation.ts
271
- var import_vuu_utils = require("@vuu-ui/vuu-utils");
272
- var useKeyboardNavigation = ({
273
- autoHighlightFirstItem = false,
274
- count,
275
- defaultHighlightedIdx,
276
- highlightedIndex: highlightedIndexProp,
277
- onActivate,
278
- onHighlight,
279
- // onKeyDown,
280
- onCloseMenu,
281
- onOpenMenu
282
- }) => {
283
- var _a;
284
- if ((0, import_vuu_utils.isValidNumber)(highlightedIndexProp) && (0, import_vuu_utils.isValidNumber)(defaultHighlightedIdx)) {
285
- throw Error(
286
- "useKeyboardNavigation do not pass values for both highlightedIndex and defaultHighlightedIdx"
145
+ function outsideClickHandler(e) {
146
+ if (_popups.length) {
147
+ const popupContainers = document.body.querySelectorAll(
148
+ ".vuuPopup,#vuu-portal-root"
287
149
  );
150
+ for (let i = 0; i < popupContainers.length; i++) {
151
+ if (popupContainers[i].contains(e.target)) {
152
+ return;
153
+ }
154
+ }
155
+ closeAllPopups({ mouseEvt: e, type: "click-away" });
288
156
  }
289
- const controlledHighlighting = (0, import_vuu_utils.isValidNumber)(highlightedIndexProp);
290
- const highlightedIndexRef = (0, import_react4.useRef)(
291
- (_a = defaultHighlightedIdx != null ? defaultHighlightedIdx : highlightedIndexProp) != null ? _a : autoHighlightFirstItem ? 0 : -1
292
- );
293
- const [, forceRender] = (0, import_react4.useState)(null);
294
- const setHighlightedIdx = (0, import_react4.useCallback)(
295
- (idx) => {
296
- highlightedIndexRef.current = idx;
297
- onHighlight == null ? void 0 : onHighlight(idx);
298
- forceRender({});
299
- },
300
- [onHighlight]
301
- );
302
- const setHighlightedIndex = (0, import_react4.useCallback)(
303
- (idx) => {
304
- if (idx !== highlightedIndexRef.current) {
305
- if (!controlledHighlighting) {
306
- setHighlightedIdx(idx);
307
- }
157
+ }
158
+ function closeAllPopups(reason) {
159
+ if (_popups.length === 1) {
160
+ PopupService.hidePopup(reason, "anon", "all");
161
+ } else if (_popups.length) {
162
+ const popupContainers = document.body.querySelectorAll(".vuuPopup");
163
+ for (let i = 0; i < popupContainers.length; i++) {
164
+ import_react_dom.default.unmountComponentAtNode(popupContainers[i]);
165
+ }
166
+ popupClosed("*");
167
+ }
168
+ }
169
+ function dialogOpened() {
170
+ if (_dialogOpen === false) {
171
+ _dialogOpen = true;
172
+ window.addEventListener("keydown", specialKeyHandler, true);
173
+ }
174
+ }
175
+ function dialogClosed() {
176
+ if (_dialogOpen) {
177
+ _dialogOpen = false;
178
+ window.removeEventListener("keydown", specialKeyHandler, true);
179
+ }
180
+ }
181
+ function popupOpened(name) {
182
+ if (_popups.indexOf(name) === -1) {
183
+ _popups.push(name);
184
+ if (_dialogOpen === false) {
185
+ window.addEventListener("keydown", specialKeyHandler, true);
186
+ window.addEventListener("click", outsideClickHandler, true);
187
+ }
188
+ }
189
+ }
190
+ function popupClosed(name) {
191
+ if (_popups.length) {
192
+ if (name === "*") {
193
+ _popups.length = 0;
194
+ } else {
195
+ const pos = _popups.indexOf(name);
196
+ if (pos !== -1) {
197
+ _popups.splice(pos, 1);
308
198
  }
309
- },
310
- [controlledHighlighting, setHighlightedIdx]
311
- );
312
- const keyBoardNavigation = (0, import_react4.useRef)(true);
313
- const ignoreFocus = (0, import_react4.useRef)(false);
314
- const setIgnoreFocus = (value) => ignoreFocus.current = value;
315
- const highlightedIndex = controlledHighlighting ? highlightedIndexProp : highlightedIndexRef.current;
316
- const navigateChildldItems = (0, import_react4.useCallback)(
317
- (e) => {
318
- const nextIdx = nextItemIdx(count, e.key, highlightedIndexRef.current);
319
- if (nextIdx !== highlightedIndexRef.current) {
320
- setHighlightedIndex(nextIdx);
321
- }
322
- },
323
- [count, setHighlightedIndex]
324
- );
325
- const handleKeyDown = (0, import_react4.useCallback)(
326
- (e) => {
327
- if (isNavigationKey(e)) {
328
- e.preventDefault();
329
- e.stopPropagation();
330
- keyBoardNavigation.current = true;
331
- navigateChildldItems(e);
332
- } else if ((e.key === "ArrowRight" || e.key === "Enter") && hasPopup(e.target, highlightedIndex)) {
333
- const menuEl = e.target;
334
- const menuItemEl = menuEl.querySelector(
335
- `:scope > [data-index='${highlightedIndex}']`
336
- );
337
- if (menuItemEl) {
338
- onOpenMenu == null ? void 0 : onOpenMenu(menuItemEl, true);
339
- }
340
- } else if (e.key === "ArrowLeft" && !isRoot(e.target)) {
341
- onCloseMenu(highlightedIndex);
342
- } else if (e.key === "Enter") {
343
- e.preventDefault();
344
- e.stopPropagation();
345
- onActivate && onActivate(highlightedIndex);
346
- } else if (e.key === "Tab") {
347
- onCloseMenu(-1);
348
- }
349
- },
350
- [
351
- highlightedIndex,
352
- navigateChildldItems,
353
- onActivate,
354
- onCloseMenu,
355
- onOpenMenu
356
- ]
357
- );
358
- const listProps = (0, import_react4.useMemo)(
359
- () => ({
360
- onFocus: () => {
361
- if (highlightedIndex === -1) {
362
- setHighlightedIdx(0);
363
- }
364
- },
365
- onKeyDown: handleKeyDown,
366
- onMouseDownCapture: () => {
367
- keyBoardNavigation.current = false;
368
- setIgnoreFocus(true);
369
- },
370
- // onMouseEnter would seem less expensive but it misses some cases
371
- onMouseMove: () => {
372
- if (keyBoardNavigation.current) {
373
- keyBoardNavigation.current = false;
374
- }
375
- },
376
- onMouseLeave: () => {
377
- keyBoardNavigation.current = true;
378
- setIgnoreFocus(false);
379
- setHighlightedIndex(-1);
380
- }
381
- }),
382
- [handleKeyDown, highlightedIndex, setHighlightedIdx, setHighlightedIndex]
383
- );
384
- return {
385
- focusVisible: keyBoardNavigation.current ? highlightedIndex : -1,
386
- controlledHighlighting,
387
- highlightedIndex,
388
- setHighlightedIndex,
389
- // keyBoardNavigation,
390
- listProps,
391
- setIgnoreFocus
392
- };
199
+ }
200
+ if (_popups.length === 0 && _dialogOpen === false) {
201
+ window.removeEventListener("keydown", specialKeyHandler, true);
202
+ window.removeEventListener("click", outsideClickHandler, true);
203
+ }
204
+ }
205
+ }
206
+ var PopupComponent = ({
207
+ children,
208
+ position,
209
+ style
210
+ }) => {
211
+ const className = (0, import_clsx3.default)("hwPopup", "hwPopupContainer", position);
212
+ return (0, import_react.createElement)("div", { className, style }, children);
393
213
  };
394
- function nextItemIdx(count, key, idx) {
395
- if (key === "ArrowUp") {
396
- if (idx > 0) {
397
- return idx - 1;
398
- } else {
399
- return idx;
214
+ var incrementingKey = 1;
215
+ var PopupService = class _PopupService {
216
+ static showPopup({
217
+ group = "all",
218
+ name = "anon",
219
+ left = 0,
220
+ position = "",
221
+ right = "auto",
222
+ top = 0,
223
+ width = "auto",
224
+ component
225
+ }) {
226
+ if (!component) {
227
+ throw Error(`PopupService showPopup, no component supplied`);
400
228
  }
401
- } else {
402
- if (idx === null) {
403
- return 0;
404
- } else if (idx === count - 1) {
405
- return idx;
229
+ if (typeof component.props.onClose === "function") {
230
+ _PopupService.onClose = component.props.onClose;
406
231
  } else {
407
- return idx + 1;
232
+ _PopupService.onClose = void 0;
233
+ }
234
+ popupOpened(name);
235
+ document.addEventListener("keydown", _PopupService.escapeKeyListener, true);
236
+ let el = document.body.querySelector(".vuuPopup." + group);
237
+ if (el === null) {
238
+ el = document.createElement("div");
239
+ el.className = "vuuPopup " + group;
240
+ document.body.appendChild(el);
241
+ }
242
+ const style = { width };
243
+ renderPortal(
244
+ (0, import_react.createElement)(
245
+ PopupComponent,
246
+ { key: incrementingKey++, position, style },
247
+ component
248
+ ),
249
+ el,
250
+ left,
251
+ top,
252
+ () => {
253
+ _PopupService.keepWithinThePage(el, right);
254
+ }
255
+ );
256
+ }
257
+ static escapeKeyListener(evt) {
258
+ if (evt.key === "Escape") {
259
+ _PopupService.hidePopup({ type: "escape", event: evt });
408
260
  }
409
261
  }
410
- }
411
-
412
- // src/menu/use-items-with-ids-next.ts
413
- var import_react5 = __toESM(require("react"));
414
- var isMenuItemGroup = (child) => child.type === MenuItemGroup || !!child.props["data-group"];
415
- var getLabelFromChildren = (children) => {
416
- if (Array.isArray(children) && isMenuItemLabel(children[0])) {
417
- return children[0];
262
+ static hidePopup(reason, name = "anon", group = "all") {
263
+ var _a;
264
+ if (_popups.indexOf(name) !== -1) {
265
+ popupClosed(name);
266
+ const popupRoot = document.body.querySelector(`.vuuPopup.${group}`);
267
+ if (popupRoot) {
268
+ import_react_dom.default.unmountComponentAtNode(popupRoot);
269
+ }
270
+ }
271
+ document.removeEventListener(
272
+ "keydown",
273
+ _PopupService.escapeKeyListener,
274
+ true
275
+ );
276
+ (_a = _PopupService == null ? void 0 : _PopupService.onClose) == null ? void 0 : _a.call(_PopupService, reason);
277
+ }
278
+ static keepWithinThePage(el, right = "auto") {
279
+ const target = el.querySelector(".vuuPopupContainer > *");
280
+ if (target) {
281
+ const {
282
+ top,
283
+ left,
284
+ width,
285
+ height,
286
+ right: currentRight
287
+ } = target.getBoundingClientRect();
288
+ const w = window.innerWidth;
289
+ const h = window.innerHeight;
290
+ const overflowH = h - (top + height);
291
+ if (overflowH < 0) {
292
+ target.style.top = Math.round(top) + overflowH + "px";
293
+ }
294
+ const overflowW = w - (left + width);
295
+ if (overflowW < 0) {
296
+ target.style.left = Math.round(left) + overflowW + "px";
297
+ }
298
+ if (typeof right === "number" && right !== currentRight) {
299
+ const adjustment = right - currentRight;
300
+ target.style.left = left + adjustment + "px";
301
+ }
302
+ }
418
303
  }
419
304
  };
420
- var assignId = (child, path, group, hasSeparator = false) => {
421
- const {
422
- props: { children }
423
- } = child;
424
- return {
425
- childWithId: import_react5.default.cloneElement(child, {
426
- hasSeparator,
427
- id: `${path}`,
428
- key: path,
429
- children: group ? getLabelFromChildren(children) : children
430
- }),
431
- grandChildren: group ? children : void 0
432
- };
433
- };
434
- var useItemsWithIdsNext = (childrenProp, rootId) => {
435
- const normalizeChildren = (0, import_react5.useCallback)(() => {
436
- const collectChildren = (children, path = rootId, menus2 = {}, actions2 = {}) => {
437
- const list = menus2[path] = [];
438
- let idx = 0;
439
- let hasSeparator = false;
440
- import_react5.default.Children.forEach(children, (child) => {
441
- if (isMenuItemLabel(child)) {
442
- } else if (child.type === Separator) {
443
- hasSeparator = true;
444
- } else {
445
- const hasChildItems = isMenuItemGroup(child);
446
- const childPath = `${path}-${idx}`;
447
- const {
448
- props: { action, options }
449
- } = child;
450
- const { childWithId, grandChildren } = assignId(
451
- child,
452
- childPath,
453
- hasChildItems,
454
- hasSeparator
455
- );
456
- list.push(childWithId);
457
- if (grandChildren) {
458
- collectChildren(grandChildren, childPath, menus2, actions2);
459
- } else {
460
- actions2[childPath] = { action, options };
305
+ var DialogService = class _DialogService {
306
+ static showDialog(dialog) {
307
+ const containerEl = ".vuuDialog";
308
+ const onClose = dialog.props.onClose;
309
+ dialogOpened();
310
+ import_react_dom.default.render(
311
+ import_react.default.cloneElement(dialog, {
312
+ container: containerEl,
313
+ onClose: () => {
314
+ _DialogService.closeDialog();
315
+ if (onClose) {
316
+ onClose();
461
317
  }
462
- idx += 1;
463
- hasSeparator = false;
464
318
  }
465
- });
466
- return [menus2, actions2];
467
- };
468
- return collectChildren(childrenProp);
469
- }, [rootId, childrenProp]);
470
- const [menus, actions] = (0, import_react5.useMemo)(
471
- () => normalizeChildren(),
472
- [normalizeChildren]
473
- );
474
- return [menus, actions];
319
+ }),
320
+ document.body.querySelector(containerEl)
321
+ );
322
+ }
323
+ static closeDialog() {
324
+ dialogClosed();
325
+ const dialogRoot = document.body.querySelector(".vuuDialog");
326
+ if (dialogRoot) {
327
+ import_react_dom.default.unmountComponentAtNode(dialogRoot);
328
+ }
329
+ }
475
330
  };
476
331
 
477
- // src/menu/MenuList.tsx
478
- var import_jsx_runtime4 = require("react/jsx-runtime");
479
- var classBase3 = "vuuMenuList";
480
- var Separator = () => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { className: "vuuMenuItem-divider" });
481
- var MenuItemGroup = () => null;
482
- var MenuItem = ({
483
- children,
484
- idx,
485
- options,
486
- ...props
487
- }) => {
488
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ...props, children });
332
+ // src/popup/Popup.tsx
333
+ var import_clsx4 = __toESM(require("clsx"));
334
+
335
+ // src/popup/useAnchoredPosition.ts
336
+ var import_react2 = require("react");
337
+ var getPositionRelativeToAnchor = (anchorElement, placement, offsetLeft, offsetTop, minWidth, dimensions) => {
338
+ const { bottom, height, left, right, top, width } = anchorElement.getBoundingClientRect();
339
+ switch (placement) {
340
+ case "below":
341
+ return { left: left + offsetLeft, top: bottom + offsetTop };
342
+ case "right":
343
+ return { left: right + offsetLeft, top: top + offsetTop };
344
+ case "below-center":
345
+ return { left: left + width / 2 + offsetLeft, top: bottom + offsetTop };
346
+ case "below-right":
347
+ return { left, minWidth, top: bottom + offsetTop };
348
+ case "below-full-width":
349
+ return {
350
+ left: left + offsetLeft,
351
+ minWidth,
352
+ top: bottom + offsetTop,
353
+ width
354
+ };
355
+ case "center":
356
+ if (dimensions) {
357
+ return {
358
+ left: width / 2 - dimensions.width / 2 + offsetLeft,
359
+ top: height / 2 - dimensions.height / 2 + offsetTop,
360
+ visibility: "visible"
361
+ };
362
+ } else {
363
+ return {
364
+ left: width / 2 + offsetLeft,
365
+ top: height / 2 + offsetTop,
366
+ visibility: "hidden"
367
+ };
368
+ }
369
+ default:
370
+ throw Error(
371
+ "Popup getPositionRelativeToAnchor only supported placement values are below and right"
372
+ );
373
+ }
489
374
  };
490
- var MenuItemLabel = ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children });
491
- MenuItemLabel.displayName = "MenuItemLabel";
492
- MenuItem.Label = MenuItemLabel;
493
- var getDisplayName = (item) => import_react6.default.isValidElement(item) && typeof item.type !== "string" && "displayName" in item.type ? item.type.displayName : void 0;
494
- var isMenuItemLabel = (item) => getDisplayName(item) === "MenuItemLabel";
495
- var hasIcon = (child) => child.props["data-icon"];
496
- var MenuList = ({
497
- activatedByKeyboard,
498
- childMenuShowing,
375
+ var useAnchoredPosition = ({
376
+ anchorElement,
377
+ minWidth,
378
+ offsetLeft = 0,
379
+ offsetTop = 0,
380
+ placement,
381
+ position: positionProp
382
+ }) => {
383
+ const popupRef = (0, import_react2.useRef)(null);
384
+ const [position, setPosition] = (0, import_react2.useState)(positionProp);
385
+ (0, import_react2.useLayoutEffect)(() => {
386
+ if (placement === "absolute" && positionProp) {
387
+ setPosition(positionProp);
388
+ } else if (anchorElement.current && placement !== "auto") {
389
+ const dimensions = popupRef.current === null ? void 0 : popupRef.current.getBoundingClientRect();
390
+ const position2 = getPositionRelativeToAnchor(
391
+ anchorElement.current,
392
+ placement,
393
+ offsetLeft,
394
+ offsetTop,
395
+ minWidth,
396
+ dimensions
397
+ );
398
+ setPosition(position2);
399
+ }
400
+ }, [anchorElement, minWidth, offsetLeft, offsetTop, placement, positionProp]);
401
+ const popupCallbackRef = (0, import_react2.useCallback)(
402
+ (el) => {
403
+ popupRef.current = el;
404
+ if (el && placement === "center" && anchorElement.current) {
405
+ const { height, width } = el.getBoundingClientRect();
406
+ setPosition(
407
+ getPositionRelativeToAnchor(
408
+ anchorElement.current,
409
+ placement,
410
+ offsetLeft,
411
+ offsetTop,
412
+ void 0,
413
+ { height, width }
414
+ )
415
+ );
416
+ }
417
+ },
418
+ [anchorElement, offsetLeft, offsetTop, placement]
419
+ );
420
+ return {
421
+ position,
422
+ popupRef: placement === "center" ? popupCallbackRef : void 0
423
+ };
424
+ };
425
+
426
+ // src/popup/Popup.tsx
427
+ var import_jsx_runtime2 = require("react/jsx-runtime");
428
+ var PopupComponent2 = ({
499
429
  children,
500
430
  className,
501
- defaultHighlightedIdx,
502
- highlightedIdx: highlightedIdxProp,
503
- id: idProp,
504
- isRoot: isRoot2,
505
- listItemProps,
506
- onHighlightMenuItem,
507
- onActivate,
508
- onCloseMenu,
509
- openMenu: onOpenMenu,
510
- ...props
431
+ anchorElement,
432
+ minWidth,
433
+ placement,
434
+ position: positionProp
511
435
  }) => {
512
- const id = (0, import_vuu_layout.useId)(idProp);
513
- const root = (0, import_react6.useRef)(null);
514
- const mapIdxToId = (0, import_react6.useMemo)(() => /* @__PURE__ */ new Map(), []);
515
- const handleActivate = (idx) => {
516
- var _a;
517
- const el = (_a = root.current) == null ? void 0 : _a.querySelector(`:scope > [data-index='${idx}']`);
518
- (el == null ? void 0 : el.id) && (onActivate == null ? void 0 : onActivate(el.id));
519
- };
520
- const { focusVisible, highlightedIndex, listProps } = useKeyboardNavigation({
521
- count: import_react6.default.Children.count(children),
522
- defaultHighlightedIdx,
523
- highlightedIndex: highlightedIdxProp,
524
- onActivate: handleActivate,
525
- onHighlight: onHighlightMenuItem,
526
- onOpenMenu,
527
- onCloseMenu
436
+ const { popupRef, position } = useAnchoredPosition({
437
+ anchorElement,
438
+ minWidth,
439
+ placement,
440
+ position: positionProp
528
441
  });
529
- const appliedFocusVisible = childMenuShowing == void 0 ? focusVisible : -1;
530
- (0, import_react6.useLayoutEffect)(() => {
531
- var _a;
532
- if (childMenuShowing === void 0 && activatedByKeyboard) {
533
- (_a = root.current) == null ? void 0 : _a.focus();
442
+ return position === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: (0, import_clsx4.default)(`vuuPortal`, className), ref: popupRef, style: position, children });
443
+ };
444
+
445
+ // src/dialog/Dialog.tsx
446
+ var import_jsx_runtime3 = require("react/jsx-runtime");
447
+ var classBase2 = "vuuDialog";
448
+ var AnchorBody = { current: document.body };
449
+ var EMPTY_PROPS = {};
450
+ var Dialog = ({
451
+ PopupProps = EMPTY_PROPS,
452
+ children,
453
+ className,
454
+ isOpen = false,
455
+ onClose,
456
+ style,
457
+ title,
458
+ hideCloseButton = false,
459
+ ...htmlAttributes
460
+ }) => {
461
+ const {
462
+ anchorElement = AnchorBody,
463
+ offsetLeft = 0,
464
+ offsetTop = 0,
465
+ placement = "auto"
466
+ } = PopupProps;
467
+ const rootRef = (0, import_react3.useRef)(null);
468
+ const portalRef = (0, import_react3.useRef)(null);
469
+ const [themeClass, densityClass, dataMode] = (0, import_vuu_utils.useThemeAttributes)();
470
+ const { position } = useAnchoredPosition({
471
+ anchorElement,
472
+ offsetLeft,
473
+ offsetTop,
474
+ placement
475
+ });
476
+ const close = (0, import_react3.useCallback)(() => {
477
+ onClose == null ? void 0 : onClose();
478
+ }, [onClose]);
479
+ (0, import_react3.useLayoutEffect)(() => {
480
+ if (rootRef.current) {
481
+ if (isOpen) {
482
+ rootRef.current.showModal();
483
+ const { left, top } = rootRef.current.getBoundingClientRect();
484
+ if (portalRef.current) {
485
+ portalRef.current.style.cssText = `left:-${left}px;position:absolute;top:-${top}px;`;
486
+ }
487
+ } else {
488
+ rootRef.current.close();
489
+ }
490
+ if (placement.endsWith("center")) {
491
+ const { width } = rootRef.current.getBoundingClientRect();
492
+ rootRef.current.style.marginLeft = `-${width / 2}px`;
493
+ }
534
494
  }
535
- }, [activatedByKeyboard, childMenuShowing]);
536
- const getActiveDescendant = () => highlightedIndex === void 0 || highlightedIndex === -1 ? void 0 : mapIdxToId.get(highlightedIndex);
537
- function renderContent() {
538
- const propsCommonToAllListItems = {
539
- ...listItemProps,
540
- role: "menuitem"
541
- };
542
- const maybeIcon = (childElement, withIcon, iconName) => withIcon ? [
543
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
544
- "span",
545
- {
546
- className: "vuuIconContainer",
547
- "data-icon": iconName
548
- },
549
- "icon"
550
- )
551
- ].concat(childElement) : childElement;
552
- function addClonedChild(list, child, idx, withIcon) {
553
- var _a;
554
- const {
555
- children: children2,
556
- className: className2,
557
- "data-icon": iconName,
558
- id: itemId,
559
- hasSeparator,
560
- label,
561
- ...props2
562
- } = child.props;
563
- const hasSubMenu = isMenuItemGroup(child);
564
- const subMenuShowing = hasSubMenu && childMenuShowing === itemId;
565
- const ariaControls = subMenuShowing ? `${id}-${itemId}` : void 0;
566
- list.push(
567
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
568
- MenuItem,
495
+ }, [isOpen, placement]);
496
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
497
+ "dialog",
498
+ {
499
+ ...htmlAttributes,
500
+ className: (0, import_clsx5.default)(classBase2, themeClass),
501
+ "data-mode": dataMode,
502
+ onClose: close,
503
+ id: "vuu-dialog",
504
+ ref: rootRef,
505
+ style: { ...style, ...position },
506
+ children: [
507
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
508
+ DialogHeader,
569
509
  {
570
- ...props2,
571
- ...propsCommonToAllListItems,
572
- ...getMenuItemProps(
573
- itemId,
574
- idx,
575
- (_a = child.key) != null ? _a : itemId,
576
- highlightedIndex,
577
- appliedFocusVisible,
578
- className2,
579
- hasSeparator
580
- ),
581
- "aria-controls": ariaControls,
582
- "aria-haspopup": hasSubMenu || void 0,
583
- "aria-expanded": subMenuShowing || void 0,
584
- children: hasSubMenu ? maybeIcon(label != null ? label : children2, withIcon, iconName) : maybeIcon(children2, withIcon, iconName)
510
+ hideCloseButton,
511
+ onClose: close,
512
+ title
585
513
  }
586
- )
587
- );
588
- }
589
- const listItems = [];
590
- if (children.length > 0) {
591
- const withIcon = children.some(hasIcon);
592
- children.forEach((child, idx) => {
593
- addClonedChild(listItems, child, idx, withIcon);
594
- });
595
- }
596
- return listItems;
597
- }
598
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
599
- "div",
600
- {
601
- ...props,
602
- ...listProps,
603
- "aria-activedescendant": getActiveDescendant(),
604
- className: (0, import_classnames3.default)(classBase3, className, {
605
- [`${classBase3}-childMenuShowing`]: childMenuShowing !== void 0
606
- }),
607
- "data-root": isRoot2 || void 0,
608
- id,
609
- ref: root,
610
- role: "menu",
611
- children: renderContent()
514
+ ),
515
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: `${classBase2}-body`, children }),
516
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { id: "vuu-dialog-portal-root", ref: portalRef })
517
+ ]
612
518
  }
613
519
  );
614
520
  };
615
- var getMenuItemProps = (itemId, idx, key, highlightedIdx, focusVisible, className, hasSeparator) => ({
616
- id: `menuitem-${itemId}`,
617
- key: key != null ? key : idx,
618
- "data-index": idx,
619
- "data-highlighted": idx === highlightedIdx || void 0,
620
- className: (0, import_classnames3.default)("vuuMenuItem", className, {
621
- "vuuMenuItem-separator": hasSeparator,
622
- focusVisible: focusVisible === idx
623
- })
624
- });
625
- MenuList.displayName = "MenuList";
626
521
 
627
- // src/menu/use-cascade.ts
628
- var import_react7 = require("react");
522
+ // src/dialog/useDialog.tsx
523
+ var import_react4 = require("react");
524
+ var import_jsx_runtime4 = require("react/jsx-runtime");
525
+ var useDialog = () => {
526
+ const [dialogState, setDialogState] = (0, import_react4.useState)();
527
+ const handleClose = (0, import_react4.useCallback)(() => {
528
+ setDialogState(void 0);
529
+ }, []);
530
+ const dialog = dialogState ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
531
+ Dialog,
532
+ {
533
+ className: "vuDialog",
534
+ isOpen: true,
535
+ onClose: handleClose,
536
+ style: { maxHeight: 500 },
537
+ title: dialogState.title,
538
+ hideCloseButton: dialogState.hideCloseButton,
539
+ children: dialogState.content
540
+ }
541
+ ) : null;
542
+ return {
543
+ dialog,
544
+ setDialogState
545
+ };
546
+ };
629
547
 
630
- // src/menu/list-dom-utils.ts
631
- var closestListItem = (el) => el == null ? void 0 : el.closest("[data-index],[aria-posinset]");
548
+ // src/menu/ContextMenu.tsx
549
+ var import_vuu_utils5 = require("@vuu-ui/vuu-utils");
550
+ var import_react10 = require("react");
632
551
 
633
- // src/menu/use-cascade.ts
634
- var nudge = (menus, distance, pos) => {
635
- return menus.map(
636
- (m, i) => i === menus.length - 1 ? {
637
- ...m,
638
- [pos]: m[pos] - distance
639
- } : m
640
- );
641
- };
642
- var nudgeLeft = (menus, distance) => nudge(menus, distance, "left");
643
- var nudgeUp = (menus, distance) => nudge(menus, distance, "top");
644
- var flipSides = (id, menus) => {
645
- const [parentMenu, menu] = menus.slice(-2);
646
- const el = document.getElementById(`${id}-${menu.id}`);
647
- if (el === null) {
648
- throw Error(`useCascade.flipSides element with id ${menu.id} not found`);
552
+ // src/portal/Portal.tsx
553
+ var import_vuu_utils2 = require("@vuu-ui/vuu-utils");
554
+ var import_react5 = require("react");
555
+ var import_react_dom2 = require("react-dom");
556
+ function getContainer(container) {
557
+ return typeof container === "function" ? container() : container;
558
+ }
559
+ var DEFAULT_ID = ["vuu-dialog-portal-root", "vuu-portal-root"];
560
+ var getFirstAvailableElementById = (id) => {
561
+ if (Array.isArray(id)) {
562
+ for (const i of id) {
563
+ const element = document.getElementById(i);
564
+ if (element) {
565
+ return element;
566
+ }
567
+ }
568
+ } else {
569
+ return document.getElementById(id);
649
570
  }
650
- const { width } = el.getBoundingClientRect();
651
- return menus.map(
652
- (m) => m === menu ? {
653
- ...m,
654
- left: parentMenu.left - (width - 2)
655
- } : m
656
- );
571
+ return null;
657
572
  };
658
- var getPosition = (el, openMenus) => {
659
- const [{ left, top: menuTop }] = openMenus.slice(-1);
660
- const { offsetWidth: width, offsetTop: top } = el;
661
- return { left: left + width, top: top + menuTop };
573
+ var Portal = ({
574
+ children,
575
+ container: containerProp = document.body,
576
+ id = DEFAULT_ID,
577
+ onRender,
578
+ open = true,
579
+ themeAttributes
580
+ }) => {
581
+ var _a;
582
+ const [mounted, setMounted] = (0, import_react5.useState)(false);
583
+ const portalRef = (0, import_react5.useRef)(null);
584
+ const container = (_a = getContainer(containerProp)) != null ? _a : document.body;
585
+ const [themeClass, densityClass, dataMode] = (0, import_vuu_utils2.useThemeAttributes)(themeAttributes);
586
+ (0, import_react5.useLayoutEffect)(() => {
587
+ const root = getFirstAvailableElementById(id);
588
+ if (root) {
589
+ portalRef.current = root;
590
+ } else {
591
+ portalRef.current = document.createElement("div");
592
+ portalRef.current.id = typeof id === "string" ? id : id.length > 0 ? id.at(-1) : "vuu-portal-root";
593
+ }
594
+ const el = portalRef.current;
595
+ if (!container.contains(el)) {
596
+ container.appendChild(el);
597
+ }
598
+ el.classList.add(themeClass, densityClass);
599
+ el.dataset.mode = dataMode;
600
+ setMounted(true);
601
+ }, [id, container, themeClass, densityClass, dataMode]);
602
+ (0, import_react5.useLayoutEffect)(() => {
603
+ requestAnimationFrame(() => {
604
+ onRender == null ? void 0 : onRender();
605
+ });
606
+ }, [onRender]);
607
+ if (open && mounted && portalRef.current && children) {
608
+ return (0, import_react_dom2.createPortal)(children, portalRef.current);
609
+ }
610
+ return null;
662
611
  };
663
- var getHostMenuId = (id, rootId) => {
664
- const pos = id.lastIndexOf("-");
665
- if (id.startsWith("menuitem")) {
666
- return pos > -1 ? id.slice(9, pos) : rootId;
667
- } else {
668
- return pos > -1 ? id.slice(0, pos) : rootId;
612
+
613
+ // src/menu/MenuList.tsx
614
+ var import_clsx6 = __toESM(require("clsx"));
615
+ var import_react8 = __toESM(require("react"));
616
+ var import_vuu_utils4 = require("@vuu-ui/vuu-utils");
617
+
618
+ // src/menu/use-items-with-ids-next.ts
619
+ var import_react6 = __toESM(require("react"));
620
+ var isMenuItemGroup = (child) => child.type === MenuItemGroup || !!child.props["data-group"];
621
+ var getLabelFromChildren = (children) => {
622
+ if (Array.isArray(children) && isMenuItemLabel(children[0])) {
623
+ return children[0];
669
624
  }
670
625
  };
671
- var getTargetMenuId = (id) => id.slice(9);
672
- var getMenuItemDetails = ({ ariaExpanded, ariaHasPopup, id }, rootId) => {
673
- if (id.startsWith("menuitem")) {
674
- return {
675
- hostMenuId: getHostMenuId(id, rootId),
676
- targetMenuId: getTargetMenuId(id),
677
- menuItemId: id,
678
- isGroup: ariaHasPopup === "true",
679
- isOpen: ariaExpanded === "true"
626
+ var assignId = (child, path, group, hasSeparator = false) => {
627
+ const {
628
+ props: { children }
629
+ } = child;
630
+ return {
631
+ childWithId: import_react6.default.cloneElement(child, {
632
+ hasSeparator,
633
+ id: `${path}`,
634
+ key: path,
635
+ children: group ? getLabelFromChildren(children) : children
636
+ }),
637
+ grandChildren: group ? children : void 0
638
+ };
639
+ };
640
+ var useItemsWithIdsNext = (childrenProp, rootId) => {
641
+ const normalizeChildren = (0, import_react6.useCallback)(() => {
642
+ const collectChildren = (children, path = rootId, menus2 = {}, actions2 = {}) => {
643
+ const list = menus2[path] = [];
644
+ let idx = 0;
645
+ let hasSeparator = false;
646
+ import_react6.default.Children.forEach(children, (child) => {
647
+ if (isMenuItemLabel(child)) {
648
+ } else if (child.type === Separator) {
649
+ hasSeparator = true;
650
+ } else {
651
+ const hasChildItems = isMenuItemGroup(child);
652
+ const childPath = `${path}-${idx}`;
653
+ const {
654
+ props: { action, options }
655
+ } = child;
656
+ const { childWithId, grandChildren } = assignId(
657
+ child,
658
+ childPath,
659
+ hasChildItems,
660
+ hasSeparator
661
+ );
662
+ list.push(childWithId);
663
+ if (grandChildren) {
664
+ collectChildren(grandChildren, childPath, menus2, actions2);
665
+ } else {
666
+ actions2[childPath] = { action, options };
667
+ }
668
+ idx += 1;
669
+ hasSeparator = false;
670
+ }
671
+ });
672
+ return [menus2, actions2];
680
673
  };
681
- } else {
682
- throw Error(`getMenuItemDetails #${id} is not a menuitem`);
674
+ return collectChildren(childrenProp);
675
+ }, [rootId, childrenProp]);
676
+ const [menus, actions] = (0, import_react6.useMemo)(
677
+ () => normalizeChildren(),
678
+ [normalizeChildren]
679
+ );
680
+ return [menus, actions];
681
+ };
682
+
683
+ // src/menu/use-keyboard-navigation.ts
684
+ var import_react7 = require("react");
685
+
686
+ // src/menu/utils.ts
687
+ var isRoot = (el) => el.closest(`[data-root='true']`) !== null;
688
+ var hasPopup = (el, idx) => {
689
+ var _a;
690
+ return el.ariaHasPopup === "true" && ((_a = el.dataset) == null ? void 0 : _a.idx) === `${idx}` || el.querySelector(`:scope > [data-index='${idx}'][aria-haspopup='true']`) !== null;
691
+ };
692
+
693
+ // src/menu/key-code.ts
694
+ function union(set1, ...sets) {
695
+ const result = new Set(set1);
696
+ for (const set of sets) {
697
+ for (const element of set) {
698
+ result.add(element);
699
+ }
683
700
  }
701
+ return result;
702
+ }
703
+ var Enter = "Enter";
704
+ var Delete = "Delete";
705
+ var actionKeys = /* @__PURE__ */ new Set([Enter, Delete]);
706
+ var focusKeys = /* @__PURE__ */ new Set(["Tab"]);
707
+ var arrowLeftRightKeys = /* @__PURE__ */ new Set(["ArrowRight", "ArrowLeft"]);
708
+ var verticalNavigationKeys = /* @__PURE__ */ new Set(["Home", "End", "ArrowDown", "ArrowUp"]);
709
+ var horizontalNavigationKeys = /* @__PURE__ */ new Set([
710
+ "Home",
711
+ "End",
712
+ "ArrowRight",
713
+ "ArrowLeft"
714
+ ]);
715
+ var functionKeys = /* @__PURE__ */ new Set([
716
+ "F1",
717
+ "F2",
718
+ "F3",
719
+ "F4",
720
+ "F5",
721
+ "F6",
722
+ "F7",
723
+ "F8",
724
+ "F9",
725
+ "F10",
726
+ "F11",
727
+ "F12"
728
+ ]);
729
+ var specialKeys = union(
730
+ actionKeys,
731
+ horizontalNavigationKeys,
732
+ verticalNavigationKeys,
733
+ arrowLeftRightKeys,
734
+ functionKeys,
735
+ focusKeys
736
+ );
737
+ var isNavigationKey = ({ key }, orientation = "vertical") => {
738
+ const navigationKeys = orientation === "vertical" ? verticalNavigationKeys : horizontalNavigationKeys;
739
+ return navigationKeys.has(key);
684
740
  };
685
- var useCascade = ({
686
- id: rootId,
741
+
742
+ // src/menu/use-keyboard-navigation.ts
743
+ var import_vuu_utils3 = require("@vuu-ui/vuu-utils");
744
+ var useKeyboardNavigation = ({
745
+ autoHighlightFirstItem = false,
746
+ count,
747
+ defaultHighlightedIdx,
748
+ highlightedIndex: highlightedIndexProp,
687
749
  onActivate,
688
- onMouseEnterItem,
689
- position: { x: posX, y: posY }
750
+ onHighlight,
751
+ // onKeyDown,
752
+ onCloseMenu,
753
+ onOpenMenu
690
754
  }) => {
691
- const [, forceRefresh] = (0, import_react7.useState)({});
692
- const openMenus = (0, import_react7.useRef)([
693
- { id: rootId, left: posX, top: posY }
694
- ]);
695
- const menuIsOpen = (0, import_react7.useCallback)(
696
- (menuId) => openMenus.current.findIndex((menu) => menu.id === menuId) !== -1,
697
- []
698
- );
699
- const getOpenMenuStatus = (0, import_react7.useCallback)((menuId) => {
700
- const state = menuState.current[menuId];
701
- if (state === void 0) {
702
- throw Error(`getOpenMenuState no entry for menu ${menuId}`);
703
- }
704
- return state;
705
- }, []);
706
- const setOpenMenus = (0, import_react7.useCallback)((menus) => {
707
- openMenus.current = menus;
708
- forceRefresh({});
709
- }, []);
710
- const menuOpenPendingTimeout = (0, import_react7.useRef)();
711
- const menuClosePendingTimeout = (0, import_react7.useRef)();
712
- const menuState = (0, import_react7.useRef)({ [rootId]: "no-popup" });
713
- const openMenu = (0, import_react7.useCallback)(
714
- (hostMenuId = rootId, targetMenuId, itemId = null) => {
715
- if (hostMenuId === rootId && itemId === null) {
716
- setOpenMenus([{ id: rootId, left: posX, top: posY }]);
717
- } else {
718
- menuState.current[hostMenuId] = "popup-open";
719
- const el = document.getElementById(itemId);
720
- if (el !== null) {
721
- const { left, top } = getPosition(el, openMenus.current);
722
- setOpenMenus(
723
- openMenus.current.concat({ id: targetMenuId, left, top })
724
- );
725
- } else {
726
- throw Error(`openMenu no menuItem ${itemId}`);
727
- }
728
- }
755
+ var _a;
756
+ if ((0, import_vuu_utils3.isValidNumber)(highlightedIndexProp) && (0, import_vuu_utils3.isValidNumber)(defaultHighlightedIdx)) {
757
+ throw Error(
758
+ "useKeyboardNavigation do not pass values for both highlightedIndex and defaultHighlightedIdx"
759
+ );
760
+ }
761
+ const controlledHighlighting = (0, import_vuu_utils3.isValidNumber)(highlightedIndexProp);
762
+ const highlightedIndexRef = (0, import_react7.useRef)(
763
+ (_a = defaultHighlightedIdx != null ? defaultHighlightedIdx : highlightedIndexProp) != null ? _a : autoHighlightFirstItem ? 0 : -1
764
+ );
765
+ const [, forceRender] = (0, import_react7.useState)(null);
766
+ const setHighlightedIdx = (0, import_react7.useCallback)(
767
+ (idx) => {
768
+ highlightedIndexRef.current = idx;
769
+ onHighlight == null ? void 0 : onHighlight(idx);
770
+ forceRender({});
729
771
  },
730
- [rootId, posX, posY, setOpenMenus]
772
+ [onHighlight]
731
773
  );
732
- const closeMenu = (0, import_react7.useCallback)(
733
- (menuId) => {
734
- if (menuId === rootId) {
735
- setOpenMenus([]);
736
- } else {
737
- const menus = openMenus.current.slice();
738
- const lastMenu = menus.pop();
739
- menuState.current[lastMenu.id] = "no-popup";
740
- const parentMenu = menus.at(-1);
741
- if (parentMenu) {
742
- menuState.current[parentMenu.id] = "no-popup";
774
+ const setHighlightedIndex = (0, import_react7.useCallback)(
775
+ (idx) => {
776
+ if (idx !== highlightedIndexRef.current) {
777
+ if (!controlledHighlighting) {
778
+ setHighlightedIdx(idx);
743
779
  }
744
- setOpenMenus(menus);
745
780
  }
746
781
  },
747
- [rootId, setOpenMenus]
782
+ [controlledHighlighting, setHighlightedIdx]
748
783
  );
749
- const closeMenus = (0, import_react7.useCallback)(
750
- (menuItemId) => {
751
- const menus = openMenus.current.slice();
752
- const menuItemMenuId = menuItemId.slice(9);
753
- let { id: lastMenuId } = menus.at(-1);
754
- while (menus.length > 1 && !menuItemMenuId.startsWith(lastMenuId)) {
755
- const parentMenuId = getHostMenuId(lastMenuId, rootId);
756
- menus.pop();
757
- menuState.current[lastMenuId] = "no-popup";
758
- menuState.current[parentMenuId] = "no-popup";
759
- ({ id: lastMenuId } = menus[menus.length - 1]);
760
- }
761
- if (menus.length < openMenus.current.length) {
762
- setOpenMenus(menus);
784
+ const keyBoardNavigation = (0, import_react7.useRef)(true);
785
+ const ignoreFocus = (0, import_react7.useRef)(false);
786
+ const setIgnoreFocus = (value) => ignoreFocus.current = value;
787
+ const highlightedIndex = controlledHighlighting ? highlightedIndexProp : highlightedIndexRef.current;
788
+ const navigateChildldItems = (0, import_react7.useCallback)(
789
+ (e) => {
790
+ const nextIdx = nextItemIdx(count, e.key, highlightedIndexRef.current);
791
+ if (nextIdx !== highlightedIndexRef.current) {
792
+ setHighlightedIndex(nextIdx);
763
793
  }
764
794
  },
765
- [rootId, setOpenMenus]
766
- );
767
- const clearAnyScheduledOpenTasks = (0, import_react7.useCallback)(() => {
768
- if (menuOpenPendingTimeout.current) {
769
- clearTimeout(menuOpenPendingTimeout.current);
770
- menuOpenPendingTimeout.current = void 0;
771
- }
772
- }, []);
773
- const scheduleOpen = (0, import_react7.useCallback)(
774
- (hostMenuId, targetMenuId, menuItemId, delay = 300) => {
775
- clearAnyScheduledOpenTasks();
776
- menuOpenPendingTimeout.current = window.setTimeout(() => {
777
- closeMenus(menuItemId);
778
- menuState.current[hostMenuId] = "popup-open";
779
- menuState.current[targetMenuId] = "no-popup";
780
- openMenu(hostMenuId, targetMenuId, menuItemId);
781
- }, delay);
782
- },
783
- [clearAnyScheduledOpenTasks, closeMenus, openMenu]
784
- );
785
- const scheduleClose = (0, import_react7.useCallback)(
786
- (hostMenuId, openMenuId, itemId) => {
787
- menuState.current[openMenuId] = "pending-close";
788
- menuClosePendingTimeout.current = window.setTimeout(() => {
789
- closeMenus(itemId);
790
- }, 400);
791
- },
792
- [closeMenus]
795
+ [count, setHighlightedIndex]
793
796
  );
794
- const handleRender = (0, import_react7.useCallback)(() => {
795
- const { current: menus } = openMenus;
796
- const menu = menus.at(-1);
797
- const el = menu ? document.getElementById(menu.id) : void 0;
798
- if (el) {
799
- const { right, bottom } = el.getBoundingClientRect();
800
- const { clientHeight, clientWidth } = document.body;
801
- if (right > clientWidth) {
802
- const newMenus = menus.length > 1 ? flipSides(rootId, menus) : nudgeLeft(menus, right - clientWidth);
803
- setOpenMenus(newMenus);
804
- } else if (bottom > clientHeight) {
805
- const newMenus = nudgeUp(menus, bottom - clientHeight);
806
- setOpenMenus(newMenus);
807
- }
808
- if (typeof el.tabIndex === "number") {
809
- el.focus();
810
- }
811
- }
812
- }, [rootId, setOpenMenus]);
813
- const triggerChildMenu = (0, import_react7.useCallback)(
814
- (menuItemEl, immediate = false) => {
815
- const { hostMenuId, targetMenuId, menuItemId, isGroup, isOpen } = getMenuItemDetails(menuItemEl, rootId);
816
- const {
817
- current: { [hostMenuId]: state }
818
- } = menuState;
819
- const delay = immediate ? 0 : void 0;
820
- if (state === "no-popup" && isGroup) {
821
- menuState.current[hostMenuId] = "popup-pending";
822
- scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
823
- } else if (state === "popup-pending" && !isGroup) {
824
- menuState.current[hostMenuId] = "no-popup";
825
- clearTimeout(menuOpenPendingTimeout.current);
826
- menuOpenPendingTimeout.current = void 0;
827
- } else if (state === "popup-pending" && isGroup) {
828
- clearTimeout(menuOpenPendingTimeout.current);
829
- scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
830
- } else if (state === "popup-open") {
831
- if (menuIsOpen(targetMenuId)) {
832
- const menuStatus = getOpenMenuStatus(targetMenuId);
833
- closeMenus(menuItemId);
834
- switch (menuStatus) {
835
- case "pending-close":
836
- clearTimeout(menuClosePendingTimeout.current);
837
- menuClosePendingTimeout.current = void 0;
838
- menuState.current[targetMenuId] = "no-popup";
839
- clearAnyScheduledOpenTasks();
840
- break;
841
- default:
842
- }
843
- } else {
844
- const [parentOfLastOpenedMenu, lastOpenedMenu] = openMenus.current.slice(-2);
845
- if (parentOfLastOpenedMenu.id === hostMenuId && menuState.current[lastOpenedMenu.id] !== "pending-close") {
846
- scheduleClose(hostMenuId, lastOpenedMenu.id, menuItemId);
847
- if (isGroup && !isOpen) {
848
- scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
849
- }
850
- } else if (parentOfLastOpenedMenu.id === hostMenuId && isGroup && menuItemId !== lastOpenedMenu.id && menuState.current[lastOpenedMenu.id] === "pending-close") {
851
- scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
852
- } else if (isGroup) {
853
- scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
854
- } else if (!(menuState.current[lastOpenedMenu.id] === "pending-close")) {
855
- closeMenus(menuItemId);
856
- }
797
+ const handleKeyDown = (0, import_react7.useCallback)(
798
+ (e) => {
799
+ if (isNavigationKey(e)) {
800
+ e.preventDefault();
801
+ e.stopPropagation();
802
+ keyBoardNavigation.current = true;
803
+ navigateChildldItems(e);
804
+ } else if ((e.key === "ArrowRight" || e.key === "Enter") && hasPopup(e.target, highlightedIndex)) {
805
+ const menuEl = e.target;
806
+ const menuItemEl = menuEl.querySelector(
807
+ `:scope > [data-index='${highlightedIndex}']`
808
+ );
809
+ if (menuItemEl) {
810
+ onOpenMenu == null ? void 0 : onOpenMenu(menuItemEl, true);
857
811
  }
858
- }
859
- if (state === "pending-close") {
860
- clearAnyScheduledOpenTasks();
861
- clearTimeout(menuClosePendingTimeout.current);
862
- menuClosePendingTimeout.current = void 0;
863
- menuState.current[hostMenuId] = "popup-open";
812
+ } else if (e.key === "ArrowLeft" && !isRoot(e.target)) {
813
+ onCloseMenu(highlightedIndex);
814
+ } else if (e.key === "Enter") {
815
+ e.preventDefault();
816
+ e.stopPropagation();
817
+ onActivate && onActivate(highlightedIndex);
818
+ } else if (e.key === "Tab") {
819
+ onCloseMenu(-1);
864
820
  }
865
821
  },
866
822
  [
867
- clearAnyScheduledOpenTasks,
868
- closeMenus,
869
- getOpenMenuStatus,
870
- menuIsOpen,
871
- rootId,
872
- scheduleClose,
873
- scheduleOpen
823
+ highlightedIndex,
824
+ navigateChildldItems,
825
+ onActivate,
826
+ onCloseMenu,
827
+ onOpenMenu
874
828
  ]
875
829
  );
876
- const listItemProps = (0, import_react7.useMemo)(
830
+ const listProps = (0, import_react7.useMemo)(
877
831
  () => ({
878
- onMouseEnter: (evt) => {
879
- const menuItemEl = closestListItem(evt.target);
880
- triggerChildMenu(menuItemEl);
881
- onMouseEnterItem(evt, menuItemEl.id);
832
+ onFocus: () => {
833
+ if (highlightedIndex === -1) {
834
+ setHighlightedIdx(0);
835
+ }
882
836
  },
883
- onClick: (evt) => {
884
- const listItemEl = closestListItem(evt.target);
885
- const { isGroup, menuItemId } = getMenuItemDetails(listItemEl, rootId);
886
- if (isGroup) {
887
- triggerChildMenu(listItemEl);
888
- } else {
889
- onActivate(menuItemId);
837
+ onKeyDown: handleKeyDown,
838
+ onMouseDownCapture: () => {
839
+ keyBoardNavigation.current = false;
840
+ setIgnoreFocus(true);
841
+ },
842
+ // onMouseEnter would seem less expensive but it misses some cases
843
+ onMouseMove: () => {
844
+ if (keyBoardNavigation.current) {
845
+ keyBoardNavigation.current = false;
890
846
  }
847
+ },
848
+ onMouseLeave: () => {
849
+ keyBoardNavigation.current = true;
850
+ setIgnoreFocus(false);
851
+ setHighlightedIndex(-1);
891
852
  }
892
853
  }),
893
- [onActivate, onMouseEnterItem, rootId, triggerChildMenu]
854
+ [handleKeyDown, highlightedIndex, setHighlightedIdx, setHighlightedIndex]
894
855
  );
895
856
  return {
896
- closeMenu,
897
- handleRender,
898
- listItemProps,
899
- openMenu: triggerChildMenu,
900
- openMenus: openMenus.current
857
+ focusVisible: keyBoardNavigation.current ? highlightedIndex : -1,
858
+ controlledHighlighting,
859
+ highlightedIndex,
860
+ setHighlightedIndex,
861
+ // keyBoardNavigation,
862
+ listProps,
863
+ setIgnoreFocus
901
864
  };
902
865
  };
903
-
904
- // src/menu/ContextMenu.tsx
905
- var import_vuu_layout2 = require("@vuu-ui/vuu-layout");
906
- var import_jsx_runtime5 = require("react/jsx-runtime");
907
- var import_react9 = require("react");
908
- var noop = () => void 0;
909
- var ContextMenu = ({
910
- PortalProps: PortalProps2,
911
- activatedByKeyboard,
912
- children: childrenProp,
913
- className,
914
- id: idProp,
915
- onClose = () => void 0,
916
- position = { x: 0, y: 0 },
917
- style,
918
- ...menuListProps
919
- }) => {
920
- const closeHandlerRef = (0, import_react8.useRef)(onClose);
921
- closeHandlerRef.current = onClose;
922
- const id = (0, import_vuu_layout2.useId)(idProp);
923
- const closeMenuRef = (0, import_react8.useRef)(noop);
924
- const [menus, actions] = useItemsWithIdsNext(childrenProp, id);
925
- const navigatingWithKeyboard = (0, import_react8.useRef)(activatedByKeyboard);
926
- const handleMouseEnterItem = (0, import_react8.useCallback)(() => {
927
- navigatingWithKeyboard.current = false;
928
- }, []);
929
- const handleActivate = (0, import_react8.useCallback)(
930
- (menuItemId) => {
931
- const actionId = menuItemId.slice(9);
932
- const { action, options } = actions[actionId];
933
- closeMenuRef.current(id);
934
- onClose({
935
- type: "menu-action",
936
- menuId: action,
937
- options
938
- });
939
- },
940
- [actions, id, onClose]
941
- );
942
- const {
943
- closeMenu,
944
- listItemProps,
945
- openMenu: onOpenMenu,
946
- openMenus,
947
- handleRender
948
- } = useCascade({
949
- // FIXME
950
- id: `${id}`,
951
- onActivate: handleActivate,
952
- onMouseEnterItem: handleMouseEnterItem,
953
- position
954
- });
955
- closeMenuRef.current = closeMenu;
956
- const handleCloseMenu = () => {
957
- navigatingWithKeyboard.current = true;
958
- closeMenu();
959
- };
960
- const handleHighlightMenuItem = () => {
961
- };
962
- const lastMenu = openMenus.length - 1;
963
- const getChildMenuId = (i) => {
964
- if (i >= lastMenu) {
965
- return void 0;
866
+ function nextItemIdx(count, key, idx) {
867
+ if (key === "ArrowUp") {
868
+ if (idx > 0) {
869
+ return idx - 1;
966
870
  } else {
967
- const { id: id2 } = openMenus[i + 1];
968
- return id2;
871
+ return idx;
969
872
  }
970
- };
971
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: openMenus.map(({ id: menuId, left, top }, i, all) => {
972
- const childMenuId = getChildMenuId(i);
973
- return /* @__PURE__ */ (0, import_react9.createElement)(Portal, { ...PortalProps2, key: i, onRender: handleRender }, /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
974
- PopupComponent,
975
- {
976
- anchorElement: { current: document.body },
977
- placement: "absolute",
978
- position: { left, top },
979
- children: /* @__PURE__ */ (0, import_react9.createElement)(
980
- MenuList,
981
- {
982
- ...menuListProps,
983
- activatedByKeyboard: navigatingWithKeyboard.current,
984
- childMenuShowing: childMenuId,
985
- className,
986
- id: menuId,
987
- isRoot: i === 0,
988
- key: i,
989
- listItemProps,
990
- onActivate: handleActivate,
991
- onHighlightMenuItem: handleHighlightMenuItem,
992
- onCloseMenu: handleCloseMenu,
993
- openMenu: onOpenMenu,
994
- style,
995
- tabIndex: i === all.length - 1 ? 0 : void 0
996
- },
997
- menus[menuId]
998
- )
999
- }
1000
- ));
1001
- }) });
1002
- };
1003
- ContextMenu.displayName = "ContextMenu";
873
+ } else {
874
+ if (idx === null) {
875
+ return 0;
876
+ } else if (idx === count - 1) {
877
+ return idx;
878
+ } else {
879
+ return idx + 1;
880
+ }
881
+ }
882
+ }
1004
883
 
1005
- // src/menu/context-menu-provider.tsx
1006
- var import_react10 = require("react");
1007
- var import_jsx_runtime6 = require("react/jsx-runtime");
1008
- var ContextMenuContext = (0, import_react10.createContext)(
1009
- null
1010
- );
1011
- var Provider = ({
884
+ // src/menu/MenuList.tsx
885
+ var import_jsx_runtime5 = require("react/jsx-runtime");
886
+ var classBase3 = "vuuMenuList";
887
+ var Separator = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { className: "vuuMenuItem-divider" });
888
+ var MenuItemGroup = () => null;
889
+ var MenuItem = ({
1012
890
  children,
1013
- context,
1014
- menuActionHandler,
1015
- menuBuilder
891
+ idx,
892
+ options,
893
+ ...props
1016
894
  }) => {
1017
- const menuBuilders = (0, import_react10.useMemo)(() => {
1018
- if ((context == null ? void 0 : context.menuBuilders) && menuBuilder) {
1019
- return context.menuBuilders.concat(menuBuilder);
1020
- } else if (menuBuilder) {
1021
- return [menuBuilder];
1022
- } else {
1023
- return (context == null ? void 0 : context.menuBuilders) || [];
1024
- }
1025
- }, [context, menuBuilder]);
1026
- const handleMenuAction = (0, import_react10.useCallback)(
1027
- (reason) => {
1028
- var _a;
1029
- if (menuActionHandler == null ? void 0 : menuActionHandler(reason)) {
1030
- return true;
1031
- }
1032
- if ((_a = context == null ? void 0 : context.menuActionHandler) == null ? void 0 : _a.call(context, reason)) {
1033
- return true;
1034
- }
1035
- },
1036
- [context, menuActionHandler]
1037
- );
1038
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1039
- ContextMenuContext.Provider,
1040
- {
1041
- value: {
1042
- menuActionHandler: handleMenuAction,
1043
- menuBuilders
1044
- },
1045
- children
1046
- }
1047
- );
895
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ...props, children });
1048
896
  };
1049
- var ContextMenuProvider = ({
897
+ var MenuItemLabel = ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children });
898
+ MenuItemLabel.displayName = "MenuItemLabel";
899
+ MenuItem.Label = MenuItemLabel;
900
+ var getDisplayName = (item) => import_react8.default.isValidElement(item) && typeof item.type !== "string" && "displayName" in item.type ? item.type.displayName : void 0;
901
+ var isMenuItemLabel = (item) => getDisplayName(item) === "MenuItemLabel";
902
+ var hasIcon = (child) => child.props["data-icon"];
903
+ var MenuList = ({
904
+ activatedByKeyboard,
905
+ childMenuShowing,
1050
906
  children,
1051
- label,
1052
- menuActionHandler,
1053
- menuBuilder
907
+ className,
908
+ defaultHighlightedIdx,
909
+ highlightedIdx: highlightedIdxProp,
910
+ id: idProp,
911
+ isRoot: isRoot2,
912
+ listItemProps,
913
+ onHighlightMenuItem,
914
+ onActivate,
915
+ onCloseMenu,
916
+ openMenu: onOpenMenu,
917
+ ...props
1054
918
  }) => {
1055
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ContextMenuContext.Consumer, { children: (parentContext) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1056
- Provider,
919
+ const id = (0, import_vuu_utils4.useId)(idProp);
920
+ const root = (0, import_react8.useRef)(null);
921
+ const mapIdxToId = (0, import_react8.useMemo)(() => /* @__PURE__ */ new Map(), []);
922
+ const handleActivate = (idx) => {
923
+ var _a;
924
+ const el = (_a = root.current) == null ? void 0 : _a.querySelector(`:scope > [data-index='${idx}']`);
925
+ (el == null ? void 0 : el.id) && (onActivate == null ? void 0 : onActivate(el.id));
926
+ };
927
+ const { focusVisible, highlightedIndex, listProps } = useKeyboardNavigation({
928
+ count: import_react8.default.Children.count(children),
929
+ defaultHighlightedIdx,
930
+ highlightedIndex: highlightedIdxProp,
931
+ onActivate: handleActivate,
932
+ onHighlight: onHighlightMenuItem,
933
+ onOpenMenu,
934
+ onCloseMenu
935
+ });
936
+ const appliedFocusVisible = childMenuShowing == void 0 ? focusVisible : -1;
937
+ (0, import_react8.useLayoutEffect)(() => {
938
+ var _a;
939
+ if (childMenuShowing === void 0 && activatedByKeyboard) {
940
+ (_a = root.current) == null ? void 0 : _a.focus();
941
+ }
942
+ }, [activatedByKeyboard, childMenuShowing]);
943
+ const getActiveDescendant = () => highlightedIndex === void 0 || highlightedIndex === -1 ? void 0 : mapIdxToId.get(highlightedIndex);
944
+ function renderContent() {
945
+ const propsCommonToAllListItems = {
946
+ ...listItemProps,
947
+ role: "menuitem"
948
+ };
949
+ const maybeIcon = (childElement, withIcon, iconName) => withIcon ? [
950
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
951
+ "span",
952
+ {
953
+ className: "vuuIconContainer",
954
+ "data-icon": iconName
955
+ },
956
+ "icon"
957
+ )
958
+ ].concat(childElement) : childElement;
959
+ function addClonedChild(list, child, idx, withIcon) {
960
+ var _a;
961
+ const {
962
+ children: children2,
963
+ className: className2,
964
+ "data-icon": iconName,
965
+ id: itemId,
966
+ hasSeparator,
967
+ label,
968
+ ...props2
969
+ } = child.props;
970
+ const hasSubMenu = isMenuItemGroup(child);
971
+ const subMenuShowing = hasSubMenu && childMenuShowing === itemId;
972
+ const ariaControls = subMenuShowing ? `${id}-${itemId}` : void 0;
973
+ list.push(
974
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
975
+ MenuItem,
976
+ {
977
+ ...props2,
978
+ ...propsCommonToAllListItems,
979
+ ...getMenuItemProps(
980
+ itemId,
981
+ idx,
982
+ (_a = child.key) != null ? _a : itemId,
983
+ highlightedIndex,
984
+ appliedFocusVisible,
985
+ className2,
986
+ hasSeparator
987
+ ),
988
+ "aria-controls": ariaControls,
989
+ "aria-haspopup": hasSubMenu || void 0,
990
+ "aria-expanded": subMenuShowing || void 0,
991
+ children: hasSubMenu ? maybeIcon(label != null ? label : children2, withIcon, iconName) : maybeIcon(children2, withIcon, iconName)
992
+ }
993
+ )
994
+ );
995
+ }
996
+ const listItems = [];
997
+ if (children.length > 0) {
998
+ const withIcon = children.some(hasIcon);
999
+ children.forEach((child, idx) => {
1000
+ addClonedChild(listItems, child, idx, withIcon);
1001
+ });
1002
+ }
1003
+ return listItems;
1004
+ }
1005
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1006
+ "div",
1057
1007
  {
1058
- context: parentContext,
1059
- label,
1060
- menuActionHandler,
1061
- menuBuilder,
1062
- children
1008
+ ...props,
1009
+ ...listProps,
1010
+ "aria-activedescendant": getActiveDescendant(),
1011
+ className: (0, import_clsx6.default)(classBase3, className, {
1012
+ [`${classBase3}-childMenuShowing`]: childMenuShowing !== void 0
1013
+ }),
1014
+ "data-root": isRoot2 || void 0,
1015
+ id,
1016
+ ref: root,
1017
+ role: "menu",
1018
+ children: renderContent()
1063
1019
  }
1064
- ) });
1020
+ );
1065
1021
  };
1022
+ var getMenuItemProps = (itemId, idx, key, highlightedIdx, focusVisible, className, hasSeparator) => ({
1023
+ id: `menuitem-${itemId}`,
1024
+ key: key != null ? key : idx,
1025
+ "data-index": idx,
1026
+ "data-highlighted": idx === highlightedIdx || void 0,
1027
+ className: (0, import_clsx6.default)("vuuMenuItem", className, {
1028
+ "vuuMenuItem-separator": hasSeparator,
1029
+ focusVisible: focusVisible === idx
1030
+ })
1031
+ });
1032
+ MenuList.displayName = "MenuList";
1066
1033
 
1067
- // src/menu/useContextMenu.tsx
1068
- var import_vuu_utils2 = require("@vuu-ui/vuu-utils");
1069
- var import_react13 = require("react");
1070
-
1071
- // src/popup/popup-service.ts
1072
- var import_classnames5 = __toESM(require("classnames"));
1073
- var import_react11 = __toESM(require("react"));
1074
- var import_react_dom2 = __toESM(require("react-dom"));
1034
+ // src/menu/use-cascade.ts
1035
+ var import_react9 = require("react");
1075
1036
 
1076
- // src/portal-deprecated/render-portal.tsx
1077
- var ReactDOM = __toESM(require("react-dom"));
1078
- var import_classnames4 = __toESM(require("classnames"));
1079
- var containerId = 1;
1080
- var getPortalContainer = ({
1081
- className,
1082
- dataMode,
1083
- x = 0,
1084
- y = 0,
1085
- win = window
1086
- }) => {
1087
- const el = win.document.createElement("div");
1088
- el.className = (0, import_classnames4.default)(`vuuPopup ${containerId++}`, className);
1089
- el.style.cssText = `left:${x}px; top:${y}px;`;
1090
- if (dataMode) {
1091
- el.dataset.mode = dataMode;
1037
+ // src/menu/list-dom-utils.ts
1038
+ var closestListItem = (el) => el == null ? void 0 : el.closest("[data-index],[aria-posinset]");
1039
+
1040
+ // src/menu/use-cascade.ts
1041
+ var nudge = (menus, distance, pos) => {
1042
+ return menus.map(
1043
+ (m, i) => i === menus.length - 1 ? {
1044
+ ...m,
1045
+ [pos]: m[pos] - distance
1046
+ } : m
1047
+ );
1048
+ };
1049
+ var nudgeLeft = (menus, distance) => nudge(menus, distance, "left");
1050
+ var nudgeUp = (menus, distance) => nudge(menus, distance, "top");
1051
+ var flipSides = (id, menus) => {
1052
+ const [parentMenu, menu] = menus.slice(-2);
1053
+ const el = document.getElementById(`${id}-${menu.id}`);
1054
+ if (el === null) {
1055
+ throw Error(`useCascade.flipSides element with id ${menu.id} not found`);
1092
1056
  }
1093
- win.document.body.appendChild(el);
1094
- return el;
1057
+ const { width } = el.getBoundingClientRect();
1058
+ return menus.map(
1059
+ (m) => m === menu ? {
1060
+ ...m,
1061
+ left: parentMenu.left - (width - 2)
1062
+ } : m
1063
+ );
1095
1064
  };
1096
- var createContainer = (props) => getPortalContainer(props);
1097
- var renderPortal = (component, container, x, y, onRender) => {
1098
- container.style.cssText = `left:${x}px; top:${y}px;position: absolute;`;
1099
- ReactDOM.render(component, container, onRender);
1065
+ var getPosition = (el, openMenus) => {
1066
+ const [{ left, top: menuTop }] = openMenus.slice(-1);
1067
+ const { offsetWidth: width, offsetTop: top } = el;
1068
+ return { left: left + width, top: top + menuTop };
1100
1069
  };
1101
-
1102
- // src/popup/popup-service.ts
1103
- var _dialogOpen = false;
1104
- var _popups = [];
1105
- var reasonIsMenuAction = (reason) => (reason == null ? void 0 : reason.type) === "menu-action";
1106
- var reasonIsClickAway = (reason) => (reason == null ? void 0 : reason.type) === "click-away";
1107
- function specialKeyHandler(e) {
1108
- if (e.key === "Esc") {
1109
- if (_popups.length) {
1110
- closeAllPopups();
1111
- } else if (_dialogOpen) {
1112
- const dialogRoot = document.body.querySelector(".vuuDialog");
1113
- if (dialogRoot) {
1114
- import_react_dom2.default.unmountComponentAtNode(dialogRoot);
1115
- }
1116
- }
1117
- }
1118
- }
1119
- function outsideClickHandler(e) {
1120
- if (_popups.length) {
1121
- const popupContainers = document.body.querySelectorAll(
1122
- ".vuuPopup,#vuu-portal-root"
1123
- );
1124
- for (let i = 0; i < popupContainers.length; i++) {
1125
- if (popupContainers[i].contains(e.target)) {
1126
- return;
1127
- }
1128
- }
1129
- closeAllPopups({ mouseEvt: e, type: "click-away" });
1130
- }
1131
- }
1132
- function closeAllPopups(reason) {
1133
- if (_popups.length === 1) {
1134
- PopupService.hidePopup(reason, "anon", "all");
1135
- } else if (_popups.length) {
1136
- const popupContainers = document.body.querySelectorAll(".vuuPopup");
1137
- for (let i = 0; i < popupContainers.length; i++) {
1138
- import_react_dom2.default.unmountComponentAtNode(popupContainers[i]);
1139
- }
1140
- popupClosed("*");
1141
- }
1142
- }
1143
- function dialogOpened() {
1144
- if (_dialogOpen === false) {
1145
- _dialogOpen = true;
1146
- window.addEventListener("keydown", specialKeyHandler, true);
1147
- }
1148
- }
1149
- function dialogClosed() {
1150
- if (_dialogOpen) {
1151
- _dialogOpen = false;
1152
- window.removeEventListener("keydown", specialKeyHandler, true);
1153
- }
1154
- }
1155
- function popupOpened(name) {
1156
- if (_popups.indexOf(name) === -1) {
1157
- _popups.push(name);
1158
- if (_dialogOpen === false) {
1159
- window.addEventListener("keydown", specialKeyHandler, true);
1160
- window.addEventListener("click", outsideClickHandler, true);
1161
- }
1070
+ var getHostMenuId = (id, rootId) => {
1071
+ const pos = id.lastIndexOf("-");
1072
+ if (id.startsWith("menuitem")) {
1073
+ return pos > -1 ? id.slice(9, pos) : rootId;
1074
+ } else {
1075
+ return pos > -1 ? id.slice(0, pos) : rootId;
1162
1076
  }
1163
- }
1164
- function popupClosed(name) {
1165
- if (_popups.length) {
1166
- if (name === "*") {
1167
- _popups.length = 0;
1168
- } else {
1169
- const pos = _popups.indexOf(name);
1170
- if (pos !== -1) {
1171
- _popups.splice(pos, 1);
1172
- }
1173
- }
1174
- if (_popups.length === 0 && _dialogOpen === false) {
1175
- window.removeEventListener("keydown", specialKeyHandler, true);
1176
- window.removeEventListener("click", outsideClickHandler, true);
1177
- }
1077
+ };
1078
+ var getTargetMenuId = (id) => id.slice(9);
1079
+ var getMenuItemDetails = ({ ariaExpanded, ariaHasPopup, id }, rootId) => {
1080
+ if (id.startsWith("menuitem")) {
1081
+ return {
1082
+ hostMenuId: getHostMenuId(id, rootId),
1083
+ targetMenuId: getTargetMenuId(id),
1084
+ menuItemId: id,
1085
+ isGroup: ariaHasPopup === "true",
1086
+ isOpen: ariaExpanded === "true"
1087
+ };
1088
+ } else {
1089
+ throw Error(`getMenuItemDetails #${id} is not a menuitem`);
1178
1090
  }
1179
- }
1180
- var PopupComponent2 = ({
1181
- children,
1182
- position,
1183
- style
1184
- }) => {
1185
- const className = (0, import_classnames5.default)("hwPopup", "hwPopupContainer", position);
1186
- return (0, import_react11.createElement)("div", { className, style }, children);
1187
1091
  };
1188
- var incrementingKey = 1;
1189
- var PopupService = class {
1190
- static showPopup({
1191
- group = "all",
1192
- name = "anon",
1193
- left = 0,
1194
- position = "",
1195
- right = "auto",
1196
- top = 0,
1197
- width = "auto",
1198
- component
1199
- }) {
1200
- if (!component) {
1201
- throw Error(`PopupService showPopup, no component supplied`);
1202
- }
1203
- if (typeof component.props.onClose === "function") {
1204
- PopupService.onClose = component.props.onClose;
1205
- } else {
1206
- PopupService.onClose = void 0;
1207
- }
1208
- popupOpened(name);
1209
- document.addEventListener("keydown", PopupService.escapeKeyListener, true);
1210
- let el = document.body.querySelector(".vuuPopup." + group);
1211
- if (el === null) {
1212
- el = document.createElement("div");
1213
- el.className = "vuuPopup " + group;
1214
- document.body.appendChild(el);
1092
+ var useCascade = ({
1093
+ id: rootId,
1094
+ onActivate,
1095
+ onMouseEnterItem,
1096
+ position: { x: posX, y: posY }
1097
+ }) => {
1098
+ const [, forceRefresh] = (0, import_react9.useState)({});
1099
+ const openMenus = (0, import_react9.useRef)([
1100
+ { id: rootId, left: posX, top: posY }
1101
+ ]);
1102
+ const menuIsOpen = (0, import_react9.useCallback)(
1103
+ (menuId) => openMenus.current.findIndex((menu) => menu.id === menuId) !== -1,
1104
+ []
1105
+ );
1106
+ const getOpenMenuStatus = (0, import_react9.useCallback)((menuId) => {
1107
+ const state = menuState.current[menuId];
1108
+ if (state === void 0) {
1109
+ throw Error(`getOpenMenuState no entry for menu ${menuId}`);
1215
1110
  }
1216
- const style = { width };
1217
- renderPortal(
1218
- (0, import_react11.createElement)(
1219
- PopupComponent2,
1220
- { key: incrementingKey++, position, style },
1221
- component
1222
- ),
1223
- el,
1224
- left,
1225
- top,
1226
- () => {
1227
- PopupService.keepWithinThePage(el, right);
1111
+ return state;
1112
+ }, []);
1113
+ const setOpenMenus = (0, import_react9.useCallback)((menus) => {
1114
+ openMenus.current = menus;
1115
+ forceRefresh({});
1116
+ }, []);
1117
+ const menuOpenPendingTimeout = (0, import_react9.useRef)();
1118
+ const menuClosePendingTimeout = (0, import_react9.useRef)();
1119
+ const menuState = (0, import_react9.useRef)({ [rootId]: "no-popup" });
1120
+ const openMenu = (0, import_react9.useCallback)(
1121
+ (hostMenuId = rootId, targetMenuId, itemId = null) => {
1122
+ if (hostMenuId === rootId && itemId === null) {
1123
+ setOpenMenus([{ id: rootId, left: posX, top: posY }]);
1124
+ } else {
1125
+ menuState.current[hostMenuId] = "popup-open";
1126
+ const el = document.getElementById(itemId);
1127
+ if (el !== null) {
1128
+ const { left, top } = getPosition(el, openMenus.current);
1129
+ setOpenMenus(
1130
+ openMenus.current.concat({ id: targetMenuId, left, top })
1131
+ );
1132
+ } else {
1133
+ throw Error(`openMenu no menuItem ${itemId}`);
1134
+ }
1228
1135
  }
1229
- );
1230
- }
1231
- static escapeKeyListener(evt) {
1232
- if (evt.key === "Escape") {
1233
- PopupService.hidePopup({ type: "escape", event: evt });
1136
+ },
1137
+ [rootId, posX, posY, setOpenMenus]
1138
+ );
1139
+ const closeMenu = (0, import_react9.useCallback)(
1140
+ (menuId) => {
1141
+ if (menuId === rootId) {
1142
+ setOpenMenus([]);
1143
+ } else {
1144
+ const menus = openMenus.current.slice();
1145
+ const lastMenu = menus.pop();
1146
+ menuState.current[lastMenu.id] = "no-popup";
1147
+ const parentMenu = menus.at(-1);
1148
+ if (parentMenu) {
1149
+ menuState.current[parentMenu.id] = "no-popup";
1150
+ }
1151
+ setOpenMenus(menus);
1152
+ }
1153
+ },
1154
+ [rootId, setOpenMenus]
1155
+ );
1156
+ const closeMenus = (0, import_react9.useCallback)(
1157
+ (menuItemId) => {
1158
+ const menus = openMenus.current.slice();
1159
+ const menuItemMenuId = menuItemId.slice(9);
1160
+ let { id: lastMenuId } = menus.at(-1);
1161
+ while (menus.length > 1 && !menuItemMenuId.startsWith(lastMenuId)) {
1162
+ const parentMenuId = getHostMenuId(lastMenuId, rootId);
1163
+ menus.pop();
1164
+ menuState.current[lastMenuId] = "no-popup";
1165
+ menuState.current[parentMenuId] = "no-popup";
1166
+ ({ id: lastMenuId } = menus[menus.length - 1]);
1167
+ }
1168
+ if (menus.length < openMenus.current.length) {
1169
+ setOpenMenus(menus);
1170
+ }
1171
+ },
1172
+ [rootId, setOpenMenus]
1173
+ );
1174
+ const clearAnyScheduledOpenTasks = (0, import_react9.useCallback)(() => {
1175
+ if (menuOpenPendingTimeout.current) {
1176
+ clearTimeout(menuOpenPendingTimeout.current);
1177
+ menuOpenPendingTimeout.current = void 0;
1234
1178
  }
1235
- }
1236
- static hidePopup(reason, name = "anon", group = "all") {
1237
- var _a;
1238
- if (_popups.indexOf(name) !== -1) {
1239
- popupClosed(name);
1240
- const popupRoot = document.body.querySelector(`.vuuPopup.${group}`);
1241
- if (popupRoot) {
1242
- import_react_dom2.default.unmountComponentAtNode(popupRoot);
1179
+ }, []);
1180
+ const scheduleOpen = (0, import_react9.useCallback)(
1181
+ (hostMenuId, targetMenuId, menuItemId, delay = 300) => {
1182
+ clearAnyScheduledOpenTasks();
1183
+ menuOpenPendingTimeout.current = window.setTimeout(() => {
1184
+ closeMenus(menuItemId);
1185
+ menuState.current[hostMenuId] = "popup-open";
1186
+ menuState.current[targetMenuId] = "no-popup";
1187
+ openMenu(hostMenuId, targetMenuId, menuItemId);
1188
+ }, delay);
1189
+ },
1190
+ [clearAnyScheduledOpenTasks, closeMenus, openMenu]
1191
+ );
1192
+ const scheduleClose = (0, import_react9.useCallback)(
1193
+ (hostMenuId, openMenuId, itemId) => {
1194
+ menuState.current[openMenuId] = "pending-close";
1195
+ menuClosePendingTimeout.current = window.setTimeout(() => {
1196
+ closeMenus(itemId);
1197
+ }, 400);
1198
+ },
1199
+ [closeMenus]
1200
+ );
1201
+ const handleRender = (0, import_react9.useCallback)(() => {
1202
+ const { current: menus } = openMenus;
1203
+ const menu = menus.at(-1);
1204
+ const el = menu ? document.getElementById(menu.id) : void 0;
1205
+ if (el) {
1206
+ const { right, bottom } = el.getBoundingClientRect();
1207
+ const { clientHeight, clientWidth } = document.body;
1208
+ if (right > clientWidth) {
1209
+ const newMenus = menus.length > 1 ? flipSides(rootId, menus) : nudgeLeft(menus, right - clientWidth);
1210
+ setOpenMenus(newMenus);
1211
+ } else if (bottom > clientHeight) {
1212
+ const newMenus = nudgeUp(menus, bottom - clientHeight);
1213
+ setOpenMenus(newMenus);
1214
+ }
1215
+ if (typeof el.tabIndex === "number") {
1216
+ el.focus();
1243
1217
  }
1244
1218
  }
1245
- document.removeEventListener(
1246
- "keydown",
1247
- PopupService.escapeKeyListener,
1248
- true
1249
- );
1250
- (_a = PopupService == null ? void 0 : PopupService.onClose) == null ? void 0 : _a.call(PopupService, reason);
1251
- }
1252
- static keepWithinThePage(el, right = "auto") {
1253
- const target = el.querySelector(".vuuPopupContainer > *");
1254
- if (target) {
1219
+ }, [rootId, setOpenMenus]);
1220
+ const triggerChildMenu = (0, import_react9.useCallback)(
1221
+ (menuItemEl, immediate = false) => {
1222
+ const { hostMenuId, targetMenuId, menuItemId, isGroup, isOpen } = getMenuItemDetails(menuItemEl, rootId);
1255
1223
  const {
1256
- top,
1257
- left,
1258
- width,
1259
- height,
1260
- right: currentRight
1261
- } = target.getBoundingClientRect();
1262
- const w = window.innerWidth;
1263
- const h = window.innerHeight;
1264
- const overflowH = h - (top + height);
1265
- if (overflowH < 0) {
1266
- target.style.top = Math.round(top) + overflowH + "px";
1224
+ current: { [hostMenuId]: state }
1225
+ } = menuState;
1226
+ const delay = immediate ? 0 : void 0;
1227
+ if (state === "no-popup" && isGroup) {
1228
+ menuState.current[hostMenuId] = "popup-pending";
1229
+ scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
1230
+ } else if (state === "popup-pending" && !isGroup) {
1231
+ menuState.current[hostMenuId] = "no-popup";
1232
+ clearTimeout(menuOpenPendingTimeout.current);
1233
+ menuOpenPendingTimeout.current = void 0;
1234
+ } else if (state === "popup-pending" && isGroup) {
1235
+ clearTimeout(menuOpenPendingTimeout.current);
1236
+ scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
1237
+ } else if (state === "popup-open") {
1238
+ if (menuIsOpen(targetMenuId)) {
1239
+ const menuStatus = getOpenMenuStatus(targetMenuId);
1240
+ closeMenus(menuItemId);
1241
+ switch (menuStatus) {
1242
+ case "pending-close":
1243
+ clearTimeout(menuClosePendingTimeout.current);
1244
+ menuClosePendingTimeout.current = void 0;
1245
+ menuState.current[targetMenuId] = "no-popup";
1246
+ clearAnyScheduledOpenTasks();
1247
+ break;
1248
+ default:
1249
+ }
1250
+ } else {
1251
+ const [parentOfLastOpenedMenu, lastOpenedMenu] = openMenus.current.slice(-2);
1252
+ if (parentOfLastOpenedMenu.id === hostMenuId && menuState.current[lastOpenedMenu.id] !== "pending-close") {
1253
+ scheduleClose(hostMenuId, lastOpenedMenu.id, menuItemId);
1254
+ if (isGroup && !isOpen) {
1255
+ scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
1256
+ }
1257
+ } else if (parentOfLastOpenedMenu.id === hostMenuId && isGroup && menuItemId !== lastOpenedMenu.id && menuState.current[lastOpenedMenu.id] === "pending-close") {
1258
+ scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
1259
+ } else if (isGroup) {
1260
+ scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
1261
+ } else if (!(menuState.current[lastOpenedMenu.id] === "pending-close")) {
1262
+ closeMenus(menuItemId);
1263
+ }
1264
+ }
1267
1265
  }
1268
- const overflowW = w - (left + width);
1269
- if (overflowW < 0) {
1270
- target.style.left = Math.round(left) + overflowW + "px";
1266
+ if (state === "pending-close") {
1267
+ clearAnyScheduledOpenTasks();
1268
+ clearTimeout(menuClosePendingTimeout.current);
1269
+ menuClosePendingTimeout.current = void 0;
1270
+ menuState.current[hostMenuId] = "popup-open";
1271
1271
  }
1272
- if (typeof right === "number" && right !== currentRight) {
1273
- const adjustment = right - currentRight;
1274
- target.style.left = left + adjustment + "px";
1272
+ },
1273
+ [
1274
+ clearAnyScheduledOpenTasks,
1275
+ closeMenus,
1276
+ getOpenMenuStatus,
1277
+ menuIsOpen,
1278
+ rootId,
1279
+ scheduleClose,
1280
+ scheduleOpen
1281
+ ]
1282
+ );
1283
+ const listItemProps = (0, import_react9.useMemo)(
1284
+ () => ({
1285
+ onMouseEnter: (evt) => {
1286
+ const menuItemEl = closestListItem(evt.target);
1287
+ triggerChildMenu(menuItemEl);
1288
+ onMouseEnterItem(evt, menuItemEl.id);
1289
+ },
1290
+ onClick: (evt) => {
1291
+ const listItemEl = closestListItem(evt.target);
1292
+ const { isGroup, menuItemId } = getMenuItemDetails(listItemEl, rootId);
1293
+ if (isGroup) {
1294
+ triggerChildMenu(listItemEl);
1295
+ } else {
1296
+ onActivate(menuItemId);
1297
+ }
1275
1298
  }
1276
- }
1277
- }
1299
+ }),
1300
+ [onActivate, onMouseEnterItem, rootId, triggerChildMenu]
1301
+ );
1302
+ return {
1303
+ closeMenu,
1304
+ handleRender,
1305
+ listItemProps,
1306
+ openMenu: triggerChildMenu,
1307
+ openMenus: openMenus.current
1308
+ };
1278
1309
  };
1279
- var DialogService = class {
1280
- static showDialog(dialog) {
1281
- const containerEl = ".vuuDialog";
1282
- const onClose = dialog.props.onClose;
1283
- dialogOpened();
1284
- import_react_dom2.default.render(
1285
- import_react11.default.cloneElement(dialog, {
1286
- container: containerEl,
1287
- onClose: () => {
1288
- DialogService.closeDialog();
1289
- if (onClose) {
1290
- onClose();
1291
- }
1292
- }
1293
- }),
1294
- document.body.querySelector(containerEl)
1295
- );
1296
- }
1297
- static closeDialog() {
1298
- dialogClosed();
1299
- const dialogRoot = document.body.querySelector(".vuuDialog");
1300
- if (dialogRoot) {
1301
- import_react_dom2.default.unmountComponentAtNode(dialogRoot);
1310
+
1311
+ // src/menu/ContextMenu.tsx
1312
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1313
+ var import_react11 = require("react");
1314
+ var noop = () => void 0;
1315
+ var ContextMenu = ({
1316
+ PortalProps: PortalProps2,
1317
+ activatedByKeyboard,
1318
+ children: childrenProp,
1319
+ className,
1320
+ id: idProp,
1321
+ onClose = () => void 0,
1322
+ position = { x: 0, y: 0 },
1323
+ style,
1324
+ ...menuListProps
1325
+ }) => {
1326
+ const closeHandlerRef = (0, import_react10.useRef)(onClose);
1327
+ closeHandlerRef.current = onClose;
1328
+ const id = (0, import_vuu_utils5.useId)(idProp);
1329
+ const closeMenuRef = (0, import_react10.useRef)(noop);
1330
+ const [menus, actions] = useItemsWithIdsNext(childrenProp, id);
1331
+ const navigatingWithKeyboard = (0, import_react10.useRef)(activatedByKeyboard);
1332
+ const handleMouseEnterItem = (0, import_react10.useCallback)(() => {
1333
+ navigatingWithKeyboard.current = false;
1334
+ }, []);
1335
+ const handleActivate = (0, import_react10.useCallback)(
1336
+ (menuItemId) => {
1337
+ const actionId = menuItemId.slice(9);
1338
+ const { action, options } = actions[actionId];
1339
+ closeMenuRef.current(id);
1340
+ onClose({
1341
+ type: "menu-action",
1342
+ menuId: action,
1343
+ options
1344
+ });
1345
+ },
1346
+ [actions, id, onClose]
1347
+ );
1348
+ const {
1349
+ closeMenu,
1350
+ listItemProps,
1351
+ openMenu: onOpenMenu,
1352
+ openMenus,
1353
+ handleRender
1354
+ } = useCascade({
1355
+ // FIXME
1356
+ id: `${id}`,
1357
+ onActivate: handleActivate,
1358
+ onMouseEnterItem: handleMouseEnterItem,
1359
+ position
1360
+ });
1361
+ closeMenuRef.current = closeMenu;
1362
+ const handleCloseMenu = () => {
1363
+ navigatingWithKeyboard.current = true;
1364
+ closeMenu();
1365
+ };
1366
+ const handleHighlightMenuItem = () => {
1367
+ };
1368
+ const lastMenu = openMenus.length - 1;
1369
+ const getChildMenuId = (i) => {
1370
+ if (i >= lastMenu) {
1371
+ return void 0;
1372
+ } else {
1373
+ const { id: id2 } = openMenus[i + 1];
1374
+ return id2;
1302
1375
  }
1303
- }
1376
+ };
1377
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: openMenus.map(({ id: menuId, left, top }, i, all) => {
1378
+ const childMenuId = getChildMenuId(i);
1379
+ return /* @__PURE__ */ (0, import_react11.createElement)(Portal, { ...PortalProps2, key: i, onRender: handleRender }, /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1380
+ PopupComponent2,
1381
+ {
1382
+ anchorElement: { current: document.body },
1383
+ placement: "absolute",
1384
+ position: { left, top },
1385
+ children: /* @__PURE__ */ (0, import_react11.createElement)(
1386
+ MenuList,
1387
+ {
1388
+ ...menuListProps,
1389
+ activatedByKeyboard: navigatingWithKeyboard.current,
1390
+ childMenuShowing: childMenuId,
1391
+ className,
1392
+ id: menuId,
1393
+ isRoot: i === 0,
1394
+ key: i,
1395
+ listItemProps,
1396
+ onActivate: handleActivate,
1397
+ onHighlightMenuItem: handleHighlightMenuItem,
1398
+ onCloseMenu: handleCloseMenu,
1399
+ openMenu: onOpenMenu,
1400
+ style,
1401
+ tabIndex: i === all.length - 1 ? 0 : void 0
1402
+ },
1403
+ menus[menuId]
1404
+ )
1405
+ }
1406
+ ));
1407
+ }) });
1304
1408
  };
1409
+ ContextMenu.displayName = "ContextMenu";
1305
1410
 
1306
- // src/popup/Popup.tsx
1307
- var import_classnames6 = __toESM(require("classnames"));
1308
-
1309
- // src/popup/useAnchoredPosition.ts
1411
+ // src/menu/context-menu-provider.tsx
1310
1412
  var import_react12 = require("react");
1311
- var getPositionRelativeToAnchor = (anchorElement, placement, offsetLeft, offsetTop, minWidth, dimensions) => {
1312
- const { bottom, height, left, right, top, width } = anchorElement.getBoundingClientRect();
1313
- switch (placement) {
1314
- case "below":
1315
- return { left: left + offsetLeft, top: bottom + offsetTop };
1316
- case "right":
1317
- return { left: right + offsetLeft, top: top + offsetTop };
1318
- case "below-center":
1319
- return { left: left + width / 2 + offsetLeft, top: bottom + offsetTop };
1320
- case "below-right":
1321
- return { left, minWidth, top: bottom + offsetTop };
1322
- case "below-full-width":
1323
- return {
1324
- left: left + offsetLeft,
1325
- minWidth,
1326
- top: bottom + offsetTop,
1327
- width
1328
- };
1329
- case "center":
1330
- if (dimensions) {
1331
- return {
1332
- left: width / 2 - dimensions.width / 2 + offsetLeft,
1333
- top: height / 2 - dimensions.height / 2 + offsetTop,
1334
- visibility: "visible"
1335
- };
1336
- } else {
1337
- return {
1338
- left: width / 2 + offsetLeft,
1339
- top: height / 2 + offsetTop,
1340
- visibility: "hidden"
1341
- };
1342
- }
1343
- default:
1344
- throw Error(
1345
- "Popup getPositionRelativeToAnchor only supported placement values are below and right"
1346
- );
1347
- }
1348
- };
1349
- var useAnchoredPosition = ({
1350
- anchorElement,
1351
- minWidth,
1352
- offsetLeft = 0,
1353
- offsetTop = 0,
1354
- placement,
1355
- position: positionProp
1413
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1414
+ var ContextMenuContext = (0, import_react12.createContext)(
1415
+ null
1416
+ );
1417
+ var Provider = ({
1418
+ children,
1419
+ context,
1420
+ menuActionHandler,
1421
+ menuBuilder
1356
1422
  }) => {
1357
- const popupRef = (0, import_react12.useRef)(null);
1358
- const [position, setPosition] = (0, import_react12.useState)(positionProp);
1359
- (0, import_react12.useLayoutEffect)(() => {
1360
- if (placement === "absolute" && positionProp) {
1361
- setPosition(positionProp);
1362
- } else if (anchorElement.current) {
1363
- const dimensions = popupRef.current === null ? void 0 : popupRef.current.getBoundingClientRect();
1364
- const position2 = getPositionRelativeToAnchor(
1365
- anchorElement.current,
1366
- placement,
1367
- offsetLeft,
1368
- offsetTop,
1369
- minWidth,
1370
- dimensions
1371
- );
1372
- setPosition(position2);
1423
+ const menuBuilders = (0, import_react12.useMemo)(() => {
1424
+ if ((context == null ? void 0 : context.menuBuilders) && menuBuilder) {
1425
+ return context.menuBuilders.concat(menuBuilder);
1426
+ } else if (menuBuilder) {
1427
+ return [menuBuilder];
1428
+ } else {
1429
+ return (context == null ? void 0 : context.menuBuilders) || [];
1373
1430
  }
1374
- }, [anchorElement, minWidth, offsetLeft, offsetTop, placement, positionProp]);
1375
- const popupCallbackRef = (0, import_react12.useCallback)(
1376
- (el) => {
1377
- popupRef.current = el;
1378
- if (el && placement === "center" && anchorElement.current) {
1379
- const { height, width } = el.getBoundingClientRect();
1380
- setPosition(
1381
- getPositionRelativeToAnchor(
1382
- anchorElement.current,
1383
- placement,
1384
- offsetLeft,
1385
- offsetTop,
1386
- void 0,
1387
- { height, width }
1388
- )
1389
- );
1431
+ }, [context, menuBuilder]);
1432
+ const handleMenuAction = (0, import_react12.useCallback)(
1433
+ (reason) => {
1434
+ var _a;
1435
+ if (menuActionHandler == null ? void 0 : menuActionHandler(reason)) {
1436
+ return true;
1437
+ }
1438
+ if ((_a = context == null ? void 0 : context.menuActionHandler) == null ? void 0 : _a.call(context, reason)) {
1439
+ return true;
1390
1440
  }
1391
1441
  },
1392
- [anchorElement, offsetLeft, offsetTop, placement]
1442
+ [context, menuActionHandler]
1443
+ );
1444
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1445
+ ContextMenuContext.Provider,
1446
+ {
1447
+ value: {
1448
+ menuActionHandler: handleMenuAction,
1449
+ menuBuilders
1450
+ },
1451
+ children
1452
+ }
1393
1453
  );
1394
- return {
1395
- position,
1396
- popupRef: placement === "center" ? popupCallbackRef : void 0
1397
- };
1398
1454
  };
1399
-
1400
- // src/popup/Popup.tsx
1401
- var import_jsx_runtime7 = require("react/jsx-runtime");
1402
- var PopupComponent = ({
1455
+ var ContextMenuProvider = ({
1403
1456
  children,
1404
- className,
1405
- anchorElement,
1406
- minWidth,
1407
- placement,
1408
- position: positionProp
1457
+ label,
1458
+ menuActionHandler,
1459
+ menuBuilder
1409
1460
  }) => {
1410
- const { popupRef, position } = useAnchoredPosition({
1411
- anchorElement,
1412
- minWidth,
1413
- placement,
1414
- position: positionProp
1415
- });
1416
- return position === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: (0, import_classnames6.default)(`vuuPortal`, className), ref: popupRef, style: position, children });
1461
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ContextMenuContext.Consumer, { children: (parentContext) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1462
+ Provider,
1463
+ {
1464
+ context: parentContext,
1465
+ label,
1466
+ menuActionHandler,
1467
+ menuBuilder,
1468
+ children
1469
+ }
1470
+ ) });
1417
1471
  };
1418
1472
 
1419
1473
  // src/menu/useContextMenu.tsx
1420
- var import_vuu_shell2 = require("@vuu-ui/vuu-shell");
1474
+ var import_vuu_utils6 = require("@vuu-ui/vuu-utils");
1475
+ var import_react13 = require("react");
1421
1476
  var import_jsx_runtime8 = require("react/jsx-runtime");
1422
1477
  var useContextMenu = (menuBuilder, menuActionHandler) => {
1423
1478
  const ctx = (0, import_react13.useContext)(ContextMenuContext);
1424
- const [themeClass, densityClass, dataMode] = (0, import_vuu_shell2.useThemeAttributes)();
1479
+ const [themeClass, densityClass, dataMode] = (0, import_vuu_utils6.useThemeAttributes)();
1425
1480
  const themeAttributes = (0, import_react13.useMemo)(
1426
1481
  () => ({
1427
1482
  themeClass,
@@ -1509,7 +1564,7 @@ var showContextMenu = (e, menuDescriptors, handleContextMenuAction, {
1509
1564
  ...contextMenuProps
1510
1565
  } = NO_OPTIONS) => {
1511
1566
  const menuItems = (menuDescriptors2) => {
1512
- const fromDescriptor = (menuItem, i) => (0, import_vuu_utils2.isGroupMenuItemDescriptor)(menuItem) ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MenuItemGroup, { label: menuItem.label, children: menuItem.children.map(fromDescriptor) }, i) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1567
+ const fromDescriptor = (menuItem, i) => (0, import_vuu_utils6.isGroupMenuItemDescriptor)(menuItem) ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MenuItemGroup, { label: menuItem.label, children: menuItem.children.map(fromDescriptor) }, i) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1513
1568
  MenuItem,
1514
1569
  {
1515
1570
  action: menuItem.action,
@@ -1547,10 +1602,10 @@ var showContextMenu = (e, menuDescriptors, handleContextMenuAction, {
1547
1602
  };
1548
1603
 
1549
1604
  // src/popup-menu/PopupMenu.tsx
1550
- var import_react14 = require("react");
1551
- var import_classnames7 = __toESM(require("classnames"));
1605
+ var import_vuu_utils7 = require("@vuu-ui/vuu-utils");
1552
1606
  var import_core2 = require("@salt-ds/core");
1553
- var import_vuu_layout3 = require("@vuu-ui/vuu-layout");
1607
+ var import_clsx7 = __toESM(require("clsx"));
1608
+ var import_react14 = require("react");
1554
1609
  var import_jsx_runtime9 = require("react/jsx-runtime");
1555
1610
  var classBase4 = "vuuPopupMenu";
1556
1611
  var getPosition2 = (element) => {
@@ -1575,7 +1630,7 @@ var PopupMenu = ({
1575
1630
  const rootRef = (0, import_react14.useRef)(null);
1576
1631
  const suppressShowMenuRef = (0, import_react14.useRef)(false);
1577
1632
  const [menuOpen, setMenuOpen] = (0, import_react14.useState)(false);
1578
- const id = (0, import_vuu_layout3.useId)(idProp);
1633
+ const id = (0, import_vuu_utils7.useId)(idProp);
1579
1634
  const [showContextMenu2] = useContextMenu(menuBuilder, menuActionHandler);
1580
1635
  const handleOpenMenu = (0, import_react14.useCallback)((el) => {
1581
1636
  console.log(`menu Open `, {
@@ -1635,7 +1690,7 @@ var PopupMenu = ({
1635
1690
  "aria-controls": `${id}-menu-root`,
1636
1691
  "aria-expanded": menuOpen,
1637
1692
  "aria-haspopup": "menu",
1638
- className: (0, import_classnames7.default)(classBase4, className, {
1693
+ className: (0, import_clsx7.default)(classBase4, className, {
1639
1694
  [`${classBase4}-withCaption`]: label !== void 0,
1640
1695
  [`${classBase4}-open`]: menuOpen
1641
1696
  }),
@@ -1651,16 +1706,16 @@ var PopupMenu = ({
1651
1706
  };
1652
1707
 
1653
1708
  // src/prompt/Prompt.tsx
1654
- var import_vuu_shell3 = require("@vuu-ui/vuu-shell");
1709
+ var import_vuu_utils8 = require("@vuu-ui/vuu-utils");
1655
1710
  var import_core3 = require("@salt-ds/core");
1656
- var import_classnames8 = __toESM(require("classnames"));
1711
+ var import_clsx8 = __toESM(require("clsx"));
1657
1712
  var import_react15 = require("react");
1658
1713
  var import_jsx_runtime10 = require("react/jsx-runtime");
1659
1714
  var classBase5 = "vuuPrompt";
1660
- var AnchorBody = { current: document.body };
1661
- var EMPTY_PROPS = {};
1715
+ var AnchorBody2 = { current: document.body };
1716
+ var EMPTY_PROPS2 = {};
1662
1717
  var Prompt = ({
1663
- PopupProps = EMPTY_PROPS,
1718
+ PopupProps = EMPTY_PROPS2,
1664
1719
  cancelButtonLabel = "Cancel",
1665
1720
  confirmButtonLabel = "Confirm",
1666
1721
  icon,
@@ -1673,12 +1728,12 @@ var Prompt = ({
1673
1728
  ...htmlAttributes
1674
1729
  }) => {
1675
1730
  const {
1676
- anchorElement = AnchorBody,
1731
+ anchorElement = AnchorBody2,
1677
1732
  offsetLeft = 0,
1678
1733
  offsetTop = 0,
1679
1734
  placement = "below"
1680
1735
  } = PopupProps;
1681
- const [themeClass, densityClass, dataMode] = (0, import_vuu_shell3.useThemeAttributes)();
1736
+ const [themeClass, densityClass, dataMode] = (0, import_vuu_utils8.useThemeAttributes)();
1682
1737
  const { position } = useAnchoredPosition({
1683
1738
  anchorElement,
1684
1739
  offsetLeft,
@@ -1703,7 +1758,7 @@ var Prompt = ({
1703
1758
  "dialog",
1704
1759
  {
1705
1760
  ...htmlAttributes,
1706
- className: (0, import_classnames8.default)(classBase5, `${classBase5}-${variant}`, themeClass),
1761
+ className: (0, import_clsx8.default)(classBase5, `${classBase5}-${variant}`, themeClass),
1707
1762
  "data-mode": dataMode,
1708
1763
  ref: rootRef,
1709
1764
  style: { ...style, ...position },
@@ -1762,7 +1817,7 @@ var useAnchoredPosition2 = ({
1762
1817
  };
1763
1818
 
1764
1819
  // src/tooltip/Tooltip.tsx
1765
- var import_classnames9 = __toESM(require("classnames"));
1820
+ var import_clsx9 = __toESM(require("clsx"));
1766
1821
  var import_jsx_runtime11 = require("react/jsx-runtime");
1767
1822
  var classBase6 = "vuuTooltip";
1768
1823
  var Tooltip = ({
@@ -1782,7 +1837,7 @@ var Tooltip = ({
1782
1837
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1783
1838
  "div",
1784
1839
  {
1785
- className: (0, import_classnames9.default)(classBase6, {
1840
+ className: (0, import_clsx9.default)(classBase6, {
1786
1841
  [`${classBase6}-error`]: status === "error"
1787
1842
  }),
1788
1843
  "data-align": placement,
@@ -1802,8 +1857,8 @@ var Tooltip = ({
1802
1857
  };
1803
1858
 
1804
1859
  // src/tooltip/useTooltip.ts
1860
+ var import_vuu_utils9 = require("@vuu-ui/vuu-utils");
1805
1861
  var import_react17 = require("react");
1806
- var import_vuu_layout4 = require("@vuu-ui/vuu-layout");
1807
1862
  var useTooltip = ({
1808
1863
  id: idProp,
1809
1864
  placement = "right",
@@ -1814,7 +1869,7 @@ var useTooltip = ({
1814
1869
  const mouseEnterTimerRef = (0, import_react17.useRef)();
1815
1870
  const mouseLeaveTimerRef = (0, import_react17.useRef)();
1816
1871
  const [tooltipProps, setTooltipProps] = (0, import_react17.useState)();
1817
- const id = (0, import_vuu_layout4.useId)(idProp);
1872
+ const id = (0, import_vuu_utils9.useId)(idProp);
1818
1873
  const escapeListener = (0, import_react17.useCallback)((evt) => {
1819
1874
  var _a;
1820
1875
  if (evt.key === "Escape") {
@@ -1891,8 +1946,8 @@ var useTooltip = ({
1891
1946
 
1892
1947
  // src/notifications/NotificationsProvider.tsx
1893
1948
  var import_react18 = __toESM(require("react"));
1894
- var import_classnames10 = __toESM(require("classnames"));
1895
- var import_vuu_utils3 = require("@vuu-ui/vuu-utils");
1949
+ var import_clsx10 = __toESM(require("clsx"));
1950
+ var import_vuu_utils10 = require("@vuu-ui/vuu-utils");
1896
1951
  var import_jsx_runtime12 = require("react/jsx-runtime");
1897
1952
  var toastOffsetTop = 60;
1898
1953
  var toastDisplayDuration = 6e3;
@@ -1917,7 +1972,7 @@ var NotificationsContext = import_react18.default.createContext({
1917
1972
  var NotificationsProvider = (props) => {
1918
1973
  const [notifications, setNotifications] = (0, import_react18.useState)([]);
1919
1974
  const notify = (0, import_react18.useCallback)((notification) => {
1920
- const newNotification = { ...notification, id: (0, import_vuu_utils3.getUniqueId)() };
1975
+ const newNotification = { ...notification, id: (0, import_vuu_utils10.getUniqueId)() };
1921
1976
  setNotifications((prev) => [...prev, newNotification]);
1922
1977
  setTimeout(() => {
1923
1978
  setNotifications((prev) => prev.filter((n) => n !== newNotification));
@@ -1959,7 +2014,7 @@ var ToastNotification = (props) => {
1959
2014
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1960
2015
  "div",
1961
2016
  {
1962
- className: (0, import_classnames10.default)(`${classBase7}-toast`, notification.type),
2017
+ className: (0, import_clsx10.default)(`${classBase7}-toast`, notification.type),
1963
2018
  style: {
1964
2019
  height: toastHeight,
1965
2020
  right,
@@ -1971,7 +2026,7 @@ var ToastNotification = (props) => {
1971
2026
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1972
2027
  "div",
1973
2028
  {
1974
- className: (0, import_classnames10.default)(
2029
+ className: (0, import_clsx10.default)(
1975
2030
  `${classBase7}-toastIcon`,
1976
2031
  `${notification.type}-icon`
1977
2032
  )