@vuu-ui/vuu-popups 0.0.26

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.
Files changed (198) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +0 -0
  3. package/cjs/dialog/Dialog.css.js +6 -0
  4. package/cjs/dialog/Dialog.css.js.map +1 -0
  5. package/cjs/dialog/Dialog.js +96 -0
  6. package/cjs/dialog/Dialog.js.map +1 -0
  7. package/cjs/dialog/useDialog.js +31 -0
  8. package/cjs/dialog/useDialog.js.map +1 -0
  9. package/cjs/dialog-header/DialogHeader.css.js +6 -0
  10. package/cjs/dialog-header/DialogHeader.css.js.map +1 -0
  11. package/cjs/dialog-header/DialogHeader.js +39 -0
  12. package/cjs/dialog-header/DialogHeader.js.map +1 -0
  13. package/cjs/index.js +53 -0
  14. package/cjs/index.js.map +1 -0
  15. package/cjs/menu/ContextMenu.js +119 -0
  16. package/cjs/menu/ContextMenu.js.map +1 -0
  17. package/cjs/menu/MenuList.css.js +6 -0
  18. package/cjs/menu/MenuList.css.js.map +1 -0
  19. package/cjs/menu/MenuList.js +172 -0
  20. package/cjs/menu/MenuList.js.map +1 -0
  21. package/cjs/menu/context-menu-provider.js +66 -0
  22. package/cjs/menu/context-menu-provider.js.map +1 -0
  23. package/cjs/menu/key-code.js +54 -0
  24. package/cjs/menu/key-code.js.map +1 -0
  25. package/cjs/menu/list-dom-utils.js +6 -0
  26. package/cjs/menu/list-dom-utils.js.map +1 -0
  27. package/cjs/menu/use-cascade.js +277 -0
  28. package/cjs/menu/use-cascade.js.map +1 -0
  29. package/cjs/menu/use-items-with-ids-next.js +68 -0
  30. package/cjs/menu/use-items-with-ids-next.js.map +1 -0
  31. package/cjs/menu/use-keyboard-navigation.js +148 -0
  32. package/cjs/menu/use-keyboard-navigation.js.map +1 -0
  33. package/cjs/menu/useContextMenu.js +143 -0
  34. package/cjs/menu/useContextMenu.js.map +1 -0
  35. package/cjs/menu/utils.js +8 -0
  36. package/cjs/menu/utils.js.map +1 -0
  37. package/cjs/notifications/NotificationsCenter.js +40 -0
  38. package/cjs/notifications/NotificationsCenter.js.map +1 -0
  39. package/cjs/notifications/NotificationsProvider.js +32 -0
  40. package/cjs/notifications/NotificationsProvider.js.map +1 -0
  41. package/cjs/notifications/ToastNotification.css.js +6 -0
  42. package/cjs/notifications/ToastNotification.css.js.map +1 -0
  43. package/cjs/notifications/ToastNotification.js +66 -0
  44. package/cjs/notifications/ToastNotification.js.map +1 -0
  45. package/cjs/popup/Popup.css.js +6 -0
  46. package/cjs/popup/Popup.css.js.map +1 -0
  47. package/cjs/popup/Popup.js +38 -0
  48. package/cjs/popup/Popup.js.map +1 -0
  49. package/cjs/popup/getPositionRelativeToAnchor.js +43 -0
  50. package/cjs/popup/getPositionRelativeToAnchor.js.map +1 -0
  51. package/cjs/popup/popup-service.js +219 -0
  52. package/cjs/popup/popup-service.js.map +1 -0
  53. package/cjs/popup/useAnchoredPosition.js +58 -0
  54. package/cjs/popup/useAnchoredPosition.js.map +1 -0
  55. package/cjs/popup-menu/PopupMenu.css.js +6 -0
  56. package/cjs/popup-menu/PopupMenu.css.js.map +1 -0
  57. package/cjs/popup-menu/PopupMenu.js +95 -0
  58. package/cjs/popup-menu/PopupMenu.js.map +1 -0
  59. package/cjs/popup-menu/usePopupMenu.js +120 -0
  60. package/cjs/popup-menu/usePopupMenu.js.map +1 -0
  61. package/cjs/portal/Portal.css.js +6 -0
  62. package/cjs/portal/Portal.css.js.map +1 -0
  63. package/cjs/portal/Portal.js +73 -0
  64. package/cjs/portal/Portal.js.map +1 -0
  65. package/cjs/portal-deprecated/render-portal.js +51 -0
  66. package/cjs/portal-deprecated/render-portal.js.map +1 -0
  67. package/cjs/prompt/Prompt.css.js +6 -0
  68. package/cjs/prompt/Prompt.css.js.map +1 -0
  69. package/cjs/prompt/Prompt.js +84 -0
  70. package/cjs/prompt/Prompt.js.map +1 -0
  71. package/cjs/tooltip/Tooltip.css.js +6 -0
  72. package/cjs/tooltip/Tooltip.css.js.map +1 -0
  73. package/cjs/tooltip/Tooltip.js +56 -0
  74. package/cjs/tooltip/Tooltip.js.map +1 -0
  75. package/cjs/tooltip/useAnchoredPosition.js +71 -0
  76. package/cjs/tooltip/useAnchoredPosition.js.map +1 -0
  77. package/cjs/tooltip/useTooltip.js +106 -0
  78. package/cjs/tooltip/useTooltip.js.map +1 -0
  79. package/esm/dialog/Dialog.css.js +4 -0
  80. package/esm/dialog/Dialog.css.js.map +1 -0
  81. package/esm/dialog/Dialog.js +94 -0
  82. package/esm/dialog/Dialog.js.map +1 -0
  83. package/esm/dialog/useDialog.js +29 -0
  84. package/esm/dialog/useDialog.js.map +1 -0
  85. package/esm/dialog-header/DialogHeader.css.js +4 -0
  86. package/esm/dialog-header/DialogHeader.css.js.map +1 -0
  87. package/esm/dialog-header/DialogHeader.js +37 -0
  88. package/esm/dialog-header/DialogHeader.js.map +1 -0
  89. package/esm/index.js +19 -0
  90. package/esm/index.js.map +1 -0
  91. package/esm/menu/ContextMenu.js +117 -0
  92. package/esm/menu/ContextMenu.js.map +1 -0
  93. package/esm/menu/MenuList.css.js +4 -0
  94. package/esm/menu/MenuList.css.js.map +1 -0
  95. package/esm/menu/MenuList.js +165 -0
  96. package/esm/menu/MenuList.js.map +1 -0
  97. package/esm/menu/context-menu-provider.js +63 -0
  98. package/esm/menu/context-menu-provider.js.map +1 -0
  99. package/esm/menu/key-code.js +50 -0
  100. package/esm/menu/key-code.js.map +1 -0
  101. package/esm/menu/list-dom-utils.js +4 -0
  102. package/esm/menu/list-dom-utils.js.map +1 -0
  103. package/esm/menu/use-cascade.js +274 -0
  104. package/esm/menu/use-cascade.js.map +1 -0
  105. package/esm/menu/use-items-with-ids-next.js +66 -0
  106. package/esm/menu/use-items-with-ids-next.js.map +1 -0
  107. package/esm/menu/use-keyboard-navigation.js +146 -0
  108. package/esm/menu/use-keyboard-navigation.js.map +1 -0
  109. package/esm/menu/useContextMenu.js +141 -0
  110. package/esm/menu/useContextMenu.js.map +1 -0
  111. package/esm/menu/utils.js +5 -0
  112. package/esm/menu/utils.js.map +1 -0
  113. package/esm/notifications/NotificationsCenter.js +38 -0
  114. package/esm/notifications/NotificationsCenter.js.map +1 -0
  115. package/esm/notifications/NotificationsProvider.js +29 -0
  116. package/esm/notifications/NotificationsProvider.js.map +1 -0
  117. package/esm/notifications/ToastNotification.css.js +4 -0
  118. package/esm/notifications/ToastNotification.css.js.map +1 -0
  119. package/esm/notifications/ToastNotification.js +64 -0
  120. package/esm/notifications/ToastNotification.js.map +1 -0
  121. package/esm/popup/Popup.css.js +4 -0
  122. package/esm/popup/Popup.css.js.map +1 -0
  123. package/esm/popup/Popup.js +36 -0
  124. package/esm/popup/Popup.js.map +1 -0
  125. package/esm/popup/getPositionRelativeToAnchor.js +41 -0
  126. package/esm/popup/getPositionRelativeToAnchor.js.map +1 -0
  127. package/esm/popup/popup-service.js +214 -0
  128. package/esm/popup/popup-service.js.map +1 -0
  129. package/esm/popup/useAnchoredPosition.js +56 -0
  130. package/esm/popup/useAnchoredPosition.js.map +1 -0
  131. package/esm/popup-menu/PopupMenu.css.js +4 -0
  132. package/esm/popup-menu/PopupMenu.css.js.map +1 -0
  133. package/esm/popup-menu/PopupMenu.js +93 -0
  134. package/esm/popup-menu/PopupMenu.js.map +1 -0
  135. package/esm/popup-menu/usePopupMenu.js +118 -0
  136. package/esm/popup-menu/usePopupMenu.js.map +1 -0
  137. package/esm/portal/Portal.css.js +4 -0
  138. package/esm/portal/Portal.css.js.map +1 -0
  139. package/esm/portal/Portal.js +71 -0
  140. package/esm/portal/Portal.js.map +1 -0
  141. package/esm/portal-deprecated/render-portal.js +29 -0
  142. package/esm/portal-deprecated/render-portal.js.map +1 -0
  143. package/esm/prompt/Prompt.css.js +4 -0
  144. package/esm/prompt/Prompt.css.js.map +1 -0
  145. package/esm/prompt/Prompt.js +82 -0
  146. package/esm/prompt/Prompt.js.map +1 -0
  147. package/esm/tooltip/Tooltip.css.js +4 -0
  148. package/esm/tooltip/Tooltip.css.js.map +1 -0
  149. package/esm/tooltip/Tooltip.js +54 -0
  150. package/esm/tooltip/Tooltip.js.map +1 -0
  151. package/esm/tooltip/useAnchoredPosition.js +69 -0
  152. package/esm/tooltip/useAnchoredPosition.js.map +1 -0
  153. package/esm/tooltip/useTooltip.js +104 -0
  154. package/esm/tooltip/useTooltip.js.map +1 -0
  155. package/package.json +39 -0
  156. package/types/dialog/Dialog.d.ts +9 -0
  157. package/types/dialog/index.d.ts +2 -0
  158. package/types/dialog/useDialog.d.ts +11 -0
  159. package/types/dialog-header/DialogHeader.d.ts +6 -0
  160. package/types/dialog-header/index.d.ts +1 -0
  161. package/types/index.d.ts +10 -0
  162. package/types/menu/ContextMenu.d.ts +17 -0
  163. package/types/menu/MenuList.d.ts +45 -0
  164. package/types/menu/aim/aim.d.ts +13 -0
  165. package/types/menu/aim/corners.d.ts +9 -0
  166. package/types/menu/aim/utils.d.ts +4 -0
  167. package/types/menu/context-menu-provider.d.ts +12 -0
  168. package/types/menu/index.d.ts +4 -0
  169. package/types/menu/key-code.d.ts +12 -0
  170. package/types/menu/list-dom-utils.d.ts +4 -0
  171. package/types/menu/use-cascade.d.ts +26 -0
  172. package/types/menu/use-items-with-ids-next.d.ts +12 -0
  173. package/types/menu/use-keyboard-navigation.d.ts +30 -0
  174. package/types/menu/useContextMenu.d.ts +19 -0
  175. package/types/menu/utils.d.ts +2 -0
  176. package/types/notifications/NotificationsCenter.d.ts +6 -0
  177. package/types/notifications/NotificationsProvider.d.ts +11 -0
  178. package/types/notifications/ToastNotification.d.ts +8 -0
  179. package/types/notifications/index.d.ts +3 -0
  180. package/types/notifications/notificationTypes.d.ts +7 -0
  181. package/types/popup/Popup.d.ts +12 -0
  182. package/types/popup/getPositionRelativeToAnchor.d.ts +19 -0
  183. package/types/popup/index.d.ts +3 -0
  184. package/types/popup/popup-service.d.ts +53 -0
  185. package/types/popup/useAnchoredPosition.d.ts +7 -0
  186. package/types/popup-menu/PopupMenu.d.ts +24 -0
  187. package/types/popup-menu/index.d.ts +1 -0
  188. package/types/popup-menu/usePopupMenu.d.ts +19 -0
  189. package/types/portal/Portal.d.ts +42 -0
  190. package/types/portal/index.d.ts +1 -0
  191. package/types/portal-deprecated/index.d.ts +1 -0
  192. package/types/portal-deprecated/render-portal.d.ts +10 -0
  193. package/types/prompt/Prompt.d.ts +13 -0
  194. package/types/prompt/index.d.ts +1 -0
  195. package/types/tooltip/Tooltip.d.ts +15 -0
  196. package/types/tooltip/index.d.ts +2 -0
  197. package/types/tooltip/useAnchoredPosition.d.ts +9 -0
  198. package/types/tooltip/useTooltip.d.ts +19 -0
