import { useEffect, useRef } from "react";
/**
* Closes a popover when the user clicks or tabs outside of it, or presses Escape. Attach the returned
* ref to the element that wraps both the trigger and the popover so clicking the trigger still toggles it.
*/
export function useDismiss<T extends HTMLElement>(open: boolean, onDismiss: () => void) {
const ref = useRef<T>(null);
const dismiss = useRef(onDismiss);
dismiss.current = onDismiss;
useEffect(() => {
if (!open) return;
const outside = (target: EventTarget | null) => !(target instanceof Node && ref.current?.contains(target));
const onPointer = (event: PointerEvent) => {
if (outside(event.target)) dismiss.current();
};
const onFocus = (event: FocusEvent) => {
if (outside(event.target)) dismiss.current();
};
const onKey = (event: KeyboardEvent) => {
if (event.key !== "Escape") return;
dismiss.current();
const trigger = ref.current?.querySelector<HTMLElement>("[aria-expanded]");
trigger?.focus();
};
document.addEventListener("pointerdown", onPointer);
document.addEventListener("focusin", onFocus);
document.addEventListener("keydown", onKey);
return () => {
document.removeEventListener("pointerdown", onPointer);
document.removeEventListener("focusin", onFocus);
document.removeEventListener("keydown", onKey);
};
}, [open]);
return ref;
}