From 542b3eb6b3934aac45bd209b76c3ccce1bcba108 Mon Sep 17 00:00:00 2001 From: lashman Date: Thu, 25 Dec 2025 19:42:41 +0200 Subject: [PATCH] focus trap hook --- frontend/src/hooks/useFocusTrap.ts | 38 ++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 frontend/src/hooks/useFocusTrap.ts diff --git a/frontend/src/hooks/useFocusTrap.ts b/frontend/src/hooks/useFocusTrap.ts new file mode 100644 index 0000000..812f8ce --- /dev/null +++ b/frontend/src/hooks/useFocusTrap.ts @@ -0,0 +1,38 @@ +import { useEffect, useRef } from 'react' + +export function useFocusTrap(active: boolean) { + const ref = useRef(null) + const previousFocus = useRef(null) + + useEffect(() => { + if (!active || !ref.current) return + const activeElement = document.activeElement + previousFocus.current = activeElement instanceof HTMLElement ? activeElement : null + + const container = ref.current + const sel = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])' + const initial = container.querySelectorAll(sel) + if (initial.length > 0) initial[0].focus() + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key !== 'Tab') return + const focusable = container.querySelectorAll(sel) + 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() + } + } + + container.addEventListener('keydown', handleKeyDown) + return () => { + container.removeEventListener('keydown', handleKeyDown) + previousFocus.current?.focus() + } + }, [active]) + + return ref +}