@@ -0,0 +1,172 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var cx = require('clsx');
5
+ var styles = require('@salt-ds/styles');
6
+ var window = require('@salt-ds/window');
7
+ var React = require('react');
8
+ var vuuUtils = require('@vuu-ui/vuu-utils');
9
+ var useKeyboardNavigation = require('./use-keyboard-navigation.js');
10
+ var MenuList$1 = require('./MenuList.css.js');
11
+
12
+ const classBase = "vuuMenuList";
13
+ const Separator = () => /* @__PURE__ */ jsxRuntime.jsx("li", { className: "vuuMenuItem-divider" });
14
+ const isMenuItemGroup = (child) => child.type === MenuItemGroup || !!child.props["data-group"];
15
+ const MenuItemGroup = () => null;
16
+ const MenuItem = ({
17
+ children,
18
+ idx,
19
+ options,
20
+ ...props
21
+ }) => {
22
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ...props, children });
23
+ };
24
+ const MenuItemLabel = ({ children }) => /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
25
+ MenuItemLabel.displayName = "MenuItemLabel";
26
+ MenuItem.Label = MenuItemLabel;
27
+ const getDisplayName = (item) => React.isValidElement(item) && typeof item.type !== "string" && "displayName" in item.type ? item.type.displayName : void 0;
28
+ const isMenuItemLabel = (item) => getDisplayName(item) === "MenuItemLabel";
29
+ const hasIcon = (child) => child.props["data-icon"];
30
+ const MenuList = ({
31
+ activatedByKeyboard,
32
+ childMenuShowing,
33
+ children,
34
+ className,
35
+ defaultHighlightedIdx,
36
+ highlightedIdx: highlightedIdxProp,
37
+ id: idProp,
38
+ isRoot,
39
+ listItemProps,
40
+ onHighlightMenuItem,
41
+ onActivate,
42
+ onCloseMenu,
43
+ openMenu: onOpenMenu,
44
+ ...props
45
+ }) => {
46
+ const targetWindow = window.useWindow();
47
+ styles.useComponentCssInjection({
48
+ testId: "vuu-menu-list",
49
+ css: MenuList$1,
50
+ window: targetWindow
51
+ });
52
+ const id = vuuUtils.useId(idProp);
53
+ const root = React.useRef(null);
54
+ const mapIdxToId = React.useMemo(() => /* @__PURE__ */ new Map(), []);
55
+ const handleActivate = (idx) => {
56
+ const el = root.current?.querySelector(`:scope > [data-index='${idx}']`);
57
+ el?.id && onActivate?.(el.id);
58
+ };
59
+ const { focusVisible, highlightedIndex, listProps } = useKeyboardNavigation.useKeyboardNavigation({
60
+ count: React.Children.count(children),
61
+ defaultHighlightedIdx,
62
+ highlightedIndex: highlightedIdxProp,
63
+ onActivate: handleActivate,
64
+ onHighlight: onHighlightMenuItem,
65
+ onOpenMenu,
66
+ onCloseMenu
67
+ });
68
+ const appliedFocusVisible = childMenuShowing == void 0 ? focusVisible : -1;
69
+ React.useLayoutEffect(() => {
70
+ if (childMenuShowing === void 0 && activatedByKeyboard) {
71
+ root.current?.focus();
72
+ }
73
+ }, [activatedByKeyboard, childMenuShowing]);
74
+ const getActiveDescendant = () => highlightedIndex === void 0 || highlightedIndex === -1 ? void 0 : mapIdxToId.get(highlightedIndex);
75
+ function renderContent() {
76
+ const propsCommonToAllListItems = {
77
+ ...listItemProps,
78
+ role: "menuitem"
79
+ };
80
+ const maybeIcon = (childElement, withIcon, iconName) => withIcon ? [
81
+ /* @__PURE__ */ jsxRuntime.jsx(
82
+ "span",
83
+ {
84
+ className: "vuuIconContainer",
85
+ "data-icon": iconName
86
+ },
87
+ "icon"
88
+ )
89
+ ].concat(childElement) : childElement;
90
+ function addClonedChild(list, child, idx, withIcon) {
91
+ const {
92
+ children: children2,
93
+ className: className2,
94
+ "data-icon": iconName,
95
+ id: itemId,
96
+ hasSeparator,
97
+ label,
98
+ ...props2
99
+ } = child.props;
100
+ const hasSubMenu = isMenuItemGroup(child);
101
+ const subMenuShowing = hasSubMenu && childMenuShowing === itemId;
102
+ const ariaControls = subMenuShowing ? `${id}-${itemId}` : void 0;
103
+ const ariaLabel = label ?? typeof children2 === "string" ? children2 : void 0;
104
+ list.push(
105
+ /* @__PURE__ */ jsxRuntime.jsx(
106
+ MenuItem,
107
+ {
108
+ ...props2,
109
+ ...propsCommonToAllListItems,
110
+ ...getMenuItemProps(
111
+ itemId,
112
+ idx,
113
+ child.key ?? itemId,
114
+ highlightedIndex,
115
+ appliedFocusVisible,
116
+ className2,
117
+ hasSeparator
118
+ ),
119
+ "aria-controls": ariaControls,
120
+ "aria-haspopup": hasSubMenu || void 0,
121
+ "aria-expanded": subMenuShowing || void 0,
122
+ "aria-label": ariaLabel,
123
+ children: hasSubMenu ? maybeIcon(label ?? children2, withIcon, iconName) : maybeIcon(children2, withIcon, iconName)
124
+ }
125
+ )
126
+ );
127
+ }
128
+ const listItems = [];
129
+ if (children.length > 0) {
130
+ const withIcon = children.some(hasIcon);
131
+ children.forEach((child, idx) => {
132
+ addClonedChild(listItems, child, idx, withIcon);
133
+ });
134
+ }
135
+ return listItems;
136
+ }
137
+ return /* @__PURE__ */ jsxRuntime.jsx(
138
+ "div",
139
+ {
140
+ ...props,
141
+ ...listProps,
142
+ "aria-activedescendant": getActiveDescendant(),
143
+ className: cx(classBase, className, {
144
+ [`${classBase}-childMenuShowing`]: childMenuShowing !== void 0
145
+ }),
146
+ "data-root": isRoot || void 0,
147
+ id,
148
+ ref: root,
149
+ role: "menu",
150
+ children: renderContent()
151
+ }
152
+ );
153
+ };
154
+ const getMenuItemProps = (itemId, idx, key, highlightedIdx, focusVisible, className, hasSeparator) => ({
155
+ id: `menuitem-${itemId}`,
156
+ key: key ?? idx,
157
+ "data-index": idx,
158
+ className: cx("vuuMenuItem", className, {
159
+ "vuuMenuItem-separator": hasSeparator,
160
+ vuuHighlighted: idx === highlightedIdx,
161
+ focusVisible: focusVisible === idx
162
+ })
163
+ });
164
+ MenuList.displayName = "MenuList";
165
+
166
+ exports.MenuItem = MenuItem;
167
+ exports.MenuItemGroup = MenuItemGroup;
168
+ exports.MenuList = MenuList;
169
+ exports.Separator = Separator;
170
+ exports.isMenuItemGroup = isMenuItemGroup;
171
+ exports.isMenuItemLabel = isMenuItemLabel;
172
+ //# sourceMappingURL=MenuList.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MenuList.js","sources":["../../src/menu/MenuList.tsx"],"sourcesContent":["import cx from \"clsx\";\nimport { useComponentCssInjection } from \"@salt-ds/styles\";\nimport { useWindow } from \"@salt-ds/window\";\nimport React, {\n FC,\n HTMLAttributes,\n ReactElement,\n ReactNode,\n useLayoutEffect,\n useMemo,\n useRef,\n} from \"react\";\n//TODO do we want this dependency ?\nimport { useId } from \"@vuu-ui/vuu-utils\";\nimport {\n MenuCloseHandler,\n useKeyboardNavigation,\n} from \"./use-keyboard-navigation\";\n\nimport menuListCss from \"./MenuList.css\";\n\nconst classBase = \"vuuMenuList\";\n\nexport const Separator = () => <li className=\"vuuMenuItem-divider\" />;\n\nexport const isMenuItemGroup = (child: ReactElement) =>\n child.type === MenuItemGroup || !!child.props[\"data-group\"];\n\nexport interface MenuItemGroupProps {\n children:\n | ReactElement<MenuItemProps>[]\n | [ReactElement<MenuItemLabelProps>, ...ReactElement<MenuItemProps>[]];\n label?: string;\n}\n\nexport interface MenuItemProps extends HTMLAttributes<HTMLDivElement> {\n action?: string;\n idx?: number;\n options?: unknown;\n}\n\n// Purely used as markers, props will be extracted\nexport const MenuItemGroup: FC<MenuItemGroupProps> = () => null;\n// eslint-disable-next-line no-unused-vars\nexport const MenuItem = ({\n children,\n idx,\n options,\n ...props\n}: MenuItemProps) => {\n return <div {...props}>{children}</div>;\n};\n\nexport interface MenuItemLabelProps {\n children: ReactNode;\n}\nconst MenuItemLabel = ({ children }: { children: ReactNode }) => (\n <>{children}</>\n);\nMenuItemLabel.displayName = \"MenuItemLabel\";\nMenuItem.Label = MenuItemLabel;\n\nconst getDisplayName = (item: ReactNode) =>\n React.isValidElement(item) &&\n typeof item.type !== \"string\" &&\n \"displayName\" in item.type\n ? item.type.displayName\n : undefined;\n\nexport const isMenuItemLabel = (\n item: ReactNode\n): item is ReactElement<MenuItemLabelProps> =>\n getDisplayName(item) === \"MenuItemLabel\";\n\nconst hasIcon = (child: ReactElement) => child.props[\"data-icon\"];\n\nexport type MenuOpenHandler = (\n menuItemEl: HTMLElement,\n immediate?: boolean\n) => void;\nexport interface MenuListProps extends HTMLAttributes<HTMLDivElement> {\n activatedByKeyboard?: boolean;\n children: ReactElement[];\n childMenuShowing?: string;\n defaultHighlightedIdx?: number;\n highlightedIdx?: number;\n isRoot?: boolean;\n listItemProps?: Partial<MenuItemProps>;\n onActivate?: (menuId: string) => void;\n onCloseMenu: MenuCloseHandler;\n openMenu?: MenuOpenHandler;\n onHighlightMenuItem?: (idx: number) => void;\n}\n\nexport const MenuList = ({\n activatedByKeyboard,\n childMenuShowing,\n children,\n className,\n defaultHighlightedIdx,\n highlightedIdx: highlightedIdxProp,\n id: idProp,\n isRoot,\n listItemProps,\n onHighlightMenuItem,\n onActivate,\n onCloseMenu,\n openMenu: onOpenMenu,\n ...props\n}: MenuListProps) => {\n const targetWindow = useWindow();\n useComponentCssInjection({\n testId: \"vuu-menu-list\",\n css: menuListCss,\n window: targetWindow,\n });\n\n const id = useId(idProp);\n const root = useRef<HTMLDivElement>(null);\n\n // The id generation be,ongs in useIttemsWithIds\n const mapIdxToId = useMemo(() => new Map(), []);\n\n const handleActivate = (idx: number) => {\n const el = root.current?.querySelector(`:scope > [data-index='${idx}']`);\n el?.id && onActivate?.(el.id);\n };\n\n const { focusVisible, highlightedIndex, listProps } = useKeyboardNavigation({\n count: React.Children.count(children),\n defaultHighlightedIdx,\n highlightedIndex: highlightedIdxProp,\n onActivate: handleActivate,\n onHighlight: onHighlightMenuItem,\n onOpenMenu,\n onCloseMenu,\n });\n\n const appliedFocusVisible = childMenuShowing == undefined ? focusVisible : -1;\n\n useLayoutEffect(() => {\n if (childMenuShowing === undefined && activatedByKeyboard) {\n root.current?.focus();\n }\n }, [activatedByKeyboard, childMenuShowing]);\n\n const getActiveDescendant = () =>\n highlightedIndex === undefined || highlightedIndex === -1\n ? undefined\n : mapIdxToId.get(highlightedIndex);\n\n function renderContent() {\n const propsCommonToAllListItems = {\n ...listItemProps,\n role: \"menuitem\",\n };\n\n const maybeIcon = (\n childElement: ReactElement,\n withIcon: boolean,\n iconName?: string\n ) =>\n withIcon\n ? [\n <span\n className=\"vuuIconContainer\"\n data-icon={iconName}\n key=\"icon\"\n />,\n ].concat(childElement)\n : childElement;\n\n function addClonedChild(\n list: ReactElement[],\n child: ReactElement,\n idx: number,\n withIcon: boolean\n ) {\n const {\n children,\n className,\n \"data-icon\": iconName,\n id: itemId,\n hasSeparator,\n label,\n ...props\n } = child.props;\n const hasSubMenu = isMenuItemGroup(child);\n const subMenuShowing = hasSubMenu && childMenuShowing === itemId;\n const ariaControls = subMenuShowing ? `${id}-${itemId}` : undefined;\n\n const ariaLabel =\n label ?? typeof children === \"string\" ? children : undefined;\n\n list.push(\n <MenuItem\n {...props}\n {...propsCommonToAllListItems}\n {...getMenuItemProps(\n itemId,\n idx,\n child.key ?? itemId,\n highlightedIndex,\n appliedFocusVisible,\n className,\n hasSeparator\n )}\n aria-controls={ariaControls}\n aria-haspopup={hasSubMenu || undefined}\n aria-expanded={subMenuShowing || undefined}\n aria-label={ariaLabel}\n >\n {hasSubMenu\n ? maybeIcon(label ?? children, withIcon, iconName)\n : maybeIcon(children, withIcon, iconName)}\n </MenuItem>\n );\n // mapIdxToId.set(idx, itemId);\n }\n\n const listItems: ReactElement[] = [];\n\n if (children.length > 0) {\n const withIcon = children.some(hasIcon);\n children.forEach((child, idx) => {\n addClonedChild(listItems, child, idx, withIcon);\n });\n }\n\n return listItems;\n }\n\n return (\n <div\n {...props}\n {...listProps}\n aria-activedescendant={getActiveDescendant()}\n className={cx(classBase, className, {\n [`${classBase}-childMenuShowing`]: childMenuShowing !== undefined,\n })}\n data-root={isRoot || undefined}\n id={id}\n ref={root}\n role=\"menu\"\n >\n {renderContent()}\n </div>\n );\n};\n\nconst getMenuItemProps = (\n itemId: string,\n idx: number,\n key: string,\n highlightedIdx: number,\n focusVisible: number,\n className: string,\n hasSeparator: boolean\n) => ({\n id: `menuitem-${itemId}`,\n key: key ?? idx,\n \"data-index\": idx,\n className: cx(\"vuuMenuItem\", className, {\n \"vuuMenuItem-separator\": hasSeparator,\n vuuHighlighted: idx === highlightedIdx,\n focusVisible: focusVisible === idx,\n }),\n});\n\nMenuList.displayName = \"MenuList\";\n"],"names":["jsx","useWindow","useComponentCssInjection","menuListCss","useId","useRef","useMemo","useKeyboardNavigation","useLayoutEffect","children","className","props"],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var React = require('react');
5
+
6
+ const ContextMenuContext = React.createContext(
7
+ null
8
+ );
9
+ const Provider = ({
10
+ children,
11
+ context,
12
+ menuActionHandler,
13
+ menuBuilder
14
+ }) => {
15
+ const menuBuilders = React.useMemo(() => {
16
+ if (context?.menuBuilders && menuBuilder) {
17
+ return context.menuBuilders.concat(menuBuilder);
18
+ } else if (menuBuilder) {
19
+ return [menuBuilder];
20
+ } else {
21
+ return context?.menuBuilders || [];
22
+ }
23
+ }, [context, menuBuilder]);
24
+ const handleMenuAction = React.useCallback(
25
+ (reason) => {
26
+ if (menuActionHandler?.(reason)) {
27
+ return true;
28
+ }
29
+ if (context?.menuActionHandler?.(reason)) {
30
+ return true;
31
+ }
32
+ },
33
+ [context, menuActionHandler]
34
+ );
35
+ return /* @__PURE__ */ jsxRuntime.jsx(
36
+ ContextMenuContext.Provider,
37
+ {
38
+ value: {
39
+ menuActionHandler: handleMenuAction,
40
+ menuBuilders
41
+ },
42
+ children
43
+ }
44
+ );
45
+ };
46
+ const ContextMenuProvider = ({
47
+ children,
48
+ label,
49
+ menuActionHandler,
50
+ menuBuilder
51
+ }) => {
52
+ return /* @__PURE__ */ jsxRuntime.jsx(ContextMenuContext.Consumer, { children: (parentContext) => /* @__PURE__ */ jsxRuntime.jsx(
53
+ Provider,
54
+ {
55
+ context: parentContext,
56
+ label,
57
+ menuActionHandler,
58
+ menuBuilder,
59
+ children
60
+ }
61
+ ) });
62
+ };
63
+
64
+ exports.ContextMenuContext = ContextMenuContext;
65
+ exports.ContextMenuProvider = ContextMenuProvider;
66
+ //# sourceMappingURL=context-menu-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-menu-provider.js","sources":["../../src/menu/context-menu-provider.tsx"],"sourcesContent":["import type {\n ContextMenuContextType,\n MenuActionHandler,\n MenuBuilder,\n} from \"@vuu-ui/vuu-data-types\";\nimport { createContext, ReactNode, useCallback, useMemo } from \"react\";\n\nexport const ContextMenuContext = createContext<ContextMenuContextType | null>(\n null\n);\n\nexport interface ContextMenuConfiguration {\n menuActionHandler?: MenuActionHandler;\n menuBuilder: MenuBuilder;\n}\n\nexport interface ContextMenuProviderProps extends ContextMenuConfiguration {\n children: ReactNode;\n label?: string;\n}\n\ninterface ProviderProps extends ContextMenuProviderProps {\n context: ContextMenuContextType | null;\n}\n\nconst Provider = ({\n children,\n context,\n menuActionHandler,\n menuBuilder,\n}: ProviderProps) => {\n const menuBuilders = useMemo(() => {\n if (context?.menuBuilders && menuBuilder) {\n return context.menuBuilders.concat(menuBuilder);\n } else if (menuBuilder) {\n return [menuBuilder];\n } else {\n return context?.menuBuilders || [];\n }\n }, [context, menuBuilder]);\n\n const handleMenuAction = useCallback(\n (reason) => {\n if (menuActionHandler?.(reason)) {\n return true;\n }\n\n if (context?.menuActionHandler?.(reason)) {\n return true;\n }\n },\n [context, menuActionHandler]\n );\n\n return (\n <ContextMenuContext.Provider\n value={{\n menuActionHandler: handleMenuAction,\n menuBuilders,\n }}\n >\n {children}\n </ContextMenuContext.Provider>\n );\n};\n\n// Need an option for local menu to override higher-level menu, rather than extend\nexport const ContextMenuProvider = ({\n children,\n label,\n menuActionHandler,\n menuBuilder,\n}: ContextMenuProviderProps) => {\n return (\n <ContextMenuContext.Consumer>\n {(parentContext) => (\n <Provider\n context={parentContext}\n label={label}\n menuActionHandler={menuActionHandler}\n menuBuilder={menuBuilder}\n >\n {children}\n </Provider>\n )}\n </ContextMenuContext.Consumer>\n );\n};\n"],"names":["createContext","useMemo","useCallback","jsx"],"mappings":";;;;;AAOO,MAAM,kBAAqB,GAAAA,mBAAA;AAAA,EAChC,IAAA;AACF,EAAA;AAgBA,MAAM,WAAW,CAAC;AAAA,EAChB,QAAA;AAAA,EACA,OAAA;AAAA,EACA,iBAAA;AAAA,EACA,WAAA;AACF,CAAqB,KAAA;AACnB,EAAM,MAAA,YAAA,GAAeC,cAAQ,MAAM;AACjC,IAAI,IAAA,OAAA,EAAS,gBAAgB,WAAa,EAAA;AACxC,MAAO,OAAA,OAAA,CAAQ,YAAa,CAAA,MAAA,CAAO,WAAW,CAAA,CAAA;AAAA,eACrC,WAAa,EAAA;AACtB,MAAA,OAAO,CAAC,WAAW,CAAA,CAAA;AAAA,KACd,MAAA;AACL,MAAO,OAAA,OAAA,EAAS,gBAAgB,EAAC,CAAA;AAAA,KACnC;AAAA,GACC,EAAA,CAAC,OAAS,EAAA,WAAW,CAAC,CAAA,CAAA;AAEzB,EAAA,MAAM,gBAAmB,GAAAC,iBAAA;AAAA,IACvB,CAAC,MAAW,KAAA;AACV,MAAI,IAAA,iBAAA,GAAoB,MAAM,CAAG,EAAA;AAC/B,QAAO,OAAA,IAAA,CAAA;AAAA,OACT;AAEA,MAAI,IAAA,OAAA,EAAS,iBAAoB,GAAA,MAAM,CAAG,EAAA;AACxC,QAAO,OAAA,IAAA,CAAA;AAAA,OACT;AAAA,KACF;AAAA,IACA,CAAC,SAAS,iBAAiB,CAAA;AAAA,GAC7B,CAAA;AAEA,EACE,uBAAAC,cAAA;AAAA,IAAC,kBAAmB,CAAA,QAAA;AAAA,IAAnB;AAAA,MACC,KAAO,EAAA;AAAA,QACL,iBAAmB,EAAA,gBAAA;AAAA,QACnB,YAAA;AAAA,OACF;AAAA,MAEC,QAAA;AAAA,KAAA;AAAA,GACH,CAAA;AAEJ,CAAA,CAAA;AAGO,MAAM,sBAAsB,CAAC;AAAA,EAClC,QAAA;AAAA,EACA,KAAA;AAAA,EACA,iBAAA;AAAA,EACA,WAAA;AACF,CAAgC,KAAA;AAC9B,EAAA,uBACGA,cAAA,CAAA,kBAAA,CAAmB,QAAnB,EAAA,EACE,WAAC,aACA,qBAAAA,cAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACC,OAAS,EAAA,aAAA;AAAA,MACT,KAAA;AAAA,MACA,iBAAA;AAAA,MACA,WAAA;AAAA,MAEC,QAAA;AAAA,KAAA;AAAA,GAGP,EAAA,CAAA,CAAA;AAEJ;;;;;"}
@@ -0,0 +1,54 @@
1
+ 'use strict';
2
+
3
+ function union(set1, ...sets) {
4
+ const result = new Set(set1);
5
+ for (const set of sets) {
6
+ for (const element of set) {
7
+ result.add(element);
8
+ }
9
+ }
10
+ return result;
11
+ }
12
+ const Enter = "Enter";
13
+ const Delete = "Delete";
14
+ const actionKeys = /* @__PURE__ */ new Set([Enter, Delete]);
15
+ const focusKeys = /* @__PURE__ */ new Set(["Tab"]);
16
+ const arrowLeftRightKeys = /* @__PURE__ */ new Set(["ArrowRight", "ArrowLeft"]);
17
+ const verticalNavigationKeys = /* @__PURE__ */ new Set(["Home", "End", "ArrowDown", "ArrowUp"]);
18
+ const horizontalNavigationKeys = /* @__PURE__ */ new Set([
19
+ "Home",
20
+ "End",
21
+ "ArrowRight",
22
+ "ArrowLeft"
23
+ ]);
24
+ const functionKeys = /* @__PURE__ */ new Set([
25
+ "F1",
26
+ "F2",
27
+ "F3",
28
+ "F4",
29
+ "F5",
30
+ "F6",
31
+ "F7",
32
+ "F8",
33
+ "F9",
34
+ "F10",
35
+ "F11",
36
+ "F12"
37
+ ]);
38
+ union(
39
+ actionKeys,
40
+ horizontalNavigationKeys,
41
+ verticalNavigationKeys,
42
+ arrowLeftRightKeys,
43
+ functionKeys,
44
+ focusKeys
45
+ );
46
+ const isNavigationKey = ({ key }, orientation = "vertical") => {
47
+ const navigationKeys = orientation === "vertical" ? verticalNavigationKeys : horizontalNavigationKeys;
48
+ return navigationKeys.has(key);
49
+ };
50
+
51
+ exports.Delete = Delete;
52
+ exports.Enter = Enter;
53
+ exports.isNavigationKey = isNavigationKey;
54
+ //# sourceMappingURL=key-code.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key-code.js","sources":["../../src/menu/key-code.ts"],"sourcesContent":["function union(set1: Set<string>, ...sets: Set<string>[]) {\n const result = new Set(set1);\n for (const set of sets) {\n for (const element of set) {\n result.add(element);\n }\n }\n return result;\n}\n\nexport const ArrowUp = \"ArrowUp\";\nexport const ArrowDown = \"ArrowDown\";\nexport const ArrowLeft = \"ArrowLeft\";\nexport const Backspace = \"Backspace\";\nexport const ArrowRight = \"ArrowRight\";\nexport const Enter = \"Enter\";\nexport const Escape = \"Escape\";\nexport const Delete = \"Delete\";\n\nconst actionKeys = new Set([Enter, Delete]);\nconst focusKeys = new Set([\"Tab\"]);\n// const navigationKeys = new Set([\"Home\", \"End\", \"ArrowRight\", \"ArrowLeft\",\"ArrowDown\", \"ArrowUp\"]);\nconst arrowLeftRightKeys = new Set([\"ArrowRight\", \"ArrowLeft\"]);\nconst verticalNavigationKeys = new Set([\"Home\", \"End\", \"ArrowDown\", \"ArrowUp\"]);\nconst horizontalNavigationKeys = new Set([\n \"Home\",\n \"End\",\n \"ArrowRight\",\n \"ArrowLeft\",\n]);\nconst functionKeys = new Set([\n \"F1\",\n \"F2\",\n \"F3\",\n \"F4\",\n \"F5\",\n \"F6\",\n \"F7\",\n \"F8\",\n \"F9\",\n \"F10\",\n \"F11\",\n \"F12\",\n]);\nconst specialKeys = union(\n actionKeys,\n horizontalNavigationKeys,\n verticalNavigationKeys,\n arrowLeftRightKeys,\n functionKeys,\n focusKeys\n);\nexport const isCharacterKey = (evt: KeyboardEvent) => {\n if (specialKeys.has(evt.key)) {\n return false;\n }\n if (typeof evt.which === \"number\" && evt.which > 0) {\n return !evt.ctrlKey && !evt.metaKey && !evt.altKey && evt.which !== 8;\n }\n};\n\nexport const isNavigationKey = (\n { key }: { key: string },\n orientation = \"vertical\"\n) => {\n const navigationKeys =\n orientation === \"vertical\"\n ? verticalNavigationKeys\n : horizontalNavigationKeys;\n return navigationKeys.has(key);\n};\n"],"names":[],"mappings":";;AAAA,SAAS,KAAA,CAAM,SAAsB,IAAqB,EAAA;AACxD,EAAM,MAAA,MAAA,GAAS,IAAI,GAAA,CAAI,IAAI,CAAA,CAAA;AAC3B,EAAA,KAAA,MAAW,OAAO,IAAM,EAAA;AACtB,IAAA,KAAA,MAAW,WAAW,GAAK,EAAA;AACzB,MAAA,MAAA,CAAO,IAAI,OAAO,CAAA,CAAA;AAAA,KACpB;AAAA,GACF;AACA,EAAO,OAAA,MAAA,CAAA;AACT,CAAA;AAOO,MAAM,KAAQ,GAAA,QAAA;AAEd,MAAM,MAAS,GAAA,SAAA;AAEtB,MAAM,6BAAiB,IAAA,GAAA,CAAI,CAAC,KAAA,EAAO,MAAM,CAAC,CAAA,CAAA;AAC1C,MAAM,SAAY,mBAAA,IAAI,GAAI,CAAA,CAAC,KAAK,CAAC,CAAA,CAAA;AAEjC,MAAM,qCAAyB,IAAA,GAAA,CAAI,CAAC,YAAA,EAAc,WAAW,CAAC,CAAA,CAAA;AAC9D,MAAM,sBAAA,uBAA6B,GAAI,CAAA,CAAC,QAAQ,KAAO,EAAA,WAAA,EAAa,SAAS,CAAC,CAAA,CAAA;AAC9E,MAAM,wBAAA,uBAA+B,GAAI,CAAA;AAAA,EACvC,MAAA;AAAA,EACA,KAAA;AAAA,EACA,YAAA;AAAA,EACA,WAAA;AACF,CAAC,CAAA,CAAA;AACD,MAAM,YAAA,uBAAmB,GAAI,CAAA;AAAA,EAC3B,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,IAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AACF,CAAC,CAAA,CAAA;AACmB,KAAA;AAAA,EAClB,UAAA;AAAA,EACA,wBAAA;AAAA,EACA,sBAAA;AAAA,EACA,kBAAA;AAAA,EACA,YAAA;AAAA,EACA,SAAA;AACF,EAAA;AAUO,MAAM,kBAAkB,CAC7B,EAAE,GAAI,EAAA,EACN,cAAc,UACX,KAAA;AACH,EAAM,MAAA,cAAA,GACJ,WAAgB,KAAA,UAAA,GACZ,sBACA,GAAA,wBAAA,CAAA;AACN,EAAO,OAAA,cAAA,CAAe,IAAI,GAAG,CAAA,CAAA;AAC/B;;;;;;"}
@@ -0,0 +1,6 @@
1
+ 'use strict';
2
+
3
+ const closestListItem = (el) => el?.closest("[data-index],[aria-posinset]");
4
+
5
+ exports.closestListItem = closestListItem;
6
+ //# sourceMappingURL=list-dom-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-dom-utils.js","sources":["../../src/menu/list-dom-utils.ts"],"sourcesContent":["export function listItemIndex(listItemEl: HTMLElement) {\n if (listItemEl) {\n const idx = listItemEl.dataset.index;\n if (idx) {\n return parseInt(idx, 10);\n // eslint-disable-next-line no-cond-assign\n } else if (listItemEl.ariaPosInSet) {\n return parseInt(listItemEl.ariaPosInSet, 10) - 1;\n }\n }\n}\n\nconst listItemId = (el: HTMLElement | null | undefined) => el?.id;\n\nexport const closestListItem = (el: HTMLElement | null | undefined) =>\n el?.closest(\"[data-index],[aria-posinset]\") as HTMLElement;\n\nexport const closestListItemId = (el: HTMLElement) =>\n listItemId(closestListItem(el));\n\nexport const closestListItemIndex = (el: HTMLElement) =>\n listItemIndex(closestListItem(el));\n"],"names":[],"mappings":";;AAcO,MAAM,eAAkB,GAAA,CAAC,EAC9B,KAAA,EAAA,EAAI,QAAQ,8BAA8B;;;;"}
@@ -0,0 +1,277 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+ var listDomUtils = require('./list-dom-utils.js');
5
+
6
+ const nudge = (menus, distance, pos) => {
7
+ return menus.map(
8
+ (m, i) => i === menus.length - 1 ? {
9
+ ...m,
10
+ [pos]: m[pos] - distance
11
+ } : m
12
+ );
13
+ };
14
+ const nudgeLeft = (menus, distance) => nudge(menus, distance, "left");
15
+ const nudgeUp = (menus, distance) => nudge(menus, distance, "top");
16
+ const flipSides = (id, menus) => {
17
+ const [parentMenu, menu] = menus.slice(-2);
18
+ const el = document.getElementById(`${id}-${menu.id}`);
19
+ if (el === null) {
20
+ throw Error(`useCascade.flipSides element with id ${menu.id} not found`);
21
+ }
22
+ const { width } = el.getBoundingClientRect();
23
+ return menus.map(
24
+ (m) => m === menu ? {
25
+ ...m,
26
+ left: parentMenu.left - (width - 2)
27
+ } : m
28
+ );
29
+ };
30
+ const getPosition = (el, openMenus) => {
31
+ const [{ left, top: menuTop }] = openMenus.slice(-1);
32
+ const { offsetWidth: width, offsetTop: top } = el;
33
+ return { left: left + width, top: top + menuTop };
34
+ };
35
+ const getHostMenuId = (id, rootId) => {
36
+ const pos = id.lastIndexOf("-");
37
+ if (id.startsWith("menuitem")) {
38
+ return pos > -1 ? id.slice(9, pos) : rootId;
39
+ } else {
40
+ return pos > -1 ? id.slice(0, pos) : rootId;
41
+ }
42
+ };
43
+ const getTargetMenuId = (id) => id.slice(9);
44
+ const getMenuItemDetails = ({ ariaExpanded, ariaHasPopup, id }, rootId) => {
45
+ if (id.startsWith("menuitem")) {
46
+ return {
47
+ hostMenuId: getHostMenuId(id, rootId),
48
+ targetMenuId: getTargetMenuId(id),
49
+ menuItemId: id,
50
+ isGroup: ariaHasPopup === "true",
51
+ isOpen: ariaExpanded === "true"
52
+ };
53
+ } else {
54
+ throw Error(`getMenuItemDetails #${id} is not a menuitem`);
55
+ }
56
+ };
57
+ const useCascade = ({
58
+ id: rootId,
59
+ onActivate,
60
+ onMouseEnterItem,
61
+ position: { x: posX, y: posY }
62
+ }) => {
63
+ const [, forceRefresh] = React.useState({});
64
+ const openMenus = React.useRef([
65
+ { id: rootId, left: posX, top: posY }
66
+ ]);
67
+ const menuIsOpen = React.useCallback(
68
+ (menuId) => openMenus.current.findIndex((menu) => menu.id === menuId) !== -1,
69
+ []
70
+ );
71
+ const getOpenMenuStatus = React.useCallback((menuId) => {
72
+ const state = menuState.current[menuId];
73
+ if (state === void 0) {
74
+ throw Error(`getOpenMenuState no entry for menu ${menuId}`);
75
+ }
76
+ return state;
77
+ }, []);
78
+ const setOpenMenus = React.useCallback((menus) => {
79
+ openMenus.current = menus;
80
+ forceRefresh({});
81
+ }, []);
82
+ const menuOpenPendingTimeout = React.useRef();
83
+ const menuClosePendingTimeout = React.useRef();
84
+ const menuState = React.useRef({ [rootId]: "no-popup" });
85
+ const openMenu = React.useCallback(
86
+ (hostMenuId = rootId, targetMenuId, itemId = null) => {
87
+ if (hostMenuId === rootId && itemId === null) {
88
+ setOpenMenus([{ id: rootId, left: posX, top: posY }]);
89
+ } else {
90
+ menuState.current[hostMenuId] = "popup-open";
91
+ const el = document.getElementById(itemId);
92
+ if (el !== null) {
93
+ const { left, top } = getPosition(el, openMenus.current);
94
+ setOpenMenus(
95
+ openMenus.current.concat({ id: targetMenuId, left, top })
96
+ );
97
+ } else {
98
+ throw Error(`openMenu no menuItem ${itemId}`);
99
+ }
100
+ }
101
+ },
102
+ [rootId, posX, posY, setOpenMenus]
103
+ );
104
+ const closeMenu = React.useCallback(
105
+ (menuId) => {
106
+ if (menuId === rootId) {
107
+ setOpenMenus([]);
108
+ } else {
109
+ const menus = openMenus.current.slice();
110
+ const lastMenu = menus.pop();
111
+ menuState.current[lastMenu.id] = "no-popup";
112
+ const parentMenu = menus.at(-1);
113
+ if (parentMenu) {
114
+ menuState.current[parentMenu.id] = "no-popup";
115
+ }
116
+ setOpenMenus(menus);
117
+ }
118
+ },
119
+ [rootId, setOpenMenus]
120
+ );
121
+ const closeMenus = React.useCallback(
122
+ (menuItemId) => {
123
+ const menus = openMenus.current.slice();
124
+ const menuItemMenuId = menuItemId.slice(9);
125
+ let { id: lastMenuId } = menus.at(-1);
126
+ while (menus.length > 1 && !menuItemMenuId.startsWith(lastMenuId)) {
127
+ const parentMenuId = getHostMenuId(lastMenuId, rootId);
128
+ menus.pop();
129
+ menuState.current[lastMenuId] = "no-popup";
130
+ menuState.current[parentMenuId] = "no-popup";
131
+ ({ id: lastMenuId } = menus[menus.length - 1]);
132
+ }
133
+ if (menus.length < openMenus.current.length) {
134
+ setOpenMenus(menus);
135
+ }
136
+ },
137
+ [rootId, setOpenMenus]
138
+ );
139
+ const clearAnyScheduledOpenTasks = React.useCallback(() => {
140
+ if (menuOpenPendingTimeout.current) {
141
+ clearTimeout(menuOpenPendingTimeout.current);
142
+ menuOpenPendingTimeout.current = void 0;
143
+ }
144
+ }, []);
145
+ const scheduleOpen = React.useCallback(
146
+ (hostMenuId, targetMenuId, menuItemId, delay = 300) => {
147
+ clearAnyScheduledOpenTasks();
148
+ menuOpenPendingTimeout.current = window.setTimeout(() => {
149
+ closeMenus(menuItemId);
150
+ menuState.current[hostMenuId] = "popup-open";
151
+ menuState.current[targetMenuId] = "no-popup";
152
+ openMenu(hostMenuId, targetMenuId, menuItemId);
153
+ }, delay);
154
+ },
155
+ [clearAnyScheduledOpenTasks, closeMenus, openMenu]
156
+ );
157
+ const scheduleClose = React.useCallback(
158
+ (hostMenuId, openMenuId, itemId) => {
159
+ menuState.current[openMenuId] = "pending-close";
160
+ menuClosePendingTimeout.current = window.setTimeout(() => {
161
+ closeMenus(itemId);
162
+ }, 400);
163
+ },
164
+ [closeMenus]
165
+ );
166
+ const handleRender = React.useCallback(() => {
167
+ const { current: menus } = openMenus;
168
+ const menu = menus.at(-1);
169
+ const el = menu ? document.getElementById(menu.id) : void 0;
170
+ if (el) {
171
+ const { right, bottom } = el.getBoundingClientRect();
172
+ const { clientHeight, clientWidth } = document.body;
173
+ if (right > clientWidth) {
174
+ const newMenus = menus.length > 1 ? flipSides(rootId, menus) : nudgeLeft(menus, right - clientWidth);
175
+ setOpenMenus(newMenus);
176
+ } else if (bottom > clientHeight) {
177
+ const newMenus = nudgeUp(menus, bottom - clientHeight);
178
+ setOpenMenus(newMenus);
179
+ }
180
+ if (typeof el.tabIndex === "number") {
181
+ el.focus();
182
+ }
183
+ }
184
+ }, [rootId, setOpenMenus]);
185
+ const triggerChildMenu = React.useCallback(
186
+ (menuItemEl, immediate = false) => {
187
+ const { hostMenuId, targetMenuId, menuItemId, isGroup, isOpen } = getMenuItemDetails(menuItemEl, rootId);
188
+ const {
189
+ current: { [hostMenuId]: state }
190
+ } = menuState;
191
+ const delay = immediate ? 0 : void 0;
192
+ if (state === "no-popup" && isGroup) {
193
+ menuState.current[hostMenuId] = "popup-pending";
194
+ scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
195
+ } else if (state === "popup-pending" && !isGroup) {
196
+ menuState.current[hostMenuId] = "no-popup";
197
+ clearTimeout(menuOpenPendingTimeout.current);
198
+ menuOpenPendingTimeout.current = void 0;
199
+ } else if (state === "popup-pending" && isGroup) {
200
+ clearTimeout(menuOpenPendingTimeout.current);
201
+ scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
202
+ } else if (state === "popup-open") {
203
+ if (menuIsOpen(targetMenuId)) {
204
+ const menuStatus = getOpenMenuStatus(targetMenuId);
205
+ closeMenus(menuItemId);
206
+ switch (menuStatus) {
207
+ case "pending-close":
208
+ clearTimeout(menuClosePendingTimeout.current);
209
+ menuClosePendingTimeout.current = void 0;
210
+ menuState.current[targetMenuId] = "no-popup";
211
+ clearAnyScheduledOpenTasks();
212
+ break;
213
+ }
214
+ } else {
215
+ const [parentOfLastOpenedMenu, lastOpenedMenu] = openMenus.current.slice(-2);
216
+ if (parentOfLastOpenedMenu.id === hostMenuId && menuState.current[lastOpenedMenu.id] !== "pending-close") {
217
+ scheduleClose(hostMenuId, lastOpenedMenu.id, menuItemId);
218
+ if (isGroup && !isOpen) {
219
+ scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
220
+ }
221
+ } else if (parentOfLastOpenedMenu.id === hostMenuId && isGroup && menuItemId !== lastOpenedMenu.id && menuState.current[lastOpenedMenu.id] === "pending-close") {
222
+ scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
223
+ } else if (isGroup) {
224
+ scheduleOpen(hostMenuId, targetMenuId, menuItemId, delay);
225
+ } else if (!(menuState.current[lastOpenedMenu.id] === "pending-close")) {
226
+ closeMenus(menuItemId);
227
+ }
228
+ }
229
+ }
230
+ if (state === "pending-close") {
231
+ clearAnyScheduledOpenTasks();
232
+ clearTimeout(menuClosePendingTimeout.current);
233
+ menuClosePendingTimeout.current = void 0;
234
+ menuState.current[hostMenuId] = "popup-open";
235
+ }
236
+ },
237
+ [
238
+ clearAnyScheduledOpenTasks,
239
+ closeMenus,
240
+ getOpenMenuStatus,
241
+ menuIsOpen,
242
+ rootId,
243
+ scheduleClose,
244
+ scheduleOpen
245
+ ]
246
+ );
247
+ const listItemProps = React.useMemo(
248
+ () => ({
249
+ onMouseEnter: (evt) => {
250
+ const menuItemEl = listDomUtils.closestListItem(evt.target);
251
+ triggerChildMenu(menuItemEl);
252
+ onMouseEnterItem(evt, menuItemEl.id);
253
+ },
254
+ onClick: (evt) => {
255
+ const listItemEl = listDomUtils.closestListItem(evt.target);
256
+ const { isGroup, menuItemId } = getMenuItemDetails(listItemEl, rootId);
257
+ if (isGroup) {
258
+ triggerChildMenu(listItemEl);
259
+ } else {
260
+ onActivate(menuItemId);
261
+ }
262
+ }
263
+ }),
264
+ [onActivate, onMouseEnterItem, rootId, triggerChildMenu]
265
+ );
266
+ return {
267
+ closeMenu,
268
+ handleRender,
269
+ listItemProps,
270
+ openMenu: triggerChildMenu,
271
+ openMenus: openMenus.current
272
+ };
273
+ };
274
+
275
+ exports.getHostMenuId = getHostMenuId;
276
+ exports.useCascade = useCascade;
277
+ //# sourceMappingURL=use-cascade.js.map