Skip to content

Commit fefb40d

Browse files
committed
feat: respect event.defaultPrevented in DialogAnchor in hide on Escape
1 parent 04542b4 commit fefb40d

2 files changed

Lines changed: 75 additions & 1 deletion

File tree

src/components/Dialog/__tests__/DialogPortal.test.tsx

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import React from 'react';
22
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
3+
import {
4+
Dropdown,
5+
type DropdownTriggerProps,
6+
useDropdownContext,
7+
} from '../../Form/Dropdown';
38
import { useDialogOnNearestManager } from '../hooks';
49
import { DialogAnchor } from '../service';
510
import { DialogManagerProvider } from '../../../context';
@@ -29,6 +34,32 @@ const DialogFixture = ({
2934
);
3035
};
3136

37+
const DropdownTriggerButton = ({
38+
children,
39+
onClick,
40+
referenceRef,
41+
...props
42+
}: DropdownTriggerProps) => (
43+
<button
44+
{...props}
45+
onClick={onClick}
46+
ref={referenceRef as React.Ref<HTMLButtonElement>}
47+
type='button'
48+
>
49+
{children}
50+
</button>
51+
);
52+
53+
const DropdownItem = ({ label }: { label: string }) => {
54+
const { close } = useDropdownContext();
55+
56+
return (
57+
<button onClick={close} role='menuitem' type='button'>
58+
{label}
59+
</button>
60+
);
61+
};
62+
3263
describe('DialogPortal', () => {
3364
it('does not close dialogs from another manager when clicking in a different manager overlay', async () => {
3465
render(
@@ -118,4 +149,47 @@ describe('DialogPortal', () => {
118149
const results = await axe(document.body);
119150
expect(results).toHaveNoViolations();
120151
});
152+
153+
it('does not close the dialog when Escape is handled by a nested dropdown', async () => {
154+
render(
155+
<DialogManagerProvider>
156+
<DialogFixture
157+
dialogId='dialog-with-dropdown'
158+
testId='dialog-dropdown-content'
159+
trapFocus
160+
>
161+
<Dropdown
162+
TriggerComponent={DropdownTriggerButton}
163+
triggerProps={{ children: 'Duration' }}
164+
>
165+
<DropdownItem label='15 minutes' />
166+
<DropdownItem label='1 hour' />
167+
</Dropdown>
168+
</DialogFixture>
169+
</DialogManagerProvider>,
170+
);
171+
172+
fireEvent.click(screen.getByTestId('open-dialog-with-dropdown'));
173+
expect(screen.getByRole('dialog')).toBeInTheDocument();
174+
175+
fireEvent.click(screen.getByRole('button', { name: 'Duration' }));
176+
await screen.findByRole('menu');
177+
178+
const item = screen.getByRole('menuitem', { name: '15 minutes' });
179+
item.focus();
180+
181+
fireEvent.keyDown(item, { key: 'Escape' });
182+
fireEvent.keyUp(item, { key: 'Escape' });
183+
184+
await waitFor(() => {
185+
expect(screen.queryByRole('menu')).not.toBeInTheDocument();
186+
});
187+
expect(screen.getByRole('dialog')).toBeInTheDocument();
188+
189+
fireEvent.keyUp(document, { key: 'Escape' });
190+
191+
await waitFor(() => {
192+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
193+
});
194+
});
121195
});

src/components/Dialog/service/DialogAnchor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ export const DialogAnchor = ({
212212
useEffect(() => {
213213
if (!open) return;
214214
const hideOnEscape = (event: KeyboardEvent) => {
215-
if (event.key !== 'Escape') return;
215+
if (event.key !== 'Escape' || event.defaultPrevented) return;
216216
dialog?.close();
217217
};
218218

0 commit comments

Comments
 (0)