diff --git a/.changeset/fix-dropdown-in-dialog-dismiss-3971.md b/.changeset/fix-dropdown-in-dialog-dismiss-3971.md new file mode 100644 index 0000000000..5ee41b3429 --- /dev/null +++ b/.changeset/fix-dropdown-in-dialog-dismiss-3971.md @@ -0,0 +1,8 @@ +--- +'@radix-ui/react-menu': patch +'@radix-ui/react-dropdown-menu': patch +'@radix-ui/react-context-menu': patch +'@radix-ui/react-menubar': patch +--- + +Defer outside pointer interactions on menu content so nested dialogs are not dismissed when closing a menu. diff --git a/apps/storybook/stories/dropdown-menu.stories.tsx b/apps/storybook/stories/dropdown-menu.stories.tsx index f387437829..3c8a1e3bd2 100644 --- a/apps/storybook/stories/dropdown-menu.stories.tsx +++ b/apps/storybook/stories/dropdown-menu.stories.tsx @@ -564,6 +564,28 @@ export const NestedComposition = () => { ); }; +export const DropdownWithinDialog = () => ( + + + + + Dialog with dropdown + + Open menu + + + Item 1 + Item 2 + + + + +

Dialog is still open

+
+
+
+); + export const SingleItemAsDialogTrigger = () => { const dropdownTriggerRef = React.useRef>(null); const dropdownTriggerRef2 = React.useRef>(null); diff --git a/packages/react/dismissable-layer/src/dismissable-layer.test.tsx b/packages/react/dismissable-layer/src/dismissable-layer.test.tsx index 4ad5769f49..5903017f78 100644 --- a/packages/react/dismissable-layer/src/dismissable-layer.test.tsx +++ b/packages/react/dismissable-layer/src/dismissable-layer.test.tsx @@ -395,4 +395,31 @@ describe('DismissableLayer', () => { expect(onDismiss).not.toHaveBeenCalled(); }); + + // Regression test for https://github.com/radix-ui/primitives/issues/3971 + it('does not dismiss a deferred parent when a nested deferred child dismisses first', async () => { + const onParentDismiss = vi.fn(); + const onChildDismiss = vi.fn(); + + render( + <> + +
dialog
+ +
menu
+
+
+ , + ); + await waitForDocumentPointerDownListener(); + + firePointerMouseClick(screen.getByTestId('parent-inside')); + + await act(async () => { + await new Promise((resolve) => window.setTimeout(resolve, 0)); + }); + + expect(onChildDismiss).toHaveBeenCalledTimes(1); + expect(onParentDismiss).not.toHaveBeenCalled(); + }); }); diff --git a/packages/react/menu/src/menu.tsx b/packages/react/menu/src/menu.tsx index 6011982e6d..e4fc3289c3 100644 --- a/packages/react/menu/src/menu.tsx +++ b/packages/react/menu/src/menu.tsx @@ -490,6 +490,7 @@ const MenuContentImpl = React.forwardRef