From 8d91f8de8fb4def516401444480b59616b9fb943 Mon Sep 17 00:00:00 2001 From: martincupela Date: Tue, 14 Apr 2026 12:07:59 +0200 Subject: [PATCH] fix(dialog): prevent outside-click from other dialog managers closing active dialogs --- .../Dialog/__tests__/DialogPortal.test.tsx | 50 +++++++++++++++++++ .../Dialog/service/DialogPortal.tsx | 4 ++ 2 files changed, 54 insertions(+) create mode 100644 src/components/Dialog/__tests__/DialogPortal.test.tsx diff --git a/src/components/Dialog/__tests__/DialogPortal.test.tsx b/src/components/Dialog/__tests__/DialogPortal.test.tsx new file mode 100644 index 0000000000..9eed41513d --- /dev/null +++ b/src/components/Dialog/__tests__/DialogPortal.test.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { useDialogOnNearestManager } from '../hooks'; +import { DialogAnchor } from '../service'; +import { DialogManagerProvider } from '../../../context'; + +const DialogFixture = ({ dialogId, testId }: { dialogId: string; testId: string }) => { + const { dialog } = useDialogOnNearestManager({ id: dialogId }); + + return ( + <> + + + + + + ); +}; + +describe('DialogPortal', () => { + it('does not close dialogs from another manager when clicking in a different manager overlay', async () => { + render( + + + + + + , + ); + + act(() => { + fireEvent.click(screen.getByTestId('open-dialog-a')); + fireEvent.click(screen.getByTestId('open-dialog-b')); + }); + + expect(screen.getByTestId('dialog-a-content')).toBeInTheDocument(); + expect(screen.getByTestId('dialog-b-content')).toBeInTheDocument(); + + act(() => { + fireEvent.click(screen.getByTestId('dialog-b-content')); + }); + + await waitFor(() => { + expect(screen.getByTestId('dialog-a-content')).toBeInTheDocument(); + expect(screen.getByTestId('dialog-b-content')).toBeInTheDocument(); + }); + }); +}); diff --git a/src/components/Dialog/service/DialogPortal.tsx b/src/components/Dialog/service/DialogPortal.tsx index dbfb45603c..fb381a412a 100644 --- a/src/components/Dialog/service/DialogPortal.tsx +++ b/src/components/Dialog/service/DialogPortal.tsx @@ -25,6 +25,10 @@ export const DialogPortalDestination = () => { const handleDocumentClick = (event: MouseEvent) => { const target = event.target as Node; + const clickedOverlay = (target as HTMLElement).closest?.( + '[data-str-chat__portal-id]', + ); + if (clickedOverlay && clickedOverlay !== destinationRoot) return; if (target !== destinationRoot && destinationRoot.contains(target)) return; // Defer so target onClick handlers (e.g. context-menu toggle buttons) can run first. setTimeout(() => {