feat: complete TASK-WP4-06 accessibility
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import { useLayoutEffect, useRef, type RefObject } from "react";
|
||||
|
||||
const focusableSelector = [
|
||||
"a[href]", "button:not([disabled])", "input:not([disabled])", "select:not([disabled])", "textarea:not([disabled])",
|
||||
'[tabindex]:not([tabindex="-1"])',
|
||||
].join(",");
|
||||
|
||||
function focusableElements(dialog: HTMLElement) {
|
||||
return [...dialog.querySelectorAll<HTMLElement>(focusableSelector)].filter((element) => !element.hidden && element.getClientRects().length > 0);
|
||||
}
|
||||
|
||||
export function useDialogFocus(onClose: () => void): RefObject<HTMLElement | null> {
|
||||
const dialogRef = useRef<HTMLElement>(null);
|
||||
const onCloseRef = useRef(onClose);
|
||||
onCloseRef.current = onClose;
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const dialog = dialogRef.current;
|
||||
if (!dialog) return undefined;
|
||||
const returnTarget = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
||||
const initial = dialog.querySelector<HTMLElement>("[data-dialog-initial-focus]") ?? focusableElements(dialog)[0] ?? dialog;
|
||||
initial.focus();
|
||||
|
||||
const handleKeyDown = (event: globalThis.KeyboardEvent) => {
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onCloseRef.current();
|
||||
return;
|
||||
}
|
||||
if (event.key !== "Tab") return;
|
||||
const focusable = focusableElements(dialog);
|
||||
if (focusable.length === 0) {
|
||||
event.preventDefault();
|
||||
dialog.focus();
|
||||
return;
|
||||
}
|
||||
const first = focusable[0]!;
|
||||
const last = focusable.at(-1)!;
|
||||
if (event.shiftKey && (document.activeElement === first || !dialog.contains(document.activeElement))) {
|
||||
event.preventDefault();
|
||||
last.focus();
|
||||
} else if (!event.shiftKey && (document.activeElement === last || !dialog.contains(document.activeElement))) {
|
||||
event.preventDefault();
|
||||
first.focus();
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", handleKeyDown, true);
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleKeyDown, true);
|
||||
if (returnTarget?.isConnected) returnTarget.focus();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return dialogRef;
|
||||
}
|
||||
Reference in New Issue
Block a user