diff --git a/index.html b/index.html index 06cff1c..b82f418 100644 --- a/index.html +++ b/index.html @@ -993,6 +993,16 @@ Sovereignty and service always.`; function trapFocusInOverlay(e) { if (!crisisOverlay.classList.contains('active')) return; + + // Escape: dismiss overlay (only when dismiss button is enabled after countdown) + if (e.key === 'Escape') { + e.preventDefault(); + if (!overlayDismissBtn.disabled) { + overlayDismissBtn.click(); + } + return; + } + if (e.key !== 'Tab') return; var focusable = getOverlayFocusableElements(); @@ -1001,6 +1011,13 @@ Sovereignty and service always.`; var first = focusable[0]; var last = focusable[focusable.length - 1]; + // If focus escaped outside the overlay, bring it back + if (!crisisOverlay.contains(document.activeElement)) { + e.preventDefault(); + first.focus(); + return; + } + if (e.shiftKey) { // Shift+Tab: if on first, wrap to last if (document.activeElement === first) { @@ -1050,7 +1067,11 @@ Sovereignty and service always.`; } }, 1000); - overlayDismissBtn.focus(); + // Focus the Call 988 link (always enabled) — not the disabled dismiss button + var callLink = crisisOverlay.querySelector('a.overlay-call'); + if (callLink) { + callLink.focus(); + } } // Register focus trap on document (always listening, gated by class check) diff --git a/tests/test_crisis_overlay_focus_trap.py b/tests/test_crisis_overlay_focus_trap.py index f657afc..5daf224 100644 --- a/tests/test_crisis_overlay_focus_trap.py +++ b/tests/test_crisis_overlay_focus_trap.py @@ -52,6 +52,38 @@ class TestCrisisOverlayFocusTrap(unittest.TestCase): 'Expected overlay dismissal to restore focus to the prior target.', ) + def test_overlay_initial_focus_targets_enabled_element(self): + """Issue #69: overlay must not focus the disabled dismiss button on open.""" + # The showOverlay function should NOT call overlayDismissBtn.focus() + # while the button is disabled. Instead it should focus an enabled element. + self.assertNotRegex( + self.html, + r"overlayDismissBtn\.disabled\s*=\s*true;.*overlayDismissBtn\.focus\(\)", + 'showOverlay must not focus the dismiss button while it is disabled (issue #69).', + ) + # Verify focus goes to the Call 988 link (always enabled) + self.assertIn( + "querySelector('a.overlay-call')", + self.html, + 'Expected showOverlay to focus the Call 988 link on open.', + ) + + def test_overlay_escape_key_dismisses(self): + """Issue #69/95: Escape key should dismiss the overlay when countdown completes.""" + self.assertRegex( + self.html, + r"e\.key\s*===\s*['\"]Escape['\"]", + 'Expected Escape key handler in overlay focus trap.', + ) + + def test_overlay_focus_recovery_when_focus_escapes(self): + """Focus trap should recover focus if it escapes the overlay.""" + self.assertRegex( + self.html, + r"crisisOverlay\.contains\(document\.activeElement\)", + 'Focus trap should check if focus is still within the overlay.', + ) + if __name__ == '__main__': unittest.main()