mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-12 13:55:51 -07:00
d8c5fc321a
- Preserve merged title aliases for future episodes - Fail closed when queued writes cannot drain
74 lines
2.0 KiB
TypeScript
74 lines
2.0 KiB
TypeScript
import { useEffect, useRef, type RefObject } from 'react';
|
|
|
|
const FOCUSABLE_SELECTOR = [
|
|
'button:not([disabled])',
|
|
'input:not([disabled])',
|
|
'select:not([disabled])',
|
|
'textarea:not([disabled])',
|
|
'a[href]',
|
|
'[tabindex]:not([tabindex="-1"])',
|
|
].join(',');
|
|
|
|
interface UseModalFocusOptions {
|
|
dialogRef: RefObject<HTMLElement | null>;
|
|
initialFocusRef: RefObject<HTMLElement | null>;
|
|
dismissDisabled?: boolean;
|
|
onDismiss: () => void;
|
|
}
|
|
|
|
export function useModalFocus({
|
|
dialogRef,
|
|
initialFocusRef,
|
|
dismissDisabled = false,
|
|
onDismiss,
|
|
}: UseModalFocusOptions): void {
|
|
const dismissDisabledRef = useRef(dismissDisabled);
|
|
const onDismissRef = useRef(onDismiss);
|
|
dismissDisabledRef.current = dismissDisabled;
|
|
onDismissRef.current = onDismiss;
|
|
|
|
useEffect(() => {
|
|
const previouslyFocused =
|
|
document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
initialFocusRef.current?.focus();
|
|
|
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
if (event.key === 'Escape') {
|
|
if (!dismissDisabledRef.current) {
|
|
event.preventDefault();
|
|
onDismissRef.current();
|
|
}
|
|
return;
|
|
}
|
|
if (event.key !== 'Tab') return;
|
|
|
|
const dialog = dialogRef.current;
|
|
if (!dialog) return;
|
|
const focusable = [...dialog.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)];
|
|
const first = focusable[0];
|
|
const last = focusable.at(-1);
|
|
if (!first || !last) return;
|
|
|
|
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);
|
|
return () => {
|
|
document.removeEventListener('keydown', handleKeyDown);
|
|
previouslyFocused?.focus();
|
|
};
|
|
}, [dialogRef, initialFocusRef]);
|
|
}
|