@linzjs/step-ag-grid 1.4.2 → 1.4.5

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 (101) hide show
  1. package/dist/index.js +1610 -3048
  2. package/dist/index.js.map +1 -1
  3. package/dist/src/components/GridLoadableCell.d.ts +2 -0
  4. package/dist/src/components/gridForm/GridFormDropDown.d.ts +1 -1
  5. package/dist/src/components/gridForm/GridFormMultiSelect.d.ts +1 -1
  6. package/dist/src/components/gridPopoverEdit/GridPopoverMenu.d.ts +1 -1
  7. package/dist/src/react-menu3/components/ControlledMenu.d.ts +2 -0
  8. package/dist/src/react-menu3/components/FocusableItem.d.ts +1 -0
  9. package/dist/src/react-menu3/components/Menu.d.ts +2 -0
  10. package/dist/src/react-menu3/components/MenuButton.d.ts +1 -0
  11. package/dist/src/react-menu3/components/MenuDivider.d.ts +2 -0
  12. package/dist/src/react-menu3/components/MenuGroup.d.ts +2 -0
  13. package/dist/src/react-menu3/components/MenuHeader.d.ts +2 -0
  14. package/dist/src/react-menu3/components/MenuItem.d.ts +1 -0
  15. package/dist/src/react-menu3/components/MenuList.d.ts +30 -0
  16. package/dist/src/react-menu3/components/MenuRadioGroup.d.ts +2 -0
  17. package/dist/src/react-menu3/components/SubMenu.d.ts +1 -0
  18. package/dist/src/react-menu3/hooks/index.d.ts +9 -0
  19. package/dist/src/react-menu3/hooks/useBEM.d.ts +9 -0
  20. package/dist/src/react-menu3/hooks/useCombinedRef.d.ts +2 -0
  21. package/dist/src/react-menu3/hooks/useIsomorphicLayoutEffect.d.ts +3 -0
  22. package/dist/src/react-menu3/hooks/useItemEffect.d.ts +2 -0
  23. package/dist/src/react-menu3/hooks/useItemState.d.ts +6 -0
  24. package/dist/src/react-menu3/hooks/useItems.d.ts +5 -0
  25. package/dist/src/react-menu3/hooks/useMenuChange.d.ts +1 -0
  26. package/dist/src/react-menu3/hooks/useMenuState.d.ts +11 -0
  27. package/dist/src/react-menu3/hooks/useMenuStateAndFocus.d.ts +10 -0
  28. package/dist/src/react-menu3/index.d.ts +11 -0
  29. package/dist/src/react-menu3/positionUtils/getPositionHelpers.d.ts +10 -0
  30. package/dist/src/react-menu3/positionUtils/index.d.ts +3 -0
  31. package/dist/src/react-menu3/positionUtils/placeArrowHorizontal.d.ts +7 -0
  32. package/dist/src/react-menu3/positionUtils/placeArrowVertical.d.ts +7 -0
  33. package/dist/src/react-menu3/positionUtils/placeLeftorRight.d.ts +21 -0
  34. package/dist/src/react-menu3/positionUtils/placeToporBottom.d.ts +21 -0
  35. package/dist/src/react-menu3/positionUtils/positionContextMenu.d.ts +8 -0
  36. package/dist/src/react-menu3/positionUtils/positionMenu.d.ts +21 -0
  37. package/dist/src/react-menu3/style-utils/index.d.ts +11 -0
  38. package/dist/src/react-menu3/utils/constants.d.ts +50 -0
  39. package/dist/src/react-menu3/utils/index.d.ts +4 -0
  40. package/dist/src/react-menu3/utils/propTypes.d.ts +55 -0
  41. package/dist/src/react-menu3/utils/utils.d.ts +27 -0
  42. package/dist/src/react-menu3/utils/withHovering.d.ts +1 -0
  43. package/dist/step-ag-grid.esm.js +1521 -2958
  44. package/dist/step-ag-grid.esm.js.map +1 -1
  45. package/package.json +18 -9
  46. package/src/components/GridLoadableCell.scss +4 -0
  47. package/src/components/GridLoadableCell.tsx +5 -1
  48. package/src/components/GridPopoverHook.tsx +4 -50
  49. package/src/components/gridForm/GridFormDropDown.tsx +3 -3
  50. package/src/components/gridForm/GridFormMultiSelect.tsx +4 -4
  51. package/src/components/gridForm/GridFormPopoutMenu.tsx +1 -1
  52. package/src/components/gridPopoverEdit/GridPopoverMenu.scss +6 -2
  53. package/src/components/gridPopoverEdit/GridPopoverMenu.tsx +1 -1
  54. package/src/components/gridRender/GridRenderPopoutMenuCell.tsx +7 -9
  55. package/src/react-menu3/components/ControlledMenu.tsx +260 -0
  56. package/src/react-menu3/components/FocusableItem.tsx +70 -0
  57. package/src/react-menu3/components/Menu.tsx +96 -0
  58. package/src/react-menu3/components/MenuButton.tsx +33 -0
  59. package/src/react-menu3/components/MenuDivider.tsx +21 -0
  60. package/src/react-menu3/components/MenuGroup.tsx +38 -0
  61. package/src/react-menu3/components/MenuHeader.tsx +21 -0
  62. package/src/react-menu3/components/MenuItem.tsx +130 -0
  63. package/src/react-menu3/components/MenuList.tsx +434 -0
  64. package/src/react-menu3/components/MenuRadioGroup.tsx +33 -0
  65. package/src/react-menu3/components/SubMenu.tsx +243 -0
  66. package/src/react-menu3/hooks/index.ts +9 -0
  67. package/src/react-menu3/hooks/useBEM.ts +32 -0
  68. package/src/react-menu3/hooks/useCombinedRef.ts +30 -0
  69. package/src/react-menu3/hooks/useIsomorphicLayoutEffect.ts +15 -0
  70. package/src/react-menu3/hooks/useItemEffect.ts +22 -0
  71. package/src/react-menu3/hooks/useItemState.ts +63 -0
  72. package/src/react-menu3/hooks/useItems.ts +110 -0
  73. package/src/react-menu3/hooks/useMenuChange.ts +12 -0
  74. package/src/react-menu3/hooks/useMenuState.ts +24 -0
  75. package/src/react-menu3/hooks/useMenuStateAndFocus.ts +15 -0
  76. package/src/react-menu3/index.d.ts +682 -0
  77. package/src/react-menu3/index.ts +11 -0
  78. package/src/react-menu3/positionUtils/getPositionHelpers.ts +76 -0
  79. package/src/react-menu3/positionUtils/index.ts +3 -0
  80. package/src/react-menu3/positionUtils/placeArrowHorizontal.ts +8 -0
  81. package/src/react-menu3/positionUtils/placeArrowVertical.ts +8 -0
  82. package/src/react-menu3/positionUtils/placeLeftorRight.ts +76 -0
  83. package/src/react-menu3/positionUtils/placeToporBottom.ts +77 -0
  84. package/src/react-menu3/positionUtils/positionContextMenu.ts +49 -0
  85. package/src/react-menu3/positionUtils/positionMenu.ts +73 -0
  86. package/src/react-menu3/style-utils/index.ts +80 -0
  87. package/src/react-menu3/style-utils.d.ts +110 -0
  88. package/src/react-menu3/styles/_mixins.scss +11 -0
  89. package/src/react-menu3/styles/_var.scss +20 -0
  90. package/src/react-menu3/styles/core.scss +80 -0
  91. package/src/react-menu3/styles/index.scss +70 -0
  92. package/src/react-menu3/styles/theme-dark.scss +30 -0
  93. package/src/react-menu3/styles/transitions/slide.scss +47 -0
  94. package/src/react-menu3/utils/constants.ts +63 -0
  95. package/src/react-menu3/utils/index.ts +4 -0
  96. package/src/react-menu3/utils/propTypes.ts +61 -0
  97. package/src/react-menu3/utils/utils.ts +71 -0
  98. package/src/react-menu3/utils/withHovering.tsx +22 -0
  99. package/src/stories/components/GridPopoutEditDropDown.stories.tsx +44 -3
  100. package/src/utils/bearing.ts +7 -4
  101. package/src/utils/util.ts +2 -1
