From b07a254d0a80887a39f972473d02e555f8518281 Mon Sep 17 00:00:00 2001 From: Ulas Kalayci Date: Sat, 25 Apr 2026 22:23:59 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20add=20focus=20trap=20to=20search=20overl?= =?UTF-8?q?ay=20=E2=80=94=20prevents=20keyboard=20focus=20leaking=20to=20h?= =?UTF-8?q?idden=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/router.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/public/router.js b/public/router.js index 6974870..f46dcd7 100644 --- a/public/router.js +++ b/public/router.js @@ -528,17 +528,41 @@ function initSearch(container) { const results = container.querySelector('#search-results'); if (!searchBtn || !overlay || !input || !results) return; + // Leichtgewichtiger Focus Trap für das Search Overlay. + // Eigenständig (kein modal.js), da modul-globale Variablen in modal.js + // bei gleichzeitig offenem Modal überschrieben würden. + let _searchTrapHandler = null; + function openSearch() { if (window._closeMoreSheet) window._closeMoreSheet(); overlay.setAttribute('aria-hidden', 'false'); overlay.classList.add('search-overlay--visible'); setTimeout(() => input.focus(), 50); if (window.lucide) window.lucide.createIcons(); + + const FOCUSABLE = 'a[href],button:not([disabled]),input,select,textarea,[tabindex]:not([tabindex="-1"])'; + _searchTrapHandler = (e) => { + if (e.key !== 'Tab') return; + const focusable = Array.from(overlay.querySelectorAll(FOCUSABLE)); + if (!focusable.length) 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(); + } + }; + overlay.addEventListener('keydown', _searchTrapHandler); } function closeSearch() { overlay.setAttribute('aria-hidden', 'true'); overlay.classList.remove('search-overlay--visible'); + if (_searchTrapHandler) { + overlay.removeEventListener('keydown', _searchTrapHandler); + _searchTrapHandler = null; + } input.value = ''; results.replaceChildren(); }