Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ab116ab

Browse files
sibbngantfu
andauthored
feat(onClickOutside): add controls (#4537)
Co-authored-by: Anthony Fu <[email protected]> Co-authored-by: Anthony Fu <[email protected]>
1 parent bca276f commit ab116ab

File tree

4 files changed

+79
-15
lines changed

4 files changed

+79
-15
lines changed

packages/core/onClickOutside/component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { onClickOutside } from '@vueuse/core'
44
import { defineComponent, h, ref } from 'vue'
55

66
export interface OnClickOutsideProps extends RenderableComponent {
7-
options?: OnClickOutsideOptions
7+
options?: Omit<OnClickOutsideOptions, 'controls'>
88
}
99

1010
export const OnClickOutside = /* #__PURE__ */ defineComponent<OnClickOutsideProps>({

packages/core/onClickOutside/directive.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { onClickOutside } from '.'
44

55
export const vOnClickOutside: ObjectDirective<
66
HTMLElement,
7-
OnClickOutsideHandler | [(evt: any) => void, OnClickOutsideOptions]
7+
OnClickOutsideHandler | [(evt: any) => void, Omit<OnClickOutsideOptions, 'controls'>]
88
> = {
99
mounted(el, binding) {
1010
const capture = !binding.modifiers.bubble

packages/core/onClickOutside/index.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,24 @@ onClickOutside(target, event => console.log(event))
2626
</template>
2727
```
2828

29+
If you need more control over triggering the handler, you can use the `controls` option.
30+
31+
```ts
32+
const { cancel, trigger } = onClickOutside(
33+
modalRef,
34+
(event) => {
35+
modal.value = false
36+
},
37+
{ controls: true },
38+
)
39+
40+
useEventListener('pointermove', (e) => {
41+
cancel()
42+
// or
43+
trigger(e)
44+
})
45+
```
46+
2947
## Component Usage
3048

3149
```vue

packages/core/onClickOutside/index.ts

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { defaultWindow } from '../_configurable'
88
import { unrefElement } from '../unrefElement'
99
import { useEventListener } from '../useEventListener'
1010

11-
export interface OnClickOutsideOptions extends ConfigurableWindow {
11+
export interface OnClickOutsideOptions<Controls extends boolean = false> extends ConfigurableWindow {
1212
/**
1313
* List of elements that should not trigger the event.
1414
*/
@@ -23,9 +23,25 @@ export interface OnClickOutsideOptions extends ConfigurableWindow {
2323
* @default false
2424
*/
2525
detectIframe?: boolean
26+
/**
27+
* Use controls to cancel/trigger listener.
28+
* @default false
29+
*/
30+
controls?: Controls
2631
}
2732

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
2945

3046
let _iOSWorkaround = false
3147

@@ -37,15 +53,31 @@ let _iOSWorkaround = false
3753
* @param handler
3854
* @param options
3955
*/
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(
4170
target: MaybeElementRef,
42-
handler: OnClickOutsideHandler<{ detectIframe: T['detectIframe'] }>,
43-
options: T = {} as T,
71+
handler: OnClickOutsideHandler,
72+
options: OnClickOutsideOptions<boolean> = {},
4473
) {
45-
const { window = defaultWindow, ignore = [], capture = true, detectIframe = false } = options
74+
const { window = defaultWindow, ignore = [], capture = true, detectIframe = false, controls = false } = options
4675

47-
if (!window)
48-
return noop
76+
if (!window) {
77+
return controls
78+
? { stop: noop, cancel: noop, trigger: noop }
79+
: noop
80+
}
4981

5082
// Fixes: https://github.com/vueuse/vueuse/issues/1520
5183
// How it works: https://stackoverflow.com/a/39712411
@@ -59,7 +91,7 @@ export function onClickOutside<T extends OnClickOutsideOptions>(
5991

6092
let shouldListen = true
6193

62-
const shouldIgnore = (event: PointerEvent) => {
94+
const shouldIgnore = (event: Event) => {
6395
return toValue(ignore).some((target) => {
6496
if (typeof target === 'string') {
6597
return Array.from(window.document.querySelectorAll(target))
@@ -81,7 +113,7 @@ export function onClickOutside<T extends OnClickOutsideOptions>(
81113
return vm && vm.$.subTree.shapeFlag === 16
82114
}
83115

84-
function checkMultipleRoots(target: MaybeElementRef, event: PointerEvent): boolean {
116+
function checkMultipleRoots(target: MaybeElementRef, event: Event): boolean {
85117
const vm = toValue(target) as ComponentPublicInstance
86118
const children = vm.$.subTree && vm.$.subTree.children
87119

@@ -92,7 +124,7 @@ export function onClickOutside<T extends OnClickOutsideOptions>(
92124
return children.some((child: VNode) => child.el === event.target || event.composedPath().includes(child.el))
93125
}
94126

95-
const listener = (event: PointerEvent) => {
127+
const listener = (event: Event) => {
96128
const el = unrefElement(target)
97129

98130
if (event.target == null)
@@ -104,15 +136,15 @@ export function onClickOutside<T extends OnClickOutsideOptions>(
104136
if (!el || el === event.target || event.composedPath().includes(el))
105137
return
106138

107-
if (event.detail === 0)
139+
if ('detail' in event && event.detail === 0)
108140
shouldListen = !shouldIgnore(event)
109141

110142
if (!shouldListen) {
111143
shouldListen = true
112144
return
113145
}
114146

115-
handler(event)
147+
handler(event as any)
116148
}
117149

118150
let isProcessingClick = false
@@ -146,5 +178,19 @@ export function onClickOutside<T extends OnClickOutsideOptions>(
146178

147179
const stop = () => cleanup.forEach(fn => fn())
148180

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+
149195
return stop
150196
}

0 commit comments

Comments
 (0)