fix(shared): trapFocus restores focus to the opener on destroy (#692)
When the bottom sheet closes, focus returns to the element that was focused before it opened instead of being dropped to document.body (WCAG 2.4.3, Architect + UX review). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -57,4 +57,19 @@ describe('trapFocus action', () => {
|
|||||||
// No trap after destroy → focus stays on the last button.
|
// No trap after destroy → focus stays on the last button.
|
||||||
expect(document.activeElement).toBe(buttons[1]);
|
expect(document.activeElement).toBe(buttons[1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('restores focus to the previously-focused element on destroy (WCAG 2.4.3)', () => {
|
||||||
|
const opener = document.createElement('button');
|
||||||
|
document.body.appendChild(opener);
|
||||||
|
nodes.push(opener);
|
||||||
|
opener.focus();
|
||||||
|
expect(document.activeElement).toBe(opener);
|
||||||
|
|
||||||
|
const { node } = makeContainer(['one', 'two']);
|
||||||
|
const handle = trapFocus(node);
|
||||||
|
expect(document.activeElement).not.toBe(opener);
|
||||||
|
|
||||||
|
handle.destroy();
|
||||||
|
expect(document.activeElement).toBe(opener);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ const FOCUSABLE_SELECTOR = [
|
|||||||
].join(',');
|
].join(',');
|
||||||
|
|
||||||
export function trapFocus(node: HTMLElement) {
|
export function trapFocus(node: HTMLElement) {
|
||||||
|
// Remember what had focus so it can be restored when the overlay closes
|
||||||
|
// (WCAG 2.4.3 — don't strand keyboard/AT users at the top of the page).
|
||||||
|
const previouslyFocused = document.activeElement as HTMLElement | null;
|
||||||
const focusable = () => Array.from(node.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR));
|
const focusable = () => Array.from(node.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR));
|
||||||
|
|
||||||
function onKeydown(event: KeyboardEvent) {
|
function onKeydown(event: KeyboardEvent) {
|
||||||
@@ -38,6 +41,7 @@ export function trapFocus(node: HTMLElement) {
|
|||||||
return {
|
return {
|
||||||
destroy() {
|
destroy() {
|
||||||
node.removeEventListener('keydown', onKeydown);
|
node.removeEventListener('keydown', onKeydown);
|
||||||
|
previouslyFocused?.focus?.();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user