Block 6 (Rechnungen: Filter, Altersgruppen, Aktionen, Dialog "Rechnung schreiben") und Block 7 (Buchungen mit Paging, Zuordnen-Dialog, bei Rechnungen zweistufig). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
40 lines
1.6 KiB
JavaScript
40 lines
1.6 KiB
JavaScript
import { useCallback, useEffect } from 'react'
|
|
|
|
const FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
|
|
|
|
/**
|
|
* Fokus für ein Dialog-Overlay: beim Öffnen springt der Fokus in den Dialog (`dialogRef`), beim Schließen zurück zum
|
|
* Element, das ihn vorher hatte (falls es noch da ist). Der zurückgegebene onKeyDown gehört an das Overlay: Tab und
|
|
* Umschalt+Tab bleiben im Dialog (sonst landet man hinter dem Overlay in der Seite).
|
|
*/
|
|
export function useDialogFocus(dialogRef) {
|
|
useEffect(() => {
|
|
const opener = document.activeElement
|
|
dialogRef.current?.focus()
|
|
return () => {
|
|
if (opener && opener.isConnected && typeof opener.focus === 'function') opener.focus()
|
|
}
|
|
}, [dialogRef])
|
|
|
|
return useCallback((e) => {
|
|
if (e.key !== 'Tab') return
|
|
const overlay = e.currentTarget
|
|
const items = [...overlay.querySelectorAll(FOCUSABLE)].filter((el) => el.getClientRects().length > 0)
|
|
if (items.length === 0) {
|
|
e.preventDefault()
|
|
return
|
|
}
|
|
const first = items[0]
|
|
const last = items[items.length - 1]
|
|
const active = document.activeElement
|
|
const onDialogBox = active === dialogRef.current // der Dialog selbst hat den Fokus (tabIndex -1)
|
|
if (e.shiftKey && (active === first || onDialogBox || !overlay.contains(active))) {
|
|
e.preventDefault()
|
|
last.focus()
|
|
} else if (!e.shiftKey && (active === last || !overlay.contains(active))) {
|
|
e.preventDefault()
|
|
first.focus()
|
|
}
|
|
}, [dialogRef])
|
|
}
|