diff --git a/packages/shared-auth-ui/src/components/AuthGateModal.svelte b/packages/shared-auth-ui/src/components/AuthGateModal.svelte index 845f92743..d6855cf23 100644 --- a/packages/shared-auth-ui/src/components/AuthGateModal.svelte +++ b/packages/shared-auth-ui/src/components/AuthGateModal.svelte @@ -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; + // 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); + }, + }; + } @@ -175,6 +214,7 @@ aria-modal="true" aria-labelledby="auth-gate-title" onclick={(e) => e.stopPropagation()} + use:trapFocus > @@ -250,6 +251,7 @@ class="pm-btn pm-btn-danger" onclick={executeDelete} disabled={loading} + aria-disabled={loading} > {#if loading} {t.cancelButton} @@ -295,6 +298,7 @@ class="pm-btn pm-btn-primary" onclick={() => saveRename(passkey.id)} disabled={loading || !editName.trim()} + aria-disabled={loading || !editName.trim()} > {t.saveButton} @@ -363,6 +367,7 @@ class="pm-btn pm-btn-cancel" onclick={cancelRegister} disabled={loading} + aria-disabled={loading} > {t.cancelButton} @@ -371,6 +376,7 @@ class="pm-btn pm-btn-primary" onclick={handleRegister} disabled={loading} + aria-disabled={loading} > {#if loading} handleRevoke(session.id)} disabled={revoking === session.id || revokingAll} + aria-disabled={revoking === session.id || revokingAll} > {#if revoking === session.id} @@ -307,6 +309,7 @@ class="revoke-all-button" onclick={handleRevokeAll} disabled={revokingAll} + aria-disabled={revokingAll} > {#if revokingAll} diff --git a/packages/shared-auth-ui/src/pages/ForgotPasswordPage.svelte b/packages/shared-auth-ui/src/pages/ForgotPasswordPage.svelte index 38b0cea5c..8a08f780f 100644 --- a/packages/shared-auth-ui/src/pages/ForgotPasswordPage.svelte +++ b/packages/shared-auth-ui/src/pages/ForgotPasswordPage.svelte @@ -250,6 +250,7 @@