|
1 | 1 | import clsx from 'clsx'; |
2 | 2 | import type { ComponentProps, PropsWithChildren } from 'react'; |
3 | | -import React, { useEffect, useState } from 'react'; |
| 3 | +import React, { useEffect, useRef, useState } from 'react'; |
4 | 4 | import { FocusScope } from '@react-aria/focus'; |
5 | 5 | import { DialogPortalEntry } from './DialogPortal'; |
6 | 6 | import { useDialog, useDialogIsOpen } from '../hooks'; |
@@ -29,22 +29,33 @@ export function useDialogAnchor<T extends HTMLElement>({ |
29 | 29 | placement, |
30 | 30 | }); |
31 | 31 |
|
| 32 | + // Freeze reference when dialog opens so submenus (e.g. ContextMenu level 2+) stay aligned to the original anchor |
| 33 | + const frozenReferenceRef = useRef<HTMLElement | null>(null); |
| 34 | + if (open && referenceElement && !frozenReferenceRef.current) { |
| 35 | + frozenReferenceRef.current = referenceElement; |
| 36 | + } |
| 37 | + if (!open) { |
| 38 | + frozenReferenceRef.current = null; |
| 39 | + } |
| 40 | + const effectiveReference = open ? frozenReferenceRef.current : referenceElement; |
| 41 | + |
32 | 42 | useEffect(() => { |
33 | | - refs.setReference(referenceElement); |
34 | | - }, [referenceElement, refs]); |
| 43 | + refs.setReference(effectiveReference); |
| 44 | + }, [effectiveReference, refs]); |
35 | 45 |
|
36 | 46 | useEffect(() => { |
37 | 47 | refs.setFloating(popperElement); |
38 | 48 | }, [popperElement, refs]); |
39 | 49 |
|
40 | 50 | useEffect(() => { |
41 | | - if (open && popperElement) { |
| 51 | + if (open && popperElement && effectiveReference) { |
| 52 | + // Re-run when reference becomes available (e.g. after ref is set) or when updateKey changes (e.g. submenu open) |
42 | 53 | // Since the popper's reference element might not be (and usually is not) visible |
43 | 54 | // all the time, it's safer to force popper update before showing it. |
44 | 55 | // update is non-null only if popperElement is non-null |
45 | 56 | update?.(); |
46 | 57 | } |
47 | | - }, [open, placement, popperElement, update, updateKey]); |
| 58 | + }, [open, placement, popperElement, update, updateKey, effectiveReference]); |
48 | 59 |
|
49 | 60 | if (popperElement && !open) { |
50 | 61 | setPopperElement(null); |
@@ -83,6 +94,7 @@ export const DialogAnchor = ({ |
83 | 94 | }: DialogAnchorProps) => { |
84 | 95 | const dialog = useDialog({ dialogManagerId, id }); |
85 | 96 | const open = useDialogIsOpen(id, dialogManagerId); |
| 97 | + |
86 | 98 | const { setPopperElement, styles } = useDialogAnchor<HTMLDivElement>({ |
87 | 99 | allowFlip, |
88 | 100 | open, |
|
0 commit comments