feat(stammbaum): mobile read path — pan, zoom, fit-to-view (#692) #694

Merged
marcel merged 39 commits from feat/issue-692-stammbaum-mobile-panzoom into main 2026-05-30 07:43:44 +02:00
2 changed files with 19 additions and 0 deletions
Showing only changes of commit d5a7974f3a - Show all commits

View File

@@ -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);
});
}); });

View File

@@ -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?.();
} }
}; };
} }