83 lines
2.3 KiB
TypeScript
83 lines
2.3 KiB
TypeScript
"use client";
|
|
|
|
import type { RefObject } from "react";
|
|
import { useEffect, useRef } from "react";
|
|
|
|
/**
|
|
* Escape-to-close, body scroll lock, focus move-in and tab trap for Create-shell modals.
|
|
*/
|
|
export function useCreateModalA11y(
|
|
isOpen: boolean,
|
|
onClose: () => void,
|
|
dialogRef: RefObject<HTMLDivElement | null>,
|
|
): void {
|
|
const previousActiveElementRef = useRef<HTMLElement | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (!isOpen) return;
|
|
|
|
const handleEscape = (e: KeyboardEvent) => {
|
|
if (e.key === "Escape") {
|
|
onClose();
|
|
}
|
|
};
|
|
|
|
document.addEventListener("keydown", handleEscape);
|
|
return () => {
|
|
document.removeEventListener("keydown", handleEscape);
|
|
};
|
|
}, [isOpen, onClose]);
|
|
|
|
useEffect(() => {
|
|
if (!isOpen) return;
|
|
|
|
previousActiveElementRef.current = document.activeElement as HTMLElement;
|
|
document.body.style.overflow = "hidden";
|
|
|
|
if (dialogRef.current) {
|
|
const focusableElements = dialogRef.current.querySelectorAll(
|
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
|
|
);
|
|
const firstElement = focusableElements[0] as HTMLElement;
|
|
if (firstElement) {
|
|
firstElement.focus();
|
|
} else {
|
|
dialogRef.current.setAttribute("tabindex", "-1");
|
|
dialogRef.current.focus();
|
|
}
|
|
}
|
|
|
|
const handleTab = (e: KeyboardEvent) => {
|
|
if (e.key !== "Tab" || !dialogRef.current) return;
|
|
|
|
const focusableElements = dialogRef.current.querySelectorAll(
|
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
|
|
);
|
|
const firstElement = focusableElements[0] as HTMLElement;
|
|
const lastElement = focusableElements[
|
|
focusableElements.length - 1
|
|
] as HTMLElement;
|
|
|
|
if (e.shiftKey) {
|
|
if (document.activeElement === firstElement) {
|
|
e.preventDefault();
|
|
lastElement?.focus();
|
|
}
|
|
} else {
|
|
if (document.activeElement === lastElement) {
|
|
e.preventDefault();
|
|
firstElement?.focus();
|
|
}
|
|
}
|
|
};
|
|
|
|
document.addEventListener("keydown", handleTab);
|
|
|
|
return () => {
|
|
document.body.style.overflow = "";
|
|
document.removeEventListener("keydown", handleTab);
|
|
previousActiveElementRef.current?.focus();
|
|
};
|
|
}, [dialogRef, isOpen]);
|
|
}
|