/** * useOutsideClick Hook * * Detects clicks outside specified elements to clear selection. * Used in constructor.tsx to deselect elements when clicking outside. */ import { useEffect, useCallback, RefObject } from 'react'; interface UseOutsideClickOptions { /** Ref to the element whose outside clicks should be detected */ containerRef: RefObject; /** Additional refs to ignore (clicking these won't trigger onOutsideClick) */ ignoreRefs?: RefObject[]; /** Data attribute to check on clicked elements (if present, won't trigger) */ ignoreDataAttribute?: string; /** Current selected value to check (e.g., element ID) */ selectedValue?: string; /** Callback when click outside is detected */ onOutsideClick: () => void; /** Whether the hook is active */ enabled?: boolean; } /** * Hook to detect clicks outside a container element. * Useful for closing panels, deselecting elements, etc. * * @example * useOutsideClick({ * containerRef: panelRef, * ignoreRefs: [buttonRef], * onOutsideClick: () => setSelectedId(''), * enabled: !!selectedId, * }); */ export function useOutsideClick({ containerRef, ignoreRefs = [], ignoreDataAttribute, selectedValue, onOutsideClick, enabled = true, }: UseOutsideClickOptions): void { const handleMouseDown = useCallback( (event: MouseEvent) => { const target = event.target as HTMLElement | null; if (!target) return; // Check if click is inside the container if (containerRef.current?.contains(target)) return; // Check if click is inside any ignored refs for (const ref of ignoreRefs) { if (ref.current?.contains(target)) return; } // Check for data attribute on clicked element or ancestors if (ignoreDataAttribute) { const clickedElement = target.closest(`[${ignoreDataAttribute}]`); if (clickedElement) { const attributeValue = clickedElement.getAttribute(ignoreDataAttribute); // If selected value matches clicked element's attribute, don't trigger if (selectedValue && attributeValue === selectedValue) return; } } onOutsideClick(); }, [ containerRef, ignoreRefs, ignoreDataAttribute, selectedValue, onOutsideClick, ], ); useEffect(() => { if (!enabled) return; window.addEventListener('mousedown', handleMouseDown); return () => window.removeEventListener('mousedown', handleMouseDown); }, [enabled, handleMouseDown]); } export default useOutsideClick;