( ref: RefObject<HTMLElement | null>, onClose: () => void, enabled: boolean = true, )
| 9 | * Uses setTimeout(0) to avoid closing on the same click that opened the menu. |
| 10 | */ |
| 11 | export function useClickOutside( |
| 12 | ref: RefObject<HTMLElement | null>, |
| 13 | onClose: () => void, |
| 14 | enabled: boolean = true, |
| 15 | ): void { |
| 16 | useEffect(() => { |
| 17 | if (!enabled) return; |
| 18 | |
| 19 | const handleClickOutside = (e: MouseEvent) => { |
| 20 | if (shouldCloseForOutsideMouseDown(ref.current, e.target)) { |
| 21 | onClose(); |
| 22 | } |
| 23 | }; |
| 24 | const handleKeyDown = (e: KeyboardEvent) => { |
| 25 | if (shouldCloseForEscapeKey(e.key)) onClose(); |
| 26 | }; |
| 27 | |
| 28 | // Delay attachment to avoid closing on the same event that opened |
| 29 | const timer = setTimeout(() => { |
| 30 | document.addEventListener('mousedown', handleClickOutside); |
| 31 | document.addEventListener('keydown', handleKeyDown); |
| 32 | }, 0); |
| 33 | |
| 34 | return () => { |
| 35 | clearTimeout(timer); |
| 36 | document.removeEventListener('mousedown', handleClickOutside); |
| 37 | document.removeEventListener('keydown', handleKeyDown); |
| 38 | }; |
| 39 | }, [ref, onClose, enabled]); |
| 40 | } |
no outgoing calls
no test coverage detected