From df93bfb64eb80dbfa30f86c89b54a9021c43a9c6 Mon Sep 17 00:00:00 2001 From: cesnimda Date: Wed, 11 Nov 2026 07:44:38 +0100 Subject: [PATCH] fix(dialog): recover initial focus from nested modal fallback --- job-tracker-ui/src/confirm.test.tsx | 16 ++++++++++++++++ job-tracker-ui/src/confirm.tsx | 7 ++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/job-tracker-ui/src/confirm.test.tsx b/job-tracker-ui/src/confirm.test.tsx index bd69952d..b3a4fd8f 100644 --- a/job-tracker-ui/src/confirm.test.tsx +++ b/job-tracker-ui/src/confirm.test.tsx @@ -67,3 +67,19 @@ test('finishing the opening transition preserves focus already moved inside the jest.useRealTimers(); } }); + +test('finishing the opening transition recovers Cancel from the nested-modal container fallback', async () => { + jest.useFakeTimers(); + try { + render(); + fireEvent.click(screen.getByRole('button', { name: /open confirm/i })); + const container = screen.getByRole('dialog', { name: 'Delete job' }).parentElement!; + expect(container).toHaveAttribute('role', 'presentation'); + act(() => container.focus()); + expect(container).toHaveFocus(); + await act(async () => { jest.advanceTimersByTime(1000); }); + expect(screen.getByRole('button', { name: 'Cancel' })).toHaveFocus(); + } finally { + jest.useRealTimers(); + } +}); diff --git a/job-tracker-ui/src/confirm.tsx b/job-tracker-ui/src/confirm.tsx index a9c27504..2b48aecd 100644 --- a/job-tracker-ui/src/confirm.tsx +++ b/job-tracker-ui/src/confirm.tsx @@ -24,6 +24,7 @@ export function ConfirmProvider({ children }: { children: React.ReactNode }) { const i18n = useOptionalI18n(); const messageId = useId(); const titleId = useId(); + const cancelRef = useRef(null); const resolverRef = useRef<((value: boolean) => void) | null>(null); const [state, setState] = useState({ open: false, @@ -63,6 +64,10 @@ export function ConfirmProvider({ children }: { children: React.ReactNode }) { closeWith(false)} aria-labelledby={titleId} aria-describedby={messageId} fullWidth maxWidth="xs" slotProps={{ paper: { sx: { borderRadius: 3, border: 1, borderColor: "divider", backgroundImage: "none" } }, + transition: { onEntered: (node) => { + // Nested modals can leave initial focus on the container; preserve deliberate focus inside it. + if (node.ownerDocument.activeElement === node) cancelRef.current?.focus(); + } }, }}> {state.title} @@ -74,7 +79,7 @@ export function ConfirmProvider({ children }: { children: React.ReactNode }) { {state.message} - +