diff --git a/apps/pwa/src/hooks/useFocusTrap.ts b/apps/pwa/src/hooks/useFocusTrap.ts index cbd5727..12fb791 100644 --- a/apps/pwa/src/hooks/useFocusTrap.ts +++ b/apps/pwa/src/hooks/useFocusTrap.ts @@ -26,7 +26,17 @@ export function useFocusTrap( dialogRef.current.querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])', ), - ).filter((el) => !el.hasAttribute('disabled') && el.getAttribute('tabindex') !== '-1'); + ).filter((el) => { + // Exclude disabled / explicitly-untabbable nodes … + if (el.hasAttribute('disabled') || el.getAttribute('tabindex') === '-1') return false; + // … and nodes that are not actually rendered/visible (WR-01). A focusable + // inside a hidden/collapsed block would otherwise become the computed + // first/last and `last.focus()` would no-op, leaking Tab to background + // content that `aria-modal="true"` promises is unreachable. + if (el.hasAttribute('hidden') || el.offsetParent === null) return false; + const r = el.getBoundingClientRect(); + return r.width > 0 && r.height > 0; + }); if (focusable.length === 0) return;