diff --git a/web/pgadmin/static/js/helpers/ModalProvider.jsx b/web/pgadmin/static/js/helpers/ModalProvider.jsx index c770e2860..383e25f72 100644 --- a/web/pgadmin/static/js/helpers/ModalProvider.jsx +++ b/web/pgadmin/static/js/helpers/ModalProvider.jsx @@ -366,9 +366,11 @@ function ModalContainer({ id, title, content, dialogHeight, dialogWidth, onClose } }; const [isFullScreen, setIsFullScreen] = useState(fullScreen); + const dialogRef = useRef(null); return ( . MUI's modal + // manager sets aria-hidden on the children of its container, which + // defaults to , so it would hide the application root and the + // dialog along with it, whilst focus is inside the dialog. Use the + // dialog's own parent as the container so that only its siblings, the + // background content, are hidden. + container={() => dialogRef.current?.parentElement} {...props} > { showTitle && diff --git a/web/regression/javascript/ModalProvider.spec.js b/web/regression/javascript/ModalProvider.spec.js index ea51ed8e5..58e383adb 100644 --- a/web/regression/javascript/ModalProvider.spec.js +++ b/web/regression/javascript/ModalProvider.spec.js @@ -66,7 +66,6 @@ describe('ModalProvider AlertContent', () => { }); }); - describe('ModalProvider keeps focus in an open modal (#9784)', () => { let iframe; @@ -166,3 +165,37 @@ describe('ModalProvider keeps focus in an open modal (#9784)', () => { expect(document.activeElement).toBe(input); }); }); + +describe('ModalProvider aria-hidden handling', () => { + let modalApi; + function CaptureModal() { + modalApi = useModal(); + return ; + } + const ThemedProvider = withTheme(ModalProvider); + + it('hides the background content but not the dialog itself', + async () => { + const ctrl = await act(async () => render( + + )); + const background = screen.getByText('Background action'); + background.focus(); + + await act(async () => { + modalApi.confirm('Reset statistics', 'Are you sure?', () => {}); + }); + + const dialog = screen.getByRole('dialog'); + expect(dialog.closest('[aria-hidden="true"]')).toBeNull(); + expect(dialog.contains(document.activeElement)).toBe(true); + expect(background.closest('[aria-hidden="true"]')).not.toBeNull(); + expect(ctrl.container.closest('[aria-hidden="true"]')).toBeNull(); + + await act(async () => { + screen.getByText('No').click(); + }); + expect(screen.queryByRole('dialog')).toBeNull(); + expect(background.closest('[aria-hidden="true"]')).toBeNull(); + }); +});