Mirror: React hooks for accessible, common web interactions. UI super powers without the UI.

Rename useDialogDismiss to useDismissable and add focusLoss option

Changed files
+25 -3
src
+25 -3
src/useDialogDismiss.ts src/useDismissable.ts
···
const usePriority = makePriorityHook();
-
export function useDialogDismiss<T extends HTMLElement>(
+
export interface DismissableOptions {
+
focusLoss?: boolean;
+
}
+
+
export function useDismissable<T extends HTMLElement>(
ref: Ref<T>,
-
onDismiss: () => void
+
onDismiss: () => void,
+
options?: DismissableOptions
) {
+
const focusLoss = !!(options && options.focusLoss);
const hasPriority = usePriority(ref);
useLayoutEffect(() => {
if (!ref.current || !hasPriority) return;
+
function onFocusOut(event: FocusEvent) {
+
if (event.defaultPrevented) return;
+
+
const { target, relatedTarget } = event;
+
if (
+
contains(ref.current, target) &&
+
!contains(ref.current, relatedTarget)
+
) {
+
onDismiss();
+
}
+
}
+
function onKey(event: KeyboardEvent) {
if (!event.isComposing && event.code === 'Escape') {
// The current dialog can be dismissed by pressing escape if it either has focus
···
}
}
+
if (focusLoss) document.body.addEventListener('focusout', onFocusOut);
+
document.addEventListener('mousedown', onClick);
document.addEventListener('touchstart', onClick);
document.addEventListener('keydown', onKey);
return () => {
+
if (focusLoss) document.body.removeEventListener('focusout', onFocusOut);
+
document.removeEventListener('mousedown', onClick);
document.removeEventListener('touchstart', onClick);
document.removeEventListener('keydown', onKey);
};
-
}, [ref, hasPriority, onDismiss]);
+
}, [ref, hasPriority, focusLoss, onDismiss]);
}