@linzjs/step-ag-grid 1.4.4 → 1.4.6

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 (102) hide show
  1. package/dist/index.js +1631 -3040
  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 +9 -0
  9. package/dist/src/react-menu3/components/Menu.d.ts +16 -0
  10. package/dist/src/react-menu3/components/MenuButton.d.ts +8 -0
  11. package/dist/src/react-menu3/components/MenuDivider.d.ts +3 -0
  12. package/dist/src/react-menu3/components/MenuGroup.d.ts +12 -0
  13. package/dist/src/react-menu3/components/MenuHeader.d.ts +4 -0
  14. package/dist/src/react-menu3/components/MenuItem.d.ts +59 -0
  15. package/dist/src/react-menu3/components/MenuList.d.ts +57 -0
  16. package/dist/src/react-menu3/components/MenuRadioGroup.d.ts +44 -0
  17. package/dist/src/react-menu3/components/SubMenu.d.ts +110 -0
  18. package/dist/src/react-menu3/hooks/index.d.ts +9 -0
  19. package/dist/src/react-menu3/hooks/useBEM.d.ts +10 -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 +7 -0
  24. package/dist/src/react-menu3/hooks/useItems.d.ts +7 -0
  25. package/dist/src/react-menu3/hooks/useMenuChange.d.ts +2 -0
  26. package/dist/src/react-menu3/hooks/useMenuState.d.ts +14 -0
  27. package/dist/src/react-menu3/hooks/useMenuStateAndFocus.d.ts +2 -0
  28. package/dist/src/react-menu3/index.d.ts +402 -0
  29. package/dist/src/react-menu3/positionUtils/getPositionHelpers.d.ts +11 -0
  30. package/dist/src/react-menu3/positionUtils/index.d.ts +3 -0
  31. package/dist/src/react-menu3/positionUtils/placeArrowHorizontal.d.ts +8 -0
  32. package/dist/src/react-menu3/positionUtils/placeArrowVertical.d.ts +8 -0
  33. package/dist/src/react-menu3/positionUtils/placeLeftorRight.d.ts +19 -0
  34. package/dist/src/react-menu3/positionUtils/placeToporBottom.d.ts +19 -0
  35. package/dist/src/react-menu3/positionUtils/positionContextMenu.d.ts +13 -0
  36. package/dist/src/react-menu3/positionUtils/positionMenu.d.ts +26 -0
  37. package/dist/src/react-menu3/style-utils/index.d.ts +11 -0
  38. package/dist/src/react-menu3/utils/constants.d.ts +92 -0
  39. package/dist/src/react-menu3/utils/index.d.ts +4 -0
  40. package/dist/src/react-menu3/utils/propTypes.d.ts +56 -0
  41. package/dist/src/react-menu3/utils/utils.d.ts +36 -0
  42. package/dist/src/react-menu3/utils/withHovering.d.ts +7 -0
  43. package/dist/src/stories/components/ReactMenu.stories.d.ts +8 -0
  44. package/dist/step-ag-grid.esm.js +1543 -2951
  45. package/dist/step-ag-grid.esm.js.map +1 -1
  46. package/package.json +22 -13
  47. package/src/components/GridLoadableCell.scss +4 -0
  48. package/src/components/GridLoadableCell.tsx +5 -1
  49. package/src/components/GridPopoverHook.tsx +4 -50
  50. package/src/components/gridForm/GridFormDropDown.tsx +3 -3
  51. package/src/components/gridForm/GridFormMultiSelect.tsx +12 -6
  52. package/src/components/gridForm/GridFormPopoutMenu.tsx +1 -1
  53. package/src/components/gridPopoverEdit/GridPopoverMenu.scss +6 -2
  54. package/src/components/gridPopoverEdit/GridPopoverMenu.tsx +1 -1
  55. package/src/components/gridRender/GridRenderPopoutMenuCell.tsx +7 -9
  56. package/src/react-menu3/components/ControlledMenu.tsx +261 -0
  57. package/src/react-menu3/components/FocusableItem.tsx +75 -0
  58. package/src/react-menu3/components/Menu.tsx +108 -0
  59. package/src/react-menu3/components/MenuButton.tsx +34 -0
  60. package/src/react-menu3/components/MenuDivider.tsx +17 -0
  61. package/src/react-menu3/components/MenuGroup.tsx +47 -0
  62. package/src/react-menu3/components/MenuHeader.tsx +17 -0
  63. package/src/react-menu3/components/MenuItem.tsx +183 -0
  64. package/src/react-menu3/components/MenuList.tsx +502 -0
  65. package/src/react-menu3/components/MenuRadioGroup.tsx +72 -0
  66. package/src/react-menu3/components/SubMenu.tsx +349 -0
  67. package/src/react-menu3/hooks/index.ts +9 -0
  68. package/src/react-menu3/hooks/useBEM.ts +33 -0
  69. package/src/react-menu3/hooks/useCombinedRef.ts +24 -0
  70. package/src/react-menu3/hooks/useIsomorphicLayoutEffect.ts +15 -0
  71. package/src/react-menu3/hooks/useItemEffect.ts +23 -0
  72. package/src/react-menu3/hooks/useItemState.ts +67 -0
  73. package/src/react-menu3/hooks/useItems.ts +111 -0
  74. package/src/react-menu3/hooks/useMenuChange.ts +12 -0
  75. package/src/react-menu3/hooks/useMenuState.ts +42 -0
  76. package/src/react-menu3/hooks/useMenuStateAndFocus.ts +17 -0
  77. package/src/react-menu3/index.d.ts +683 -0
  78. package/src/react-menu3/index.ts +442 -0
  79. package/src/react-menu3/positionUtils/getPositionHelpers.ts +83 -0
  80. package/src/react-menu3/positionUtils/index.ts +3 -0
  81. package/src/react-menu3/positionUtils/placeArrowHorizontal.ts +15 -0
  82. package/src/react-menu3/positionUtils/placeArrowVertical.ts +15 -0
  83. package/src/react-menu3/positionUtils/placeLeftorRight.ts +92 -0
  84. package/src/react-menu3/positionUtils/placeToporBottom.ts +93 -0
  85. package/src/react-menu3/positionUtils/positionContextMenu.ts +61 -0
  86. package/src/react-menu3/positionUtils/positionMenu.ts +80 -0
  87. package/src/react-menu3/style-utils/index.ts +83 -0
  88. package/src/react-menu3/style-utils.d.ts +110 -0
  89. package/src/react-menu3/styles/_mixins.scss +11 -0
  90. package/src/react-menu3/styles/_var.scss +20 -0
  91. package/src/react-menu3/styles/core.scss +80 -0
  92. package/src/react-menu3/styles/index.scss +70 -0
  93. package/src/react-menu3/styles/theme-dark.scss +30 -0
  94. package/src/react-menu3/styles/transitions/slide.scss +47 -0
  95. package/src/react-menu3/utils/constants.ts +116 -0
  96. package/src/react-menu3/utils/index.ts +4 -0
  97. package/src/react-menu3/utils/propTypes.ts +63 -0
  98. package/src/react-menu3/utils/utils.ts +88 -0
  99. package/src/react-menu3/utils/withHovering.tsx +27 -0
  100. package/src/stories/components/GridPopoutEditDropDown.stories.tsx +1 -3
  101. package/src/stories/components/ReactMenu.stories.tsx +37 -0
  102. package/src/utils/util.ts +2 -1
