fix(auth-ui): add focus traps to modals + aria-disabled on all buttons

- Add focus trap (Tab/Shift+Tab cycling) to AuthGateModal and
  GuestWelcomeModal with auto-focus on primary action
- Add aria-disabled to all disabled buttons across 8 components
  for proper screen reader announcements

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-31 14:22:25 +02:00
parent 201819280e
commit e5c63f65fb
8 changed files with 98 additions and 0 deletions

View file

@ -159,6 +159,45 @@
onClose();
}
}
function trapFocus(node: HTMLElement) {
const focusableSelectors =
'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
function handleKeydown(e: KeyboardEvent) {
if (e.key !== 'Tab') return;
const focusable = Array.from(node.querySelectorAll(focusableSelectors)) as HTMLElement[];
if (focusable.length === 0) return;
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
node.addEventListener('keydown', handleKeydown);
// Auto-focus the primary (login) button
const focusable = node.querySelectorAll(focusableSelectors) as NodeListOf<HTMLElement>;
// Skip the close button (index 0), focus the login button (index 1)
if (focusable.length > 1) {
focusable[1].focus();
} else if (focusable.length > 0) {
focusable[0].focus();
}
return {
destroy() {
node.removeEventListener('keydown', handleKeydown);
},
};
}
</script>
<svelte:window onkeydown={handleKeydown} />
@ -175,6 +214,7 @@
aria-modal="true"
aria-labelledby="auth-gate-title"
onclick={(e) => e.stopPropagation()}
use:trapFocus
>
<!-- Close button -->
<button