forked from vueuse/vueuse
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.ts
102 lines (86 loc) 路 2.87 KB
/
index.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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import type { Fn } from '@vueuse/shared'
import type { MaybeElementRef } from '../unrefElement'
import { unrefElement } from '../unrefElement'
import { useEventListener } from '../useEventListener'
import type { ConfigurableWindow } from '../_configurable'
import { defaultWindow } from '../_configurable'
export interface OnClickOutsideOptions extends ConfigurableWindow {
/**
* List of elements that should not trigger the event.
*/
ignore?: MaybeElementRef[]
/**
* Use capturing phase for internal event listener.
* @default true
*/
capture?: boolean
/**
* Run handler function if focus moves to an iframe.
* @default false
*/
detectIframe?: boolean
}
export type OnClickOutsideHandler<T extends { detectIframe: OnClickOutsideOptions['detectIframe'] } = { detectIframe: false }> = (evt: T['detectIframe'] extends true ? PointerEvent | FocusEvent : PointerEvent) => void
/**
* Listen for clicks outside of an element.
*
* @see https://vueuse.org/onClickOutside
* @param target
* @param handler
* @param options
*/
export function onClickOutside<T extends OnClickOutsideOptions>(
target: MaybeElementRef,
handler: OnClickOutsideHandler<{ detectIframe: T['detectIframe'] }>,
options: T = {} as T,
) {
const { window = defaultWindow, ignore = [], capture = true, detectIframe = false } = options
if (!window)
return
let shouldListen = true
let fallback: number
const shouldIgnore = (event: PointerEvent) => {
return ignore.some((target) => {
const el = unrefElement(target)
return el && (event.target === el || event.composedPath().includes(el))
})
}
const listener = (event: PointerEvent) => {
window.clearTimeout(fallback)
const el = unrefElement(target)
if (!el || el === event.target || event.composedPath().includes(el))
return
if (event.detail === 0)
shouldListen = !shouldIgnore(event)
if (!shouldListen) {
shouldListen = true
return
}
handler(event)
}
const cleanup = [
useEventListener(window, 'click', listener, { passive: true, capture }),
useEventListener(window, 'pointerdown', (e) => {
const el = unrefElement(target)
if (el)
shouldListen = !e.composedPath().includes(el) && !shouldIgnore(e)
}, { passive: true }),
useEventListener(window, 'pointerup', (e) => {
if (e.button === 0) {
const path = e.composedPath()
e.composedPath = () => path
fallback = window.setTimeout(() => listener(e), 50)
}
}, { passive: true }),
detectIframe && useEventListener(window, 'blur', (event) => {
const el = unrefElement(target)
if (
window.document.activeElement?.tagName === 'IFRAME'
&& !el?.contains(window.document.activeElement)
)
handler(event as any)
}),
].filter(Boolean) as Fn[]
const stop = () => cleanup.forEach(fn => fn())
return stop
}