@@ -0,0 +1,434 @@
1
+ // @ts-nocheck
2
+ import { useState, useReducer, useEffect, useRef, useMemo, useCallback, useContext } from "react";
3
+ import { flushSync } from "react-dom";
4
+ import { useBEM, useCombinedRef, useLayoutEffect, useItems } from "../hooks";
5
+ import { getPositionHelpers, positionMenu, positionContextMenu } from "../positionUtils";
6
+ import {
7
+ mergeProps,
8
+ batchedUpdates,
9
+ commonProps,
10
+ floatEqual,
11
+ getScrollAncestor,
12
+ getTransition,
13
+ safeCall,
14
+ isMenuOpen,
15
+ menuClass,
16
+ menuArrowClass,
17
+ CloseReason,
18
+ Keys,
19
+ FocusPositions,
20
+ HoverActionTypes,
21
+ SettingsContext,
22
+ MenuListContext,
23
+ MenuListItemContext,
24
+ HoverItemContext,
25
+ } from "../utils";
26
+
27
+ export const MenuList = ({
28
+ ariaLabel,
29
+ menuClassName,
30
+ menuStyle,
31
+ arrowClassName,
32
+ arrowStyle,
33
+ anchorPoint,
34
+ anchorRef,
35
+ containerRef,
36
+ externalRef,
37
+ parentScrollingRef,
38
+ arrow,
39
+ align = "start",
40
+ direction = "bottom",
41
+ position = "auto",
42
+ overflow = "visible",
43
+ setDownOverflow,
44
+ repositionFlag,
45
+ captureFocus = true,
46
+ state,
47
+ endTransition,
48
+ isDisabled,
49
+ menuItemFocus,
50
+ offsetX = 0,
51
+ offsetY = 0,
52
+ children,
53
+ onClose,
54
+ ...restProps
55
+ }) => {
56
+ const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 });
57
+ const [arrowPosition, setArrowPosition] = useState({});
58
+ const [overflowData, setOverflowData] = useState();
59
+ const [expandedDirection, setExpandedDirection] = useState(direction);
60
+ const [openSubmenuCount, setOpenSubmenuCount] = useState(0);
61
+ const [reposSubmenu, forceReposSubmenu] = useReducer((c) => c + 1, 1);
62
+ const {
63
+ transition,
64
+ boundingBoxRef,
65
+ boundingBoxPadding,
66
+ rootMenuRef,
67
+ rootAnchorRef,
68
+ scrollNodesRef,
69
+ reposition,
70
+ viewScroll,
71
+ } = useContext(SettingsContext);
72
+ const reposFlag = useContext(MenuListContext).reposSubmenu || repositionFlag;
73
+ const menuRef = useRef(null);
74
+ const focusRef = useRef();
75
+ const arrowRef = useRef();
76
+ const prevOpen = useRef(false);
77
+ const latestMenuSize = useRef({ width: 0, height: 0 });
78
+ const latestHandlePosition = useRef(() => {});
79
+ const { hoverItem, dispatch, updateItems } = useItems(menuRef, focusRef);
80
+
81
+ const isOpen = isMenuOpen(state);
82
+ const openTransition = getTransition(transition, "open");
83
+ const closeTransition = getTransition(transition, "close");
84
+ const scrollNodes = scrollNodesRef.current;
85
+
86
+ const onKeyDown = (e) => {
87
+ const isTextInputTarget =
88
+ e.target.nodeName === "TEXTAREA" || (e.target.nodeName === "INPUT" && e.target.getAttribute("type") === "text");
89
+ switch (e.key) {
90
+ case Keys.HOME:
91
+ // Don't eat home/end events on inputs
92
+ if (isTextInputTarget) return;
93
+ dispatch(HoverActionTypes.FIRST);
94
+ break;
95
+
96
+ case Keys.END:
97
+ // Don't eat home/end events on inputs
98
+ if (isTextInputTarget) return;
99
+ dispatch(HoverActionTypes.LAST);
100
+ break;
101
+
102
+ case Keys.UP:
103
+ dispatch(HoverActionTypes.DECREASE, hoverItem);
104
+ break;
105
+
106
+ case Keys.DOWN:
107
+ dispatch(HoverActionTypes.INCREASE, hoverItem);
108
+ break;
109
+
110
+ // prevent browser from scrolling the page when SPACE is pressed
111
+ case Keys.SPACE:
112
+ // Don't preventDefault on children of FocusableItem
113
+ if (e.target && e.target.className.indexOf(menuClass) !== -1) {
114
+ e.preventDefault();
115
+ }
116
+ return;
117
+
118
+ default:
119
+ return;
120
+ }
121
+
122
+ e.preventDefault();
123
+ e.stopPropagation();
124
+ };
125
+
126
+ const onAnimationEnd = () => {
127
+ if (state === "closing") {
128
+ setOverflowData(); // reset overflowData after closing
129
+ }
130
+
131
+ safeCall(endTransition);
132
+ };
133
+
134
+ const handlePosition = useCallback(
135
+ (noOverflowCheck) => {
136
+ if (!containerRef.current) {
137
+ if (process.env.NODE_ENV !== "production") {
138
+ console.error(
139
+ '[React-Menu] Menu cannot be positioned properly as container ref is null. If you need to initialise `state` prop to "open" for ControlledMenu, please see this solution: https://codesandbox.io/s/initial-open-sp10wn',
140
+ );
141
+ }
142
+ return;
143
+ }
144
+
145
+ if (!scrollNodes.menu) {
146
+ scrollNodes.menu =
147
+ (boundingBoxRef
148
+ ? boundingBoxRef.current // user explicitly sets boundingBoxRef
149
+ : getScrollAncestor(rootMenuRef.current)) || window; // try to discover bounding box automatically
150
+ }
151
+
152
+ const positionHelpers = getPositionHelpers(containerRef, menuRef, scrollNodes.menu, boundingBoxPadding);
153
+ const { menuRect } = positionHelpers;
154
+ let results = { computedDirection: "bottom" };
155
+ if (anchorPoint) {
156
+ results = positionContextMenu({ positionHelpers, anchorPoint });
157
+ } else if (anchorRef) {
158
+ results = positionMenu({
159
+ arrow,
160
+ align,
161
+ direction,
162
+ offsetX,
163
+ offsetY,
164
+ position,
165
+ anchorRef,
166
+ arrowRef,
167
+ positionHelpers,
168
+ });
169
+ }
170
+ let { arrowX, arrowY, x, y, computedDirection } = results;
171
+ let menuHeight = menuRect.height;
172
+
173
+ if (!noOverflowCheck && overflow !== "visible") {
174
+ const { getTopOverflow, getBottomOverflow } = positionHelpers;
175
+
176
+ let height, overflowAmt;
177
+ const prevHeight = latestMenuSize.current.height;
178
+ const bottomOverflow = getBottomOverflow(y);
179
+ // When bottomOverflow is 0, menu is on the bottom edge of viewport
180
+ // This might be the result of a previous maxHeight set on the menu.
181
+ // In this situation, we need to still apply a new maxHeight.
182
+ // Same reason for the top side
183
+ if (bottomOverflow > 0 || (floatEqual(bottomOverflow, 0) && floatEqual(menuHeight, prevHeight))) {
184
+ height = menuHeight - bottomOverflow;
185
+ overflowAmt = bottomOverflow;
186
+ } else {
187
+ const topOverflow = getTopOverflow(y);
188
+ if (topOverflow < 0 || (floatEqual(topOverflow, 0) && floatEqual(menuHeight, prevHeight))) {
189
+ height = menuHeight + topOverflow;
190
+ overflowAmt = 0 - topOverflow; // avoid getting -0
191
+ if (height >= 0) y -= topOverflow;
192
+ }
193
+ }
194
+
195
+ if (height >= 0) {
196
+ // To avoid triggering reposition in the next ResizeObserver callback
197
+ menuHeight = height;
198
+ setOverflowData({ height, overflowAmt });
199
+ } else {
200
+ setOverflowData();
201
+ }
202
+ }
203
+
204
+ if (arrow) setArrowPosition({ x: arrowX, y: arrowY });
205
+ setMenuPosition({ x, y });
206
+ setExpandedDirection(computedDirection);
207
+ latestMenuSize.current = { width: menuRect.width, height: menuHeight };
208
+ },
209
+ [
210
+ arrow,
211
+ align,
212
+ boundingBoxPadding,
213
+ direction,
214
+ offsetX,
215
+ offsetY,
216
+ position,
217
+ overflow,
218
+ anchorPoint,
219
+ anchorRef,
220
+ containerRef,
221
+ boundingBoxRef,
222
+ rootMenuRef,
223
+ scrollNodes,
224
+ ],
225
+ );
226
+
227
+ useLayoutEffect(() => {
228
+ if (isOpen) {
229
+ handlePosition();
230
+ // Reposition submenu whenever deps(except isOpen) have changed
231
+ if (prevOpen.current) forceReposSubmenu();
232
+ }
233
+ prevOpen.current = isOpen;
234
+ latestHandlePosition.current = handlePosition;
235
+ }, [isOpen, handlePosition, /* effect dep */ reposFlag]);
236
+
237
+ useLayoutEffect(() => {
238
+ if (overflowData && !setDownOverflow) menuRef.current.scrollTop = 0;
239
+ }, [overflowData, setDownOverflow]);
240
+
241
+ useLayoutEffect(() => updateItems, [updateItems]);
242
+
243
+ useEffect(() => {
244
+ let { menu: menuScroll } = scrollNodes;
245
+ if (!isOpen || !menuScroll) return;
246
+
247
+ menuScroll = menuScroll.addEventListener ? menuScroll : window;
248
+ if (!scrollNodes.anchors) {
249
+ scrollNodes.anchors = [];
250
+ let anchorScroll = getScrollAncestor(rootAnchorRef && rootAnchorRef.current);
251
+ while (anchorScroll && anchorScroll !== menuScroll) {
252
+ scrollNodes.anchors.push(anchorScroll);
253
+ anchorScroll = getScrollAncestor(anchorScroll);
254
+ }
255
+ }
256
+
257
+ let scroll = viewScroll;
258
+ if (scrollNodes.anchors.length && scroll === "initial") scroll = "auto";
259
+ if (scroll === "initial") return;
260
+
261
+ const handleScroll = () => {
262
+ if (scroll === "auto") {
263
+ batchedUpdates(() => handlePosition(true));
264
+ } else {
265
+ safeCall(onClose, { reason: CloseReason.SCROLL });
266
+ }
267
+ };
268
+
269
+ const scrollObservers = scrollNodes.anchors.concat(viewScroll !== "initial" ? menuScroll : []);
270
+ scrollObservers.forEach((o) => o.addEventListener("scroll", handleScroll));
271
+ return () => scrollObservers.forEach((o) => o.removeEventListener("scroll", handleScroll));
272
+ }, [rootAnchorRef, scrollNodes, isOpen, onClose, viewScroll, handlePosition]);
273
+
274
+ const hasOverflow = !!overflowData && overflowData.overflowAmt > 0;
275
+ useEffect(() => {
276
+ if (hasOverflow || !isOpen || !parentScrollingRef) return;
277
+
278
+ const handleScroll = () => batchedUpdates(handlePosition);
279
+ const parentScroll = parentScrollingRef.current;
280
+ parentScroll.addEventListener("scroll", handleScroll);
281
+ return () => parentScroll.removeEventListener("scroll", handleScroll);
282
+ }, [isOpen, hasOverflow, parentScrollingRef, handlePosition]);
283
+
284
+ useEffect(() => {
285
+ if (typeof ResizeObserver !== "function" || reposition === "initial") return;
286
+
287
+ const resizeObserver = new ResizeObserver(([entry]) => {
288
+ const { borderBoxSize, target } = entry;
289
+ let width, height;
290
+ if (borderBoxSize) {
291
+ const { inlineSize, blockSize } = borderBoxSize[0] || borderBoxSize;
292
+ width = inlineSize;
293
+ height = blockSize;
294
+ } else {
295
+ const borderRect = target.getBoundingClientRect();
296
+ width = borderRect.width;
297
+ height = borderRect.height;
298
+ }
299
+
300
+ if (width === 0 || height === 0) return;
301
+ if (floatEqual(width, latestMenuSize.current.width, 1) && floatEqual(height, latestMenuSize.current.height, 1))
302
+ return;
303
+ flushSync(() => {
304
+ latestHandlePosition.current();
305
+ forceReposSubmenu();
306
+ });
307
+ });
308
+
309
+ const observeTarget = menuRef.current;
310
+ resizeObserver.observe(observeTarget, { box: "border-box" });
311
+ return () => resizeObserver.unobserve(observeTarget);
312
+ }, [reposition]);
313
+
314
+ useEffect(() => {
315
+ if (!isOpen) {
316
+ dispatch(HoverActionTypes.RESET);
317
+ if (!closeTransition) setOverflowData();
318
+ return;
319
+ }
320
+
321
+ const { position, alwaysUpdate } = menuItemFocus || {};
322
+ const setItemFocus = () => {
323
+ if (position === FocusPositions.FIRST) {
324
+ dispatch(HoverActionTypes.FIRST);
325
+ } else if (position === FocusPositions.LAST) {
326
+ dispatch(HoverActionTypes.LAST);
327
+ } else if (position >= -1) {
328
+ dispatch(HoverActionTypes.SET_INDEX, undefined, position);
329
+ }
330
+ };
331
+
332
+ if (alwaysUpdate) {
333
+ setItemFocus();
334
+ } else if (captureFocus) {
335
+ // Use a timeout here because if set focus immediately, page might scroll unexpectedly.
336
+ const id = setTimeout(
337
+ () => {
338
+ // If focus has already been set to a children element, don't set focus on menu or item
339
+ if (!menuRef.current.contains(document.activeElement)) {
340
+ focusRef.current.focus();
341
+ setItemFocus();
342
+ }
343
+ },
344
+ openTransition ? 170 : 100,
345
+ );
346
+
347
+ return () => clearTimeout(id);
348
+ }
349
+ }, [isOpen, openTransition, closeTransition, captureFocus, menuItemFocus, dispatch]);
350
+
351
+ const isSubmenuOpen = openSubmenuCount > 0;
352
+ const itemContext = useMemo(
353
+ () => ({
354
+ isParentOpen: isOpen,
355
+ isSubmenuOpen,
356
+ setOpenSubmenuCount,
357
+ dispatch,
358
+ updateItems,
359
+ }),
360
+ [isOpen, isSubmenuOpen, dispatch, updateItems],
361
+ );
362
+
363
+ let maxHeight, overflowAmt;
364
+ if (overflowData) {
365
+ setDownOverflow ? (overflowAmt = overflowData.overflowAmt) : (maxHeight = overflowData.height);
366
+ }
367
+
368
+ const listContext = useMemo(
369
+ () => ({
370
+ reposSubmenu,
371
+ overflow,
372
+ overflowAmt,
373
+ parentMenuRef: menuRef,
374
+ parentDir: expandedDirection,
375
+ }),
376
+ [reposSubmenu, overflow, overflowAmt, expandedDirection],
377
+ );
378
+ const overflowStyle = maxHeight >= 0 ? { maxHeight, overflow } : undefined;
379
+
380
+ const modifiers = useMemo(
381
+ () => ({
382
+ state,
383
+ dir: expandedDirection,
384
+ }),
385
+ [state, expandedDirection],
386
+ );
387
+ const arrowModifiers = useMemo(() => ({ dir: expandedDirection }), [expandedDirection]);
388
+ const _arrowClass = useBEM({
389
+ block: menuClass,
390
+ element: menuArrowClass,
391
+ modifiers: arrowModifiers,
392
+ className: arrowClassName,
393
+ });
394
+
395
+ return (
396
+ <ul
397
+ role="menu"
398
+ aria-label={ariaLabel}
399
+ {...mergeProps({ onKeyDown, onAnimationEnd }, restProps)}
400
+ {...commonProps(isDisabled)}
401
+ ref={useCombinedRef(externalRef, menuRef)}
402
+ className={useBEM({ block: menuClass, modifiers, className: menuClassName })}
403
+ style={{
404
+ ...menuStyle,
405
+ ...overflowStyle,
406
+ margin: 0,
407
+ display: state === "closed" ? "none" : undefined,
408
+ position: "absolute",
409
+ left: menuPosition.x,
410
+ top: menuPosition.y,
411
+ }}
412
+ >
413
+ <div ref={focusRef} tabIndex={-1} style={{ position: "absolute", left: 0, top: 0 }} />
414
+ {arrow && (
415
+ <div
416
+ className={_arrowClass}
417
+ style={{
418
+ ...arrowStyle,
419
+ position: "absolute",
420
+ left: arrowPosition.x,
421
+ top: arrowPosition.y,
422
+ }}
423
+ ref={arrowRef}
424
+ />
425
+ )}
426
+
427
+ <MenuListContext.Provider value={listContext}>
428
+ <MenuListItemContext.Provider value={itemContext}>
429
+ <HoverItemContext.Provider value={hoverItem}>{children}</HoverItemContext.Provider>
430
+ </MenuListItemContext.Provider>
431
+ </MenuListContext.Provider>
432
+ </ul>
433
+ );
434
+ };
@@ -0,0 +1,33 @@
1
+ // @ts-nocheck
2
+ import { forwardRef, useMemo } from "react";
3
+ import { any, string, func } from "prop-types";
4
+ import { useBEM } from "../hooks";
5
+ import { stylePropTypes, menuClass, radioGroupClass, RadioGroupContext } from "../utils";
6
+
7
+ export const MenuRadioGroup = forwardRef(function MenuRadioGroup(
8
+ { "aria-label": ariaLabel, className, name, value, onRadioChange, ...restProps },
9
+ externalRef,
10
+ ) {
11
+ const contextValue = useMemo(() => ({ name, value, onRadioChange }), [name, value, onRadioChange]);
12
+
13
+ return (
14
+ <RadioGroupContext.Provider value={contextValue}>
15
+ <li role="presentation">
16
+ <ul
17
+ role="group"
18
+ aria-label={ariaLabel || name || "Radio group"}
19
+ {...restProps}
20
+ ref={externalRef}
21
+ className={useBEM({ block: menuClass, element: radioGroupClass, className })}
22
+ />
23
+ </li>
24
+ </RadioGroupContext.Provider>
25
+ );
26
+ });
27
+
28
+ MenuRadioGroup.propTypes = {
29
+ ...stylePropTypes(),
30
+ name: string,
31
+ value: any,
32
+ onRadioChange: func,
33
+ };
@@ -0,0 +1,243 @@
1
+ // @ts-nocheck
2
+ import { useRef, useContext, useEffect, useMemo, useImperativeHandle } from "react";
3
+ import { createPortal } from "react-dom";
4
+ import { node, func, bool, shape, oneOf, oneOfType } from "prop-types";
5
+ import { useBEM, useCombinedRef, useMenuChange, useMenuStateAndFocus, useItemEffect } from "../hooks";
6
+ import { MenuList } from "./MenuList";
7
+ import {
8
+ mergeProps,
9
+ batchedUpdates,
10
+ commonProps,
11
+ safeCall,
12
+ stylePropTypes,
13
+ uncontrolledMenuPropTypes,
14
+ menuPropTypes,
15
+ menuClass,
16
+ subMenuClass,
17
+ menuItemClass,
18
+ isMenuOpen,
19
+ withHovering,
20
+ SettingsContext,
21
+ ItemSettingsContext,
22
+ MenuListContext,
23
+ MenuListItemContext,
24
+ Keys,
25
+ HoverActionTypes,
26
+ FocusPositions,
27
+ } from "../utils";
28
+
29
+ export const SubMenu = withHovering(
30
+ "SubMenu",
31
+ function SubMenu({
32
+ "aria-label": ariaLabel,
33
+ className,
34
+ disabled,
35
+ direction,
36
+ label,
37
+ openTrigger,
38
+ onMenuChange,
39
+ isHovering,
40
+ instanceRef,
41
+ itemRef,
42
+ captureFocus: _1,
43
+ repositionFlag: _2,
44
+ itemProps = {},
45
+ ...restProps
46
+ }) {
47
+ const settings = useContext(SettingsContext);
48
+ const { rootMenuRef } = settings;
49
+ const { submenuOpenDelay, submenuCloseDelay } = useContext(ItemSettingsContext);
50
+ const { parentMenuRef, parentDir, overflow: parentOverflow } = useContext(MenuListContext);
51
+ const { isParentOpen, isSubmenuOpen, setOpenSubmenuCount, dispatch, updateItems } = useContext(MenuListItemContext);
52
+ const isPortal = parentOverflow !== "visible";
53
+
54
+ const [stateProps, toggleMenu, _openMenu] = useMenuStateAndFocus(settings);
55
+
56
+ const { state } = stateProps;
57
+ const isDisabled = !!disabled;
58
+ const isOpen = isMenuOpen(state);
59
+ const containerRef = useRef(null);
60
+ const timeoutId = useRef(0);
61
+
62
+ const stopTimer = () => {
63
+ if (timeoutId.current) {
64
+ clearTimeout(timeoutId.current);
65
+ timeoutId.current = 0;
66
+ }
67
+ };
68
+
69
+ const openMenu = (...args) => {
70
+ stopTimer();
71
+ setHover();
72
+ !isDisabled && _openMenu(...args);
73
+ };
74
+
75
+ const setHover = () => !isHovering && !isDisabled && dispatch(HoverActionTypes.SET, itemRef.current);
76
+
77
+ const delayOpen = (delay) => {
78
+ setHover();
79
+ if (!openTrigger) timeoutId.current = setTimeout(() => batchedUpdates(openMenu), Math.max(delay, 0));
80
+ };
81
+
82
+ const handlePointerMove = () => {
83
+ if (timeoutId.current || isOpen || isDisabled) return;
84
+
85
+ if (isSubmenuOpen) {
86
+ timeoutId.current = setTimeout(() => delayOpen(submenuOpenDelay - submenuCloseDelay), submenuCloseDelay);
87
+ } else {
88
+ delayOpen(submenuOpenDelay);
89
+ }
90
+ };
91
+
92
+ const handlePointerLeave = () => {
93
+ stopTimer();
94
+ if (!isOpen) dispatch(HoverActionTypes.UNSET, itemRef.current);
95
+ };
96
+
97
+ const handleKeyDown = (e) => {
98
+ let handled = false;
99
+
100
+ switch (e.key) {
101
+ // LEFT key is bubbled up from submenu items
102
+ case Keys.LEFT:
103
+ if (isOpen) {
104
+ itemRef.current.focus();
105
+ toggleMenu(false);
106
+ handled = true;
107
+ }
108
+ break;
109
+
110
+ // prevent browser from scrolling page to the right
111
+ case Keys.RIGHT:
112
+ if (!isOpen) handled = true;
113
+ break;
114
+ }
115
+
116
+ if (handled) {
117
+ e.preventDefault();
118
+ e.stopPropagation();
119
+ }
120
+ };
121
+
122
+ const handleItemKeyDown = (e) => {
123
+ if (!isHovering) return;
124
+
125
+ switch (e.key) {
126
+ case Keys.ENTER:
127
+ case Keys.SPACE:
128
+ case Keys.RIGHT:
129
+ openTrigger !== "none" && openMenu(FocusPositions.FIRST);
130
+ break;
131
+ }
132
+ };
133
+
134
+ useItemEffect(isDisabled, itemRef, updateItems);
135
+ useMenuChange(onMenuChange, isOpen);
136
+
137
+ useEffect(() => () => clearTimeout(timeoutId.current), []);
138
+ useEffect(() => {
139
+ // Don't set focus when parent menu is closed, otherwise focus will be lost
140
+ // and onBlur event will be fired with relatedTarget setting as null.
141
+ if (isHovering && isParentOpen) {
142
+ itemRef.current.focus();
143
+ } else {
144
+ toggleMenu(false);
145
+ }
146
+ }, [isHovering, isParentOpen, toggleMenu, itemRef]);
147
+
148
+ useEffect(() => {
149
+ setOpenSubmenuCount((count) => (isOpen ? count + 1 : Math.max(count - 1, 0)));
150
+ }, [setOpenSubmenuCount, isOpen]);
151
+
152
+ useImperativeHandle(instanceRef, () => ({
153
+ openMenu: (...args) => {
154
+ isParentOpen && openMenu(...args);
155
+ },
156
+ closeMenu: () => {
157
+ if (isOpen) {
158
+ itemRef.current.focus();
159
+ toggleMenu(false);
160
+ }
161
+ },
162
+ }));
163
+
164
+ const modifiers = useMemo(
165
+ () => ({
166
+ open: isOpen,
167
+ hover: isHovering,
168
+ disabled: isDisabled,
169
+ submenu: true,
170
+ }),
171
+ [isOpen, isHovering, isDisabled],
172
+ );
173
+
174
+ const { ref: externalItemRef, className: itemClassName, ...restItemProps } = itemProps;
175
+
176
+ const mergedItemProps = mergeProps(
177
+ {
178
+ onPointerMove: handlePointerMove,
179
+ onPointerLeave: handlePointerLeave,
180
+ onKeyDown: handleItemKeyDown,
181
+ onClick: () => openTrigger !== "none" && openMenu(),
182
+ },
183
+ restItemProps,
184
+ );
185
+
186
+ const getMenuList = () => {
187
+ const menuList = (
188
+ <MenuList
189
+ {...restProps}
190
+ {...stateProps}
191
+ ariaLabel={ariaLabel || (typeof label === "string" ? label : "Submenu")}
192
+ anchorRef={itemRef}
193
+ containerRef={isPortal ? rootMenuRef : containerRef}
194
+ direction={direction || (parentDir === "right" || parentDir === "left" ? parentDir : "right")}
195
+ parentScrollingRef={isPortal && parentMenuRef}
196
+ isDisabled={isDisabled}
197
+ />
198
+ );
199
+ const container = rootMenuRef.current;
200
+ return isPortal && container ? createPortal(menuList, container) : menuList;
201
+ };
202
+
203
+ return (
204
+ <li
205
+ className={useBEM({ block: menuClass, element: subMenuClass, className })}
206
+ style={{ position: "relative" }}
207
+ role="presentation"
208
+ ref={containerRef}
209
+ onKeyDown={handleKeyDown}
210
+ >
211
+ <div
212
+ role="menuitem"
213
+ aria-haspopup
214
+ aria-expanded={isOpen}
215
+ {...mergedItemProps}
216
+ {...commonProps(isDisabled, isHovering)}
217
+ ref={useCombinedRef(externalItemRef, itemRef)}
218
+ className={useBEM({
219
+ block: menuClass,
220
+ element: menuItemClass,
221
+ modifiers,
222
+ className: itemClassName,
223
+ })}
224
+ >
225
+ {useMemo(() => safeCall(label, modifiers), [label, modifiers])}
226
+ </div>
227
+
228
+ {state && getMenuList()}
229
+ </li>
230
+ );
231
+ },
232
+ );
233
+
234
+ SubMenu.propTypes = {
235
+ ...menuPropTypes,
236
+ ...uncontrolledMenuPropTypes,
237
+ disabled: bool,
238
+ openTrigger: oneOf(["none", "clickOnly"]),
239
+ label: oneOfType([node, func]),
240
+ itemProps: shape({
241
+ ...stylePropTypes(),
242
+ }),
243
+ };