@@ -0,0 +1,502 @@
1
+ import { useState, useReducer, useEffect, useRef, useMemo, useCallback, useContext, MutableRefObject } from "react";
2
+ import { flushSync } from "react-dom";
3
+ import { useBEM, useCombinedRef, useLayoutEffect, useItems } from "../hooks";
4
+ import { getPositionHelpers, positionMenu, positionContextMenu } from "../positionUtils";
5
+ import {
6
+ mergeProps,
7
+ batchedUpdates,
8
+ commonProps,
9
+ floatEqual,
10
+ getScrollAncestor,
11
+ getTransition,
12
+ safeCall,
13
+ isMenuOpen,
14
+ menuClass,
15
+ menuArrowClass,
16
+ CloseReason,
17
+ Keys,
18
+ FocusPositions,
19
+ HoverActionTypes,
20
+ SettingsContext,
21
+ MenuListContext,
22
+ MenuListItemContext,
23
+ HoverItemContext,
24
+ } from "../utils";
25
+ import { EventHandler, FocusPosition, MenuCloseEvent, MenuDirection, MenuState, RootMenuProps } from "../index";
26
+
27
+ interface ExtraMenuProps {
28
+ isDisabled?: boolean;
29
+ ariaLabel?: string;
30
+ containerRef: MutableRefObject<HTMLElement>;
31
+ externalRef: MutableRefObject<HTMLUListElement>;
32
+ parentScrollingRef: MutableRefObject<any>;
33
+ }
34
+
35
+ //
36
+ // ControlledMenu
37
+ // ----------------------------------------------------------------------
38
+ export interface ControlledMenuProps extends RootMenuProps, ExtraMenuProps {
39
+ /**
40
+ * Viewport coordinates to which context menu will be positioned.
41
+ *
42
+ * *Use this prop only for context menu*
43
+ */
44
+ anchorPoint?: {
45
+ x: number;
46
+ y: number;
47
+ };
48
+ /**
49
+ * A ref object attached to a DOM element to which menu will be positioned.
50
+ *
51
+ * *Don't set this prop for context menu*
52
+ */
53
+ anchorRef?: React.MutableRefObject<HTMLElement>;
54
+ skipOpen?: React.RefObject<boolean>;
55
+ /**
56
+ * If `true`, the menu list element will gain focus after menu is open.
57
+ * @default true
58
+ */
59
+ captureFocus?: boolean;
60
+ /**
61
+ * Controls the state of menu. When the prop is `undefined`, menu will be unmounted from DOM.
62
+ */
63
+ state?: MenuState;
64
+ /**
65
+ * Sets which menu item receives focus (hover) when menu opens.
66
+ * You will usually set this prop when the menu is opened by keyboard events.
67
+ *
68
+ * *Note: If you don't intend to update focus (hover) position,
69
+ * it's important to keep this prop's identity stable when your component re-renders.*
70
+ */
71
+ menuItemFocus?: {
72
+ position?: FocusPosition;
73
+ alwaysUpdate?: boolean;
74
+ };
75
+ /**
76
+ * Set the return value of `useMenuState` to this prop.
77
+ */
78
+ endTransition?: () => void;
79
+ /**
80
+ * Event fired when menu is about to close.
81
+ */
82
+ onClose?: EventHandler<MenuCloseEvent>;
83
+ }
84
+
85
+ export const MenuList = ({
86
+ ariaLabel,
87
+ menuClassName,
88
+ menuStyle,
89
+ arrowClassName,
90
+ arrowStyle,
91
+ anchorPoint,
92
+ anchorRef,
93
+ containerRef,
94
+ externalRef,
95
+ parentScrollingRef,
96
+ arrow,
97
+ align = "start",
98
+ direction = "bottom",
99
+ position = "auto",
100
+ overflow = "visible",
101
+ setDownOverflow,
102
+ repositionFlag,
103
+ captureFocus = true,
104
+ state,
105
+ endTransition,
106
+ isDisabled,
107
+ menuItemFocus,
108
+ offsetX = 0,
109
+ offsetY = 0,
110
+ children,
111
+ onClose,
112
+ ...restProps
113
+ }: ControlledMenuProps) => {
114
+ const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 });
115
+ const [arrowPosition, setArrowPosition] = useState({ x: 0, y: 0 });
116
+ const [overflowData, setOverflowData] = useState<{ height: number; overflowAmt: number | undefined }>();
117
+ const [expandedDirection, setExpandedDirection] = useState(direction);
118
+ const [openSubmenuCount, setOpenSubmenuCount] = useState(0);
119
+ const [reposSubmenu, forceReposSubmenu] = useReducer((c) => c + 1, 1);
120
+ const {
121
+ transition,
122
+ boundingBoxRef,
123
+ boundingBoxPadding,
124
+ rootMenuRef,
125
+ rootAnchorRef,
126
+ scrollNodesRef,
127
+ reposition,
128
+ viewScroll,
129
+ } = useContext(SettingsContext);
130
+ const reposFlag = useContext(MenuListContext).reposSubmenu || repositionFlag;
131
+ const menuRef = useRef<HTMLUListElement>({} as HTMLUListElement);
132
+ const focusRef = useRef<HTMLDivElement>(null);
133
+ const arrowRef = useRef<HTMLDivElement>(null);
134
+ const prevOpen = useRef(false);
135
+ const latestMenuSize = useRef({ width: 0, height: 0 });
136
+ const latestHandlePosition = useRef(() => {});
137
+ const { hoverItem, dispatch, updateItems } = useItems(menuRef, focusRef);
138
+
139
+ const isOpen = isMenuOpen(state);
140
+ const openTransition = getTransition(transition, "open");
141
+ const closeTransition = getTransition(transition, "close");
142
+ const scrollNodes = scrollNodesRef.current;
143
+
144
+ const onKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {
145
+ const elementTarget = e.target instanceof HTMLElement ? (e.target as HTMLElement) : null;
146
+ const isTextInputTarget =
147
+ elementTarget &&
148
+ (elementTarget.nodeName === "TEXTAREA" ||
149
+ (elementTarget.nodeName === "INPUT" && elementTarget.getAttribute("type") === "text"));
150
+ switch (e.key) {
151
+ case Keys.HOME:
152
+ // Don't eat home/end events on inputs
153
+ if (isTextInputTarget) return;
154
+ dispatch(HoverActionTypes.FIRST, null, 0);
155
+ break;
156
+
157
+ case Keys.END:
158
+ // Don't eat home/end events on inputs
159
+ if (isTextInputTarget) return;
160
+ dispatch(HoverActionTypes.LAST, null, 0);
161
+ break;
162
+
163
+ case Keys.UP:
164
+ dispatch(HoverActionTypes.DECREASE, hoverItem, 0);
165
+ break;
166
+
167
+ case Keys.DOWN:
168
+ dispatch(HoverActionTypes.INCREASE, hoverItem, 0);
169
+ break;
170
+
171
+ // prevent browser from scrolling the page when SPACE is pressed
172
+ case Keys.SPACE:
173
+ // Don't preventDefault on children of FocusableItem
174
+ if (elementTarget && elementTarget.className.includes(menuClass)) {
175
+ e.preventDefault();
176
+ }
177
+ return;
178
+
179
+ default:
180
+ return;
181
+ }
182
+
183
+ e.preventDefault();
184
+ e.stopPropagation();
185
+ };
186
+
187
+ const onAnimationEnd = () => {
188
+ if (state === "closing") {
189
+ setOverflowData(undefined); // reset overflowData after closing
190
+ }
191
+
192
+ endTransition && safeCall(endTransition);
193
+ };
194
+
195
+ const handlePosition = useCallback(
196
+ (noOverflowCheck?: boolean) => {
197
+ if (!containerRef.current) {
198
+ if (process.env.NODE_ENV !== "production") {
199
+ console.error(
200
+ '[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',
201
+ );
202
+ }
203
+ return;
204
+ }
205
+
206
+ if (!scrollNodes.menu) {
207
+ scrollNodes.menu =
208
+ (boundingBoxRef
209
+ ? boundingBoxRef.current // user explicitly sets boundingBoxRef
210
+ : getScrollAncestor(rootMenuRef?.current)) || window; // try to discover bounding box automatically
211
+ }
212
+
213
+ const positionHelpers = getPositionHelpers(containerRef, menuRef, scrollNodes.menu, boundingBoxPadding);
214
+ const { menuRect } = positionHelpers;
215
+ let results: { computedDirection: MenuDirection; arrowX?: number; arrowY?: number; x: number; y: number } = {
216
+ computedDirection: "bottom",
217
+ x: 0,
218
+ y: 0,
219
+ };
220
+ if (anchorPoint) {
221
+ results = positionContextMenu({ positionHelpers, anchorPoint });
222
+ } else if (anchorRef) {
223
+ results = positionMenu({
224
+ arrow,
225
+ align,
226
+ direction,
227
+ offsetX,
228
+ offsetY,
229
+ position,
230
+ anchorRef,
231
+ arrowRef,
232
+ positionHelpers,
233
+ });
234
+ }
235
+ let { y } = results;
236
+ const { x, arrowX, arrowY, computedDirection } = results;
237
+ let menuHeight = menuRect.height;
238
+
239
+ if (!noOverflowCheck && overflow !== "visible") {
240
+ const { getTopOverflow, getBottomOverflow } = positionHelpers;
241
+
242
+ let height: number | undefined, overflowAmt: number | undefined;
243
+ const prevHeight = latestMenuSize.current.height;
244
+ const bottomOverflow = getBottomOverflow(y);
245
+ // When bottomOverflow is 0, menu is on the bottom edge of viewport
246
+ // This might be the result of a previous maxHeight set on the menu.
247
+ // In this situation, we need to still apply a new maxHeight.
248
+ // Same reason for the top side
249
+ if (bottomOverflow > 0 || (floatEqual(bottomOverflow, 0) && floatEqual(menuHeight, prevHeight))) {
250
+ height = menuHeight - bottomOverflow;
251
+ overflowAmt = bottomOverflow;
252
+ } else {
253
+ const topOverflow = getTopOverflow(y);
254
+ if (topOverflow < 0 || (floatEqual(topOverflow, 0) && floatEqual(menuHeight, prevHeight))) {
255
+ height = menuHeight + topOverflow;
256
+ overflowAmt = 0 - topOverflow; // avoid getting -0
257
+ if (height >= 0) y -= topOverflow;
258
+ }
259
+ }
260
+
261
+ if (height != null && height >= 0) {
262
+ // To avoid triggering reposition in the next ResizeObserver callback
263
+ menuHeight = height;
264
+ setOverflowData({ height, overflowAmt });
265
+ } else {
266
+ setOverflowData(undefined);
267
+ }
268
+ }
269
+
270
+ if (arrow) setArrowPosition({ x: arrowX ?? 0, y: arrowY ?? 0 });
271
+ setMenuPosition({ x, y });
272
+ setExpandedDirection(computedDirection);
273
+ latestMenuSize.current = { width: menuRect.width, height: menuHeight };
274
+ },
275
+ [
276
+ arrow,
277
+ align,
278
+ boundingBoxPadding,
279
+ direction,
280
+ offsetX,
281
+ offsetY,
282
+ position,
283
+ overflow,
284
+ anchorPoint,
285
+ anchorRef,
286
+ containerRef,
287
+ boundingBoxRef,
288
+ rootMenuRef,
289
+ scrollNodes,
290
+ ],
291
+ );
292
+
293
+ useLayoutEffect(() => {
294
+ if (isOpen) {
295
+ handlePosition();
296
+ // Reposition submenu whenever deps(except isOpen) have changed
297
+ if (prevOpen.current) forceReposSubmenu();
298
+ }
299
+ prevOpen.current = isOpen;
300
+ latestHandlePosition.current = handlePosition;
301
+ }, [isOpen, handlePosition, /* effect dep */ reposFlag]);
302
+
303
+ useLayoutEffect(() => {
304
+ if (overflowData && !setDownOverflow) menuRef.current.scrollTop = 0;
305
+ }, [overflowData, setDownOverflow]);
306
+
307
+ useLayoutEffect(() => updateItems, [updateItems]);
308
+
309
+ useEffect(() => {
310
+ let { menu: menuScroll } = scrollNodes;
311
+ if (!isOpen || !menuScroll) return;
312
+
313
+ menuScroll = menuScroll.addEventListener ? menuScroll : window;
314
+ if (!scrollNodes.anchors) {
315
+ scrollNodes.anchors = [];
316
+ let anchorScroll = getScrollAncestor(rootAnchorRef && rootAnchorRef.current);
317
+ while (anchorScroll && anchorScroll !== menuScroll) {
318
+ scrollNodes.anchors.push(anchorScroll);
319
+ anchorScroll = getScrollAncestor(anchorScroll);
320
+ }
321
+ }
322
+
323
+ let scroll = viewScroll;
324
+ if (scrollNodes.anchors.length && scroll === "initial") scroll = "auto";
325
+ if (scroll === "initial") return;
326
+
327
+ const handleScroll = () => {
328
+ if (scroll === "auto") {
329
+ batchedUpdates(() => handlePosition(true));
330
+ } else {
331
+ safeCall(onClose, { reason: CloseReason.SCROLL });
332
+ }
333
+ };
334
+
335
+ const scrollObservers = scrollNodes.anchors.concat(viewScroll !== "initial" ? menuScroll : []);
336
+ scrollObservers.forEach((o: any) => o.addEventListener("scroll", handleScroll));
337
+ return () => scrollObservers.forEach((o) => o.removeEventListener("scroll", handleScroll));
338
+ }, [rootAnchorRef, scrollNodes, isOpen, onClose, viewScroll, handlePosition]);
339
+
340
+ const hasOverflow = !!overflowData && overflowData.overflowAmt != null && overflowData.overflowAmt > 0;
341
+ useEffect(() => {
342
+ if (hasOverflow || !isOpen || !parentScrollingRef) return;
343
+
344
+ const handleScroll = () => batchedUpdates(handlePosition);
345
+ const parentScroll = parentScrollingRef.current;
346
+ parentScroll.addEventListener("scroll", handleScroll);
347
+ return () => parentScroll.removeEventListener("scroll", handleScroll);
348
+ }, [isOpen, hasOverflow, parentScrollingRef, handlePosition]);
349
+
350
+ useEffect(() => {
351
+ if (typeof ResizeObserver !== "function" || reposition === "initial") return;
352
+
353
+ const resizeObserver = new ResizeObserver(([entry]) => {
354
+ const { borderBoxSize, target } = entry;
355
+ let width, height;
356
+ if (borderBoxSize) {
357
+ const { inlineSize, blockSize } = borderBoxSize[0] || borderBoxSize;
358
+ width = inlineSize;
359
+ height = blockSize;
360
+ } else {
361
+ const borderRect = target.getBoundingClientRect();
362
+ width = borderRect.width;
363
+ height = borderRect.height;
364
+ }
365
+
366
+ if (width === 0 || height === 0) return;
367
+ if (floatEqual(width, latestMenuSize.current.width, 1) && floatEqual(height, latestMenuSize.current.height, 1))
368
+ return;
369
+ flushSync(() => {
370
+ latestHandlePosition.current();
371
+ forceReposSubmenu();
372
+ });
373
+ });
374
+
375
+ const observeTarget = menuRef.current;
376
+ resizeObserver.observe(observeTarget, { box: "border-box" });
377
+ return () => resizeObserver.unobserve(observeTarget);
378
+ }, [reposition]);
379
+
380
+ useEffect(() => {
381
+ if (!isOpen) {
382
+ dispatch(HoverActionTypes.RESET, undefined, 0);
383
+ if (!closeTransition) setOverflowData(undefined);
384
+ return () => {};
385
+ }
386
+
387
+ const { position, alwaysUpdate } = menuItemFocus || {};
388
+ const setItemFocus = () => {
389
+ if (position === FocusPositions.FIRST) {
390
+ dispatch(HoverActionTypes.FIRST, undefined, 0);
391
+ } else if (position === FocusPositions.LAST) {
392
+ dispatch(HoverActionTypes.LAST, undefined, 0);
393
+ } else if (typeof position === "number" && position >= -1) {
394
+ dispatch(HoverActionTypes.SET_INDEX, undefined, position);
395
+ }
396
+ };
397
+
398
+ if (alwaysUpdate) {
399
+ setItemFocus();
400
+ } else if (captureFocus) {
401
+ // Use a timeout here because if set focus immediately, page might scroll unexpectedly.
402
+ const id = setTimeout(
403
+ () => {
404
+ // If focus has already been set to a children element, don't set focus on menu or item
405
+ if (!menuRef.current.contains(document.activeElement)) {
406
+ focusRef.current?.focus();
407
+ setItemFocus();
408
+ }
409
+ },
410
+ openTransition ? 170 : 100,
411
+ );
412
+
413
+ return () => clearTimeout(id);
414
+ }
415
+ return () => {};
416
+ }, [isOpen, openTransition, closeTransition, captureFocus, menuItemFocus, dispatch]);
417
+
418
+ const isSubmenuOpen = openSubmenuCount > 0;
419
+ const itemContext = useMemo(
420
+ () => ({
421
+ isParentOpen: isOpen,
422
+ isSubmenuOpen,
423
+ setOpenSubmenuCount,
424
+ dispatch,
425
+ updateItems,
426
+ }),
427
+ [isOpen, isSubmenuOpen, dispatch, updateItems],
428
+ );
429
+
430
+ let maxHeight: number | undefined;
431
+ let overflowAmt: number | undefined;
432
+ if (overflowData) {
433
+ setDownOverflow ? (overflowAmt = overflowData.overflowAmt) : (maxHeight = overflowData.height);
434
+ }
435
+
436
+ const listContext = useMemo(
437
+ () => ({
438
+ reposSubmenu,
439
+ overflow,
440
+ overflowAmt,
441
+ parentMenuRef: menuRef,
442
+ parentDir: expandedDirection,
443
+ }),
444
+ [reposSubmenu, overflow, overflowAmt, expandedDirection],
445
+ );
446
+ const overflowStyle = maxHeight != null && maxHeight >= 0 ? { maxHeight, overflow } : undefined;
447
+
448
+ const modifiers = useMemo(
449
+ () => ({
450
+ state,
451
+ dir: expandedDirection,
452
+ }),
453
+ [state, expandedDirection],
454
+ );
455
+ const arrowModifiers = useMemo(() => ({ dir: expandedDirection }), [expandedDirection]);
456
+ const _arrowClass = useBEM({
457
+ block: menuClass,
458
+ element: menuArrowClass,
459
+ modifiers: arrowModifiers,
460
+ className: arrowClassName,
461
+ });
462
+
463
+ return (
464
+ <ul
465
+ role="menu"
466
+ aria-label={ariaLabel}
467
+ {...mergeProps({ onKeyDown, onAnimationEnd }, restProps)}
468
+ {...commonProps(isDisabled)}
469
+ ref={useCombinedRef(externalRef, menuRef)}
470
+ className={useBEM({ block: menuClass, modifiers, className: menuClassName })}
471
+ style={{
472
+ ...menuStyle,
473
+ ...overflowStyle,
474
+ margin: 0,
475
+ display: state === "closed" ? "none" : undefined,
476
+ position: "absolute",
477
+ left: menuPosition.x,
478
+ top: menuPosition.y,
479
+ }}
480
+ >
481
+ <div ref={focusRef} tabIndex={-1} style={{ position: "absolute", left: 0, top: 0 }} />
482
+ {arrow && (
483
+ <div
484
+ className={_arrowClass}
485
+ style={{
486
+ ...arrowStyle,
487
+ position: "absolute",
488
+ left: arrowPosition.x,
489
+ top: arrowPosition.y,
490
+ }}
491
+ ref={arrowRef}
492
+ />
493
+ )}
494
+
495
+ <MenuListContext.Provider value={listContext}>
496
+ <MenuListItemContext.Provider value={itemContext}>
497
+ <HoverItemContext.Provider value={hoverItem}>{children}</HoverItemContext.Provider>
498
+ </MenuListItemContext.Provider>
499
+ </MenuListContext.Provider>
500
+ </ul>
501
+ );
502
+ };
@@ -0,0 +1,72 @@
1
+ import { ForwardedRef, forwardRef, ReactNode, useMemo } from "react";
2
+ import { useBEM } from "../hooks";
3
+ import { menuClass, radioGroupClass, RadioGroupContext } from "../utils";
4
+ import { BaseProps, Event, EventHandler } from "../index";
5
+
6
+ export interface RadioChangeEvent extends Event {
7
+ /**
8
+ * The `name` prop passed to the `MenuRadioGroup` when the menu item is in a radio group.
9
+ */
10
+ name?: string;
11
+ /**
12
+ * Set this property on event object to control whether to keep menu open after menu item is activated.
13
+ * Leaving it `undefined` will behave in accordance with WAI-ARIA Authoring Practices.
14
+ */
15
+ keepOpen?: boolean;
16
+ /**
17
+ * Setting this property on event object to `true` will skip `onItemClick` event on root menu component.
18
+ */
19
+ stopPropagation?: boolean;
20
+ /**
21
+ * DOM event object (React synthetic event)
22
+ */
23
+ syntheticEvent: MouseEvent | KeyboardEvent;
24
+ }
25
+
26
+ //
27
+ // MenuRadioGroup
28
+ // ----------------------------------------------------------------------
29
+ export interface MenuRadioGroupProps extends BaseProps {
30
+ /**
31
+ * Optionally set the radio group name.
32
+ *
33
+ * The name will be passed to the `onRadioChange` event.
34
+ * It's useful for identifying radio groups if you attach the same event handler to multiple groups.
35
+ */
36
+ name?: string;
37
+ /**
38
+ * Set value of the radio group.
39
+ *
40
+ * The child menu item which has the same value (strict equality ===) as the
41
+ * radio group is marked as checked.
42
+ */
43
+ value?: any;
44
+ children?: ReactNode;
45
+ /**
46
+ * Event fired when a child menu item is clicked (selected).
47
+ */
48
+ onRadioChange?: EventHandler<RadioChangeEvent>;
49
+ }
50
+
51
+ export const MenuRadioGroupFr = (
52
+ { "aria-label": ariaLabel, className, name, value, onRadioChange, ...restProps }: MenuRadioGroupProps,
53
+ externalRef: ForwardedRef<HTMLUListElement>,
54
+ ) => {
55
+ const contextValue = useMemo(() => ({ name, value, onRadioChange }), [name, value, onRadioChange]);
56
+
57
+ return (
58
+ <RadioGroupContext.Provider value={contextValue}>
59
+ <li role="presentation">
60
+ <ul
61
+ role="group"
62
+ aria-label={ariaLabel || name || "Radio group"}
63
+ {...restProps}
64
+ ref={externalRef}
65
+ className={useBEM({ block: menuClass, element: radioGroupClass, className })}
66
+ />
67
+ </li>
68
+ </RadioGroupContext.Provider>
69
+ );
70
+ };
71
+
72
+ export const MenuRadioGroup = forwardRef(MenuRadioGroupFr) as any as typeof MenuRadioGroupFr;