/
use-outside-click.ts
56 lines (45 loc) · 1.65 KB
/
use-outside-click.ts
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
import { MutableRefObject, useEffect, useRef } from 'react'
import { useWindowEvent } from './use-window-event'
type Container = MutableRefObject<HTMLElement | null> | HTMLElement | null
type ContainerCollection = Container[] | Set<Container>
type ContainerInput = Container | ContainerCollection
export function useOutsideClick(
containers: ContainerInput | (() => ContainerInput),
cb: (event: MouseEvent | PointerEvent, target: HTMLElement) => void,
enabled: boolean = true
) {
// TODO: remove this once the React bug has been fixed: https://github.com/facebook/react/issues/24657
let enabledRef = useRef(false)
useEffect(() => {
requestAnimationFrame(() => {
enabledRef.current = enabled
})
}, [enabled])
useWindowEvent('click', (event) => {
if (!enabledRef.current) return
let _containers = (function resolve(containers): ContainerCollection {
if (typeof containers === 'function') {
return resolve(containers())
}
if (Array.isArray(containers)) {
return containers
}
if (containers instanceof Set) {
return containers
}
return [containers]
})(containers)
let target = event.target as HTMLElement
// Ignore if the target doesn't exist in the DOM anymore
if (!target.ownerDocument.documentElement.contains(target)) return
// Ignore if the target exists in one of the containers
for (let container of _containers) {
if (container === null) continue
let domNode = container instanceof HTMLElement ? container : container.current
if (domNode?.contains(target)) {
return
}
}
return cb(event, target)
})
}