Skip to content
This repository was archived by the owner on Sep 20, 2024. It is now read-only.

Commit fc321fc

Browse files
committed
feat: create use-event-listener hook
1 parent a808a10 commit fc321fc

File tree

5 files changed

+163
-0
lines changed

5 files changed

+163
-0
lines changed

packages/utils/src/configurable.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { canUseDOM } from "@chakra-ui/utils"
2+
3+
export const defaultWindow = canUseDOM() ? window : null

packages/utils/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ export * from "./dom-query"
55
export * from "./types"
66
export * from "./timers"
77
export * from "./props"
8+
export * from "./scope"
9+
export * from "./configurable"

packages/utils/src/scope.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { getCurrentScope, onScopeDispose } from "vue"
2+
import { Fn } from "./types"
3+
4+
/**
5+
* Invokes onScopeDispose() if it's inside a effect scope lifecycle, if not, do nothing
6+
*
7+
* @param fn
8+
*/
9+
export function tryOnScopeDispose(fn: Fn) {
10+
if (getCurrentScope()) {
11+
onScopeDispose(fn)
12+
return true
13+
}
14+
return false
15+
}

packages/utils/src/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,17 @@ import { TemplateRef } from "./dom"
1010
*/
1111
export type MaybeRef<T> = T | Ref<T> | ComputedRef<T>
1212

13+
/**
14+
* May be a simple ref (nor computed ref)
15+
*/
16+
export type MaybeBaseRef<T> = T | Ref<T>
17+
1318
/** VNodeProps Object */
1419
export interface MergedVNodeProps extends VNodeProps {
1520
ref: TemplateRef | ((el: TemplateRef | null) => void)
1621
}
22+
23+
/**
24+
* Any function
25+
*/
26+
export type Fn = () => void
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/**
2+
* Much of this has ben adopted from the cgood folks at @vueuse/core
3+
*/
4+
5+
import { unref, watch } from "vue"
6+
import { isString, noop } from "@chakra-ui/utils"
7+
import {
8+
Fn,
9+
MaybeBaseRef,
10+
defaultWindow,
11+
tryOnScopeDispose,
12+
} from "@chakra-ui/vue-utils"
13+
14+
interface InferEventTarget<Events> {
15+
addEventListener(event: Events, fn?: any, options?: any): any
16+
removeEventListener(event: Events, fn?: any, options?: any): any
17+
}
18+
19+
export type WindowEventName = keyof WindowEventMap
20+
export type DocumentEventName = keyof DocumentEventMap
21+
22+
export type GeneralEventListener<E = Event> = {
23+
(evt: E): void
24+
}
25+
26+
/**
27+
* Register using addEventListener on mounted, and removeEventListener automatically on unmounted.
28+
*
29+
* Overload 1: Omitted Window target
30+
*
31+
* @param event
32+
* @param listener
33+
* @param options
34+
*/
35+
export function useEventListener<E extends keyof WindowEventMap>(
36+
event: E,
37+
listener: (this: Window, ev: WindowEventMap[E]) => any,
38+
options?: boolean | AddEventListenerOptions
39+
): Fn
40+
41+
/**
42+
* Register using addEventListener on mounted, and removeEventListener automatically on unmounted.
43+
*
44+
* Overload 2: Explicitly Window target
45+
*
46+
* @param target
47+
* @param event
48+
* @param listener
49+
* @param options
50+
*/
51+
export function useEventListener<E extends keyof WindowEventMap>(
52+
target: Window,
53+
event: E,
54+
listener: (this: Window, ev: WindowEventMap[E]) => any,
55+
options?: boolean | AddEventListenerOptions
56+
): Fn
57+
58+
/**
59+
* Register using addEventListener on mounted, and removeEventListener automatically on unmounted.
60+
*
61+
* Overload 4: Custom event target with event type infer
62+
*
63+
* @param target
64+
* @param event
65+
* @param listener
66+
* @param options
67+
*/
68+
export function useEventListener<Names extends string, EventType = Event>(
69+
target: InferEventTarget<Names>,
70+
event: Names,
71+
listener: GeneralEventListener<EventType>,
72+
options?: boolean | AddEventListenerOptions
73+
): Fn
74+
75+
/**
76+
* Register using addEventListener on mounted, and removeEventListener automatically on unmounted.
77+
*
78+
* Overload 5: Custom event target fallback
79+
*
80+
* @param target
81+
* @param event
82+
* @param listener
83+
* @param options
84+
*/
85+
export function useEventListener<EventType = Event>(
86+
target: MaybeBaseRef<EventTarget | null | undefined>,
87+
event: string,
88+
listener: GeneralEventListener<EventType>,
89+
options?: boolean | AddEventListenerOptions
90+
): Fn
91+
92+
export function useEventListener(...args: any[]) {
93+
let target: MaybeBaseRef<EventTarget> | undefined | null
94+
let event: string
95+
let listener: any
96+
let options: any
97+
98+
if (isString(args[0])) {
99+
;[event, listener, options] = args
100+
target = defaultWindow
101+
} else {
102+
;[target, event, listener, options] = args
103+
}
104+
105+
if (!target) return noop
106+
107+
let cleanup = noop
108+
109+
const stopWatch = watch(
110+
() => unref(target),
111+
(el) => {
112+
cleanup()
113+
if (!el) return
114+
115+
el.addEventListener(event, listener, options)
116+
117+
cleanup = () => {
118+
el.removeEventListener(event, listener, options)
119+
cleanup = noop
120+
}
121+
},
122+
{ immediate: true, flush: "post" }
123+
)
124+
125+
const stop = () => {
126+
stopWatch()
127+
cleanup()
128+
}
129+
130+
tryOnScopeDispose(stop)
131+
132+
return stop
133+
}

0 commit comments

Comments
 (0)