@@ -4,14 +4,12 @@ import {
4
4
Composite ,
5
5
CompositeItem ,
6
6
FloatingList ,
7
- FloatingTree ,
8
7
flip ,
9
8
offset ,
10
9
safePolygon ,
11
10
shift ,
12
11
useClick ,
13
12
useDismiss ,
14
- useFloatingNodeId ,
15
13
useFloatingParentNodeId ,
16
14
useFloatingRootContext ,
17
15
useFloatingTree ,
@@ -39,7 +37,65 @@ import './Menu.scss';
39
37
40
38
const b = block ( 'menu2' ) ;
41
39
42
- function MenuComponent ( {
40
+ // The component is needed to run submenu logic hooks.
41
+ // We get <nodeId> of the Popup using "useFloatingParentNodeId" here
42
+ // and <parentId> from using "useFloatingParentNodeId" outside the Popup.
43
+ function MenuPopupContent ( {
44
+ open,
45
+ onRequestClose,
46
+ parentId,
47
+ children,
48
+ className,
49
+ style,
50
+ qa,
51
+ } : Pick < MenuProps , 'children' | 'className' | 'style' | 'qa' > & {
52
+ open : boolean ;
53
+ onRequestClose : ( ) => void ;
54
+ parentId : string | null ;
55
+ } ) {
56
+ const tree = useFloatingTree ( ) ;
57
+ const nodeId = useFloatingParentNodeId ( ) ;
58
+
59
+ React . useEffect ( ( ) => {
60
+ if ( ! tree ) return ;
61
+
62
+ function handleTreeClick ( ) {
63
+ // Closing only the root Menu so the closing animation runs once for all menus due to shared portal container
64
+ if ( ! parentId ) {
65
+ onRequestClose ( ) ;
66
+ }
67
+ }
68
+
69
+ function handleSubMenuOpen ( event : { nodeId : string ; parentId : string } ) {
70
+ // Closing on sibling submenu open
71
+ if ( event . nodeId !== nodeId && event . parentId === parentId ) {
72
+ onRequestClose ( ) ;
73
+ }
74
+ }
75
+
76
+ tree . events . on ( 'click' , handleTreeClick ) ;
77
+ tree . events . on ( 'menuopen' , handleSubMenuOpen ) ;
78
+
79
+ return ( ) => {
80
+ tree . events . off ( 'click' , handleTreeClick ) ;
81
+ tree . events . off ( 'menuopen' , handleSubMenuOpen ) ;
82
+ } ;
83
+ } , [ onRequestClose , tree , nodeId , parentId ] ) ;
84
+
85
+ React . useEffect ( ( ) => {
86
+ if ( open && tree ) {
87
+ tree . events . emit ( 'menuopen' , { parentId, nodeId} ) ;
88
+ }
89
+ } , [ open , tree , nodeId , parentId ] ) ;
90
+
91
+ return (
92
+ < div className = { b ( null , className ) } style = { style } data-qa = { qa } >
93
+ { children }
94
+ </ div >
95
+ ) ;
96
+ }
97
+
98
+ export function Menu ( {
43
99
trigger,
44
100
inline = false ,
45
101
defaultOpen,
@@ -63,8 +119,6 @@ function MenuComponent({
63
119
const itemsRef = React . useRef < Array < HTMLElement | null > > ( [ ] ) ;
64
120
const parentMenu = React . useContext ( MenuContext ) ;
65
121
66
- const tree = useFloatingTree ( ) ;
67
- const nodeId = useFloatingNodeId ( ) ;
68
122
const parentId = useFloatingParentNodeId ( ) ;
69
123
const isNested = Boolean ( parentId ) ;
70
124
@@ -114,6 +168,10 @@ function MenuComponent({
114
168
? trigger ( anchorProps , anchorRef )
115
169
: null ;
116
170
171
+ const handleContentRequestClose = React . useCallback ( ( ) => {
172
+ setIsOpen ( false ) ;
173
+ } , [ setIsOpen ] ) ;
174
+
117
175
const getItemPropsInline = React . useCallback (
118
176
( userProps ?: React . HTMLAttributes < HTMLElement > ) => {
119
177
const handleItemPointerEnter = ( event : React . PointerEvent < HTMLElement > ) => {
@@ -163,37 +221,6 @@ function MenuComponent({
163
221
[ parentMenu , inline , size , activeIndex , getItemPropsInline , getItemProps ] ,
164
222
) ;
165
223
166
- React . useEffect ( ( ) => {
167
- if ( ! tree ) return ;
168
-
169
- function handleTreeClick ( ) {
170
- // Closing only the root Menu so the closing animation runs once for all menus due to shared portal container
171
- if ( ! parentId ) {
172
- setIsOpen ( false ) ;
173
- }
174
- }
175
-
176
- function handleSubMenuOpen ( event : { nodeId : string ; parentId : string } ) {
177
- if ( event . nodeId !== nodeId && event . parentId === parentId ) {
178
- setIsOpen ( false ) ;
179
- }
180
- }
181
-
182
- tree . events . on ( 'click' , handleTreeClick ) ;
183
- tree . events . on ( 'menuopen' , handleSubMenuOpen ) ;
184
-
185
- return ( ) => {
186
- tree . events . off ( 'click' , handleTreeClick ) ;
187
- tree . events . off ( 'menuopen' , handleSubMenuOpen ) ;
188
- } ;
189
- } , [ setIsOpen , tree , nodeId , parentId ] ) ;
190
-
191
- React . useEffect ( ( ) => {
192
- if ( isOpen && tree ) {
193
- tree . events . emit ( 'menuopen' , { parentId, nodeId} ) ;
194
- }
195
- } , [ isOpen , tree , nodeId , parentId ] ) ;
196
-
197
224
React . useEffect ( ( ) => {
198
225
if ( ! anchorNode ) {
199
226
if ( trigger ) {
@@ -251,37 +278,29 @@ function MenuComponent({
251
278
placement = { isNested ? `${ isRTL ? 'left' : 'right' } -start` : placement }
252
279
disablePortal = { isNested }
253
280
floatingContext = { floatingContext }
254
- floatingNodeId = { nodeId }
255
281
floatingRef = { setFloatingElement }
256
282
floatingMiddlewares = { middlewares }
257
283
floatingInteractions = { interactions }
258
284
>
259
285
< MenuContext . Provider value = { contextValue } >
260
286
< FloatingList elementsRef = { itemsRef } >
261
- < div className = { b ( null , className ) } style = { style } data-qa = { qa } >
287
+ < MenuPopupContent
288
+ open = { isOpen }
289
+ onRequestClose = { handleContentRequestClose }
290
+ parentId = { parentId }
291
+ className = { className }
292
+ style = { style }
293
+ qa = { qa }
294
+ >
262
295
{ children }
263
- </ div >
296
+ </ MenuPopupContent >
264
297
</ FloatingList >
265
298
</ MenuContext . Provider >
266
299
</ Popup >
267
300
</ React . Fragment >
268
301
) ;
269
302
}
270
303
271
- export function Menu ( props : MenuProps ) {
272
- const parentId = useFloatingParentNodeId ( ) ;
273
-
274
- if ( ! props . inline && parentId === null ) {
275
- return (
276
- < FloatingTree >
277
- < MenuComponent { ...props } />
278
- </ FloatingTree >
279
- ) ;
280
- }
281
-
282
- return < MenuComponent { ...props } /> ;
283
- }
284
-
285
304
Menu . displayName = 'Menu' ;
286
305
287
306
Menu . Trigger = MenuTrigger ;
0 commit comments