@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.
- package/dist/index.js +1631 -3040
- package/dist/index.js.map +1 -1
- package/dist/src/components/GridLoadableCell.d.ts +2 -0
- package/dist/src/components/gridForm/GridFormDropDown.d.ts +1 -1
- package/dist/src/components/gridForm/GridFormMultiSelect.d.ts +1 -1
- package/dist/src/components/gridPopoverEdit/GridPopoverMenu.d.ts +1 -1
- package/dist/src/react-menu3/components/ControlledMenu.d.ts +2 -0
- package/dist/src/react-menu3/components/FocusableItem.d.ts +9 -0
- package/dist/src/react-menu3/components/Menu.d.ts +16 -0
- package/dist/src/react-menu3/components/MenuButton.d.ts +8 -0
- package/dist/src/react-menu3/components/MenuDivider.d.ts +3 -0
- package/dist/src/react-menu3/components/MenuGroup.d.ts +12 -0
- package/dist/src/react-menu3/components/MenuHeader.d.ts +4 -0
- package/dist/src/react-menu3/components/MenuItem.d.ts +59 -0
- package/dist/src/react-menu3/components/MenuList.d.ts +57 -0
- package/dist/src/react-menu3/components/MenuRadioGroup.d.ts +44 -0
- package/dist/src/react-menu3/components/SubMenu.d.ts +110 -0
- package/dist/src/react-menu3/hooks/index.d.ts +9 -0
- package/dist/src/react-menu3/hooks/useBEM.d.ts +10 -0
- package/dist/src/react-menu3/hooks/useCombinedRef.d.ts +2 -0
- package/dist/src/react-menu3/hooks/useIsomorphicLayoutEffect.d.ts +3 -0
- package/dist/src/react-menu3/hooks/useItemEffect.d.ts +2 -0
- package/dist/src/react-menu3/hooks/useItemState.d.ts +7 -0
- package/dist/src/react-menu3/hooks/useItems.d.ts +7 -0
- package/dist/src/react-menu3/hooks/useMenuChange.d.ts +2 -0
- package/dist/src/react-menu3/hooks/useMenuState.d.ts +14 -0
- package/dist/src/react-menu3/hooks/useMenuStateAndFocus.d.ts +2 -0
- package/dist/src/react-menu3/index.d.ts +402 -0
- package/dist/src/react-menu3/positionUtils/getPositionHelpers.d.ts +11 -0
- package/dist/src/react-menu3/positionUtils/index.d.ts +3 -0
- package/dist/src/react-menu3/positionUtils/placeArrowHorizontal.d.ts +8 -0
- package/dist/src/react-menu3/positionUtils/placeArrowVertical.d.ts +8 -0
- package/dist/src/react-menu3/positionUtils/placeLeftorRight.d.ts +19 -0
- package/dist/src/react-menu3/positionUtils/placeToporBottom.d.ts +19 -0
- package/dist/src/react-menu3/positionUtils/positionContextMenu.d.ts +13 -0
- package/dist/src/react-menu3/positionUtils/positionMenu.d.ts +26 -0
- package/dist/src/react-menu3/style-utils/index.d.ts +11 -0
- package/dist/src/react-menu3/utils/constants.d.ts +92 -0
- package/dist/src/react-menu3/utils/index.d.ts +4 -0
- package/dist/src/react-menu3/utils/propTypes.d.ts +56 -0
- package/dist/src/react-menu3/utils/utils.d.ts +36 -0
- package/dist/src/react-menu3/utils/withHovering.d.ts +7 -0
- package/dist/src/stories/components/ReactMenu.stories.d.ts +8 -0
- package/dist/step-ag-grid.esm.js +1543 -2951
- package/dist/step-ag-grid.esm.js.map +1 -1
- package/package.json +22 -13
- package/src/components/GridLoadableCell.scss +4 -0
- package/src/components/GridLoadableCell.tsx +5 -1
- package/src/components/GridPopoverHook.tsx +4 -50
- package/src/components/gridForm/GridFormDropDown.tsx +3 -3
- package/src/components/gridForm/GridFormMultiSelect.tsx +12 -6
- package/src/components/gridForm/GridFormPopoutMenu.tsx +1 -1
- package/src/components/gridPopoverEdit/GridPopoverMenu.scss +6 -2
- package/src/components/gridPopoverEdit/GridPopoverMenu.tsx +1 -1
- package/src/components/gridRender/GridRenderPopoutMenuCell.tsx +7 -9
- package/src/react-menu3/components/ControlledMenu.tsx +261 -0
- package/src/react-menu3/components/FocusableItem.tsx +75 -0
- package/src/react-menu3/components/Menu.tsx +108 -0
- package/src/react-menu3/components/MenuButton.tsx +34 -0
- package/src/react-menu3/components/MenuDivider.tsx +17 -0
- package/src/react-menu3/components/MenuGroup.tsx +47 -0
- package/src/react-menu3/components/MenuHeader.tsx +17 -0
- package/src/react-menu3/components/MenuItem.tsx +183 -0
- package/src/react-menu3/components/MenuList.tsx +502 -0
- package/src/react-menu3/components/MenuRadioGroup.tsx +72 -0
- package/src/react-menu3/components/SubMenu.tsx +349 -0
- package/src/react-menu3/hooks/index.ts +9 -0
- package/src/react-menu3/hooks/useBEM.ts +33 -0
- package/src/react-menu3/hooks/useCombinedRef.ts +24 -0
- package/src/react-menu3/hooks/useIsomorphicLayoutEffect.ts +15 -0
- package/src/react-menu3/hooks/useItemEffect.ts +23 -0
- package/src/react-menu3/hooks/useItemState.ts +67 -0
- package/src/react-menu3/hooks/useItems.ts +111 -0
- package/src/react-menu3/hooks/useMenuChange.ts +12 -0
- package/src/react-menu3/hooks/useMenuState.ts +42 -0
- package/src/react-menu3/hooks/useMenuStateAndFocus.ts +17 -0
- package/src/react-menu3/index.d.ts +683 -0
- package/src/react-menu3/index.ts +442 -0
- package/src/react-menu3/positionUtils/getPositionHelpers.ts +83 -0
- package/src/react-menu3/positionUtils/index.ts +3 -0
- package/src/react-menu3/positionUtils/placeArrowHorizontal.ts +15 -0
- package/src/react-menu3/positionUtils/placeArrowVertical.ts +15 -0
- package/src/react-menu3/positionUtils/placeLeftorRight.ts +92 -0
- package/src/react-menu3/positionUtils/placeToporBottom.ts +93 -0
- package/src/react-menu3/positionUtils/positionContextMenu.ts +61 -0
- package/src/react-menu3/positionUtils/positionMenu.ts +80 -0
- package/src/react-menu3/style-utils/index.ts +83 -0
- package/src/react-menu3/style-utils.d.ts +110 -0
- package/src/react-menu3/styles/_mixins.scss +11 -0
- package/src/react-menu3/styles/_var.scss +20 -0
- package/src/react-menu3/styles/core.scss +80 -0
- package/src/react-menu3/styles/index.scss +70 -0
- package/src/react-menu3/styles/theme-dark.scss +30 -0
- package/src/react-menu3/styles/transitions/slide.scss +47 -0
- package/src/react-menu3/utils/constants.ts +116 -0
- package/src/react-menu3/utils/index.ts +4 -0
- package/src/react-menu3/utils/propTypes.ts +63 -0
- package/src/react-menu3/utils/utils.ts +88 -0
- package/src/react-menu3/utils/withHovering.tsx +27 -0
- package/src/stories/components/GridPopoutEditDropDown.stories.tsx +1 -3
- package/src/stories/components/ReactMenu.stories.tsx +37 -0
- 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;
|