@@ -8,7 +8,7 @@ import { defaultWindow } from '../_configurable'
8
8
import { unrefElement } from '../unrefElement'
9
9
import { useEventListener } from '../useEventListener'
10
10
11
- export interface OnClickOutsideOptions extends ConfigurableWindow {
11
+ export interface OnClickOutsideOptions < Controls extends boolean = false > extends ConfigurableWindow {
12
12
/**
13
13
* List of elements that should not trigger the event.
14
14
*/
@@ -23,9 +23,25 @@ export interface OnClickOutsideOptions extends ConfigurableWindow {
23
23
* @default false
24
24
*/
25
25
detectIframe ?: boolean
26
+ /**
27
+ * Use controls to cancel/trigger listener.
28
+ * @default false
29
+ */
30
+ controls ?: Controls
26
31
}
27
32
28
- export type OnClickOutsideHandler < T extends { detectIframe : OnClickOutsideOptions [ 'detectIframe' ] } = { detectIframe : false } > = ( evt : T [ 'detectIframe' ] extends true ? PointerEvent | FocusEvent : PointerEvent ) => void
33
+ export type OnClickOutsideHandler <
34
+ T extends {
35
+ detectIframe : OnClickOutsideOptions [ 'detectIframe' ]
36
+ controls : boolean
37
+ } = { detectIframe : false , controls : false } ,
38
+ > = (
39
+ event : T [ 'controls' ] extends true ? Event | ( T [ 'detectIframe' ] extends true
40
+ ? PointerEvent | FocusEvent
41
+ : PointerEvent ) : T [ 'detectIframe' ] extends true
42
+ ? PointerEvent | FocusEvent
43
+ : PointerEvent ,
44
+ ) => void
29
45
30
46
let _iOSWorkaround = false
31
47
@@ -37,15 +53,31 @@ let _iOSWorkaround = false
37
53
* @param handler
38
54
* @param options
39
55
*/
40
- export function onClickOutside < T extends OnClickOutsideOptions > (
56
+ export function onClickOutside (
57
+ target : MaybeElementRef ,
58
+ handler : OnClickOutsideHandler < { detectIframe : OnClickOutsideOptions [ 'detectIframe' ] , controls : true } > ,
59
+ options : OnClickOutsideOptions < true > ,
60
+ ) : { stop : Fn , cancel : Fn , trigger : ( event : Event ) => void }
61
+
62
+ export function onClickOutside (
63
+ target : MaybeElementRef ,
64
+ handler : OnClickOutsideHandler < { detectIframe : OnClickOutsideOptions [ 'detectIframe' ] , controls : false } > ,
65
+ options ?: OnClickOutsideOptions < false > ,
66
+ ) : Fn
67
+
68
+ // Implementation
69
+ export function onClickOutside (
41
70
target : MaybeElementRef ,
42
- handler : OnClickOutsideHandler < { detectIframe : T [ 'detectIframe' ] } > ,
43
- options : T = { } as T ,
71
+ handler : OnClickOutsideHandler ,
72
+ options : OnClickOutsideOptions < boolean > = { } ,
44
73
) {
45
- const { window = defaultWindow , ignore = [ ] , capture = true , detectIframe = false } = options
74
+ const { window = defaultWindow , ignore = [ ] , capture = true , detectIframe = false , controls = false } = options
46
75
47
- if ( ! window )
48
- return noop
76
+ if ( ! window ) {
77
+ return controls
78
+ ? { stop : noop , cancel : noop , trigger : noop }
79
+ : noop
80
+ }
49
81
50
82
// Fixes: https://github.com/vueuse/vueuse/issues/1520
51
83
// How it works: https://stackoverflow.com/a/39712411
@@ -59,7 +91,7 @@ export function onClickOutside<T extends OnClickOutsideOptions>(
59
91
60
92
let shouldListen = true
61
93
62
- const shouldIgnore = ( event : PointerEvent ) => {
94
+ const shouldIgnore = ( event : Event ) => {
63
95
return toValue ( ignore ) . some ( ( target ) => {
64
96
if ( typeof target === 'string' ) {
65
97
return Array . from ( window . document . querySelectorAll ( target ) )
@@ -81,7 +113,7 @@ export function onClickOutside<T extends OnClickOutsideOptions>(
81
113
return vm && vm . $ . subTree . shapeFlag === 16
82
114
}
83
115
84
- function checkMultipleRoots ( target : MaybeElementRef , event : PointerEvent ) : boolean {
116
+ function checkMultipleRoots ( target : MaybeElementRef , event : Event ) : boolean {
85
117
const vm = toValue ( target ) as ComponentPublicInstance
86
118
const children = vm . $ . subTree && vm . $ . subTree . children
87
119
@@ -92,7 +124,7 @@ export function onClickOutside<T extends OnClickOutsideOptions>(
92
124
return children . some ( ( child : VNode ) => child . el === event . target || event . composedPath ( ) . includes ( child . el ) )
93
125
}
94
126
95
- const listener = ( event : PointerEvent ) => {
127
+ const listener = ( event : Event ) => {
96
128
const el = unrefElement ( target )
97
129
98
130
if ( event . target == null )
@@ -104,15 +136,15 @@ export function onClickOutside<T extends OnClickOutsideOptions>(
104
136
if ( ! el || el === event . target || event . composedPath ( ) . includes ( el ) )
105
137
return
106
138
107
- if ( event . detail === 0 )
139
+ if ( 'detail' in event && event . detail === 0 )
108
140
shouldListen = ! shouldIgnore ( event )
109
141
110
142
if ( ! shouldListen ) {
111
143
shouldListen = true
112
144
return
113
145
}
114
146
115
- handler ( event )
147
+ handler ( event as any )
116
148
}
117
149
118
150
let isProcessingClick = false
@@ -146,5 +178,19 @@ export function onClickOutside<T extends OnClickOutsideOptions>(
146
178
147
179
const stop = ( ) => cleanup . forEach ( fn => fn ( ) )
148
180
181
+ if ( controls ) {
182
+ return {
183
+ stop,
184
+ cancel : ( ) => {
185
+ shouldListen = false
186
+ } ,
187
+ trigger : ( event : Event ) => {
188
+ shouldListen = true
189
+ listener ( event )
190
+ shouldListen = false
191
+ } ,
192
+ }
193
+ }
194
+
149
195
return stop
150
196
}
0 commit comments