Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions web/pgadmin/static/js/helpers/ModalProvider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -366,9 +366,11 @@ function ModalContainer({ id, title, content, dialogHeight, dialogWidth, onClose
}
};
const [isFullScreen, setIsFullScreen] = useState(fullScreen);
const dialogRef = useRef(null);

return (
<StyleDialog
ref={dialogRef}
open={true}
onClose={closeModal}
PaperComponent={PaperComponent}
Expand All @@ -380,6 +382,14 @@ function ModalContainer({ id, title, content, dialogHeight, dialogWidth, onClose
fullScreen={isFullScreen}
fullWidth={isFullWidth}
disablePortal
// The dialog is rendered in place (disablePortal), so it sits inside
// the application root rather than directly under <body>. MUI's modal
// manager sets aria-hidden on the children of its container, which
// defaults to <body>, 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 &&
Expand Down
35 changes: 34 additions & 1 deletion web/regression/javascript/ModalProvider.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ describe('ModalProvider AlertContent', () => {
});
});


describe('ModalProvider keeps focus in an open modal (#9784)', () => {
let iframe;

Expand Down Expand Up @@ -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 <button>Background action</button>;
}
const ThemedProvider = withTheme(ModalProvider);

it('hides the background content but not the dialog itself',
async () => {
const ctrl = await act(async () => render(
<ThemedProvider><CaptureModal /></ThemedProvider>
));
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();
});
});
Loading