From cd511e31b8f781ca290d96d1397af06b3db70ade Mon Sep 17 00:00:00 2001 From: Ridwan Sanusi Date: Thu, 11 Jun 2026 10:06:21 -0400 Subject: [PATCH 1/2] =?UTF-8?q?[WCAG=202.4.3]=20focus-trap=20=E2=80=94=20c?= =?UTF-8?q?apture=20and=20restore=20focus=20to=20trigger=20element=20on=20?= =?UTF-8?q?close?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keyboard users who open a Modal/Drawer/Maximizable and close it via Escape had focus fall to document.body, forcing full re-navigation. Fix captures document.activeElement before first focus() call and restores it in cleanUp(). The MutationObserver path (DOM changes inside the trap) now calls removeListeners() directly instead of cleanUp() to avoid incorrectly restoring focus on in-trap content changes. Co-Authored-By: Claude Sonnet 4.6 --- src/lib/utilities/focus-trap.ts | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/lib/utilities/focus-trap.ts b/src/lib/utilities/focus-trap.ts index d1dfd3d8e4..e1bda80e6f 100644 --- a/src/lib/utilities/focus-trap.ts +++ b/src/lib/utilities/focus-trap.ts @@ -12,6 +12,7 @@ export const getFocusableElements = (node: HTMLElement) => export const focusTrap = (node: HTMLElement, enabled: boolean) => { let firstFocusable: HTMLElement; let lastFocusable: HTMLElement; + let previouslyFocused: HTMLElement | null = null; const onKeydown = (event: KeyboardEvent) => { if (event.key === 'Tab') { @@ -27,6 +28,11 @@ export const focusTrap = (node: HTMLElement, enabled: boolean) => { } }; + const removeListeners = () => { + firstFocusable?.removeEventListener('keydown', onKeydown); + lastFocusable?.removeEventListener('keydown', onKeydown); + }; + const setFocus = (fromObserver: boolean = false) => { if (enabled === false) return; @@ -34,15 +40,23 @@ export const focusTrap = (node: HTMLElement, enabled: boolean) => { firstFocusable = focusable[0]; lastFocusable = focusable[focusable.length - 1]; - if (!fromObserver) firstFocusable?.focus(); + if (!fromObserver) { + if (previouslyFocused === null && document.activeElement instanceof HTMLElement) { + previouslyFocused = document.activeElement; + } + firstFocusable?.focus(); + } firstFocusable?.addEventListener('keydown', onKeydown); lastFocusable?.addEventListener('keydown', onKeydown); }; const cleanUp = () => { - firstFocusable?.removeEventListener('keydown', onKeydown); - lastFocusable?.removeEventListener('keydown', onKeydown); + removeListeners(); + if (previouslyFocused && document.body.contains(previouslyFocused)) { + previouslyFocused.focus(); + } + previouslyFocused = null; }; const onChange = ( @@ -50,7 +64,7 @@ export const focusTrap = (node: HTMLElement, enabled: boolean) => { observer: MutationObserver, ) => { if (mutationRecords.length) { - cleanUp(); + removeListeners(); setFocus(true); } return observer; From 7335fedae4fb7f3cc7ca8760dd0ba06f6e8673fb Mon Sep 17 00:00:00 2001 From: Ridwan Sanusi Date: Thu, 11 Jun 2026 12:42:18 -0400 Subject: [PATCH 2/2] fix: prettier format long if condition in focus-trap Co-Authored-By: Claude Sonnet 4.6 --- src/lib/utilities/focus-trap.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/utilities/focus-trap.ts b/src/lib/utilities/focus-trap.ts index e1bda80e6f..c81362301a 100644 --- a/src/lib/utilities/focus-trap.ts +++ b/src/lib/utilities/focus-trap.ts @@ -41,7 +41,10 @@ export const focusTrap = (node: HTMLElement, enabled: boolean) => { lastFocusable = focusable[focusable.length - 1]; if (!fromObserver) { - if (previouslyFocused === null && document.activeElement instanceof HTMLElement) { + if ( + previouslyFocused === null && + document.activeElement instanceof HTMLElement + ) { previouslyFocused = document.activeElement; } firstFocusable?.focus();