Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | 1x 1x 1x 51x 51x 51x 51x 51x 51x 51x 51x 14x 3x 3x 2x 2x 3x 1x 1x 1x 1x 14x 14x 14x 14x 14x 14x 13x 13x 13x 2x 2x 13x 14x 14x 14x 14x 14x 14x 51x 51x | import { useEffect, useRef } from 'react';
// Constants
import { SELECT_CLOSE_DELAY } from '@shared/constants';
interface UseClickOutsideProps {
wrapperRef: React.RefObject<HTMLDivElement | null>;
open: boolean;
onClose: () => void;
}
/**
* Detects clicks outside the wrapper,
* intelligently ignores Radix Select dropdowns
* and adds a small delay to avoid race conditions when switching dropdowns.
*/
export const useClickOutside = ({
wrapperRef,
open,
onClose,
}: UseClickOutsideProps) => {
const isSelectOpen = useRef(false);
const closeTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => {
if (!open || !wrapperRef.current) return;
const observer = new MutationObserver(() => {
const hasSelect = !!document.querySelector('[data-radix-select-content]');
if (hasSelect) {
if (closeTimeout.current) clearTimeout(closeTimeout.current);
isSelectOpen.current = true;
} else {
closeTimeout.current = setTimeout(() => {
isSelectOpen.current = false;
}, SELECT_CLOSE_DELAY);
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
const handleClickOutside = (event: MouseEvent) => {
const target = event.target as HTMLElement;
if (isSelectOpen.current) return;
if (wrapperRef.current?.contains(target)) return;
if (target.closest('[data-radix-select-content]')) return;
onClose();
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
observer.disconnect();
document.removeEventListener('mousedown', handleClickOutside);
if (closeTimeout.current) clearTimeout(closeTimeout.current);
};
}, [wrapperRef, open, onClose]);
};
|