From 5722494458e5f67bc03d39d3a144add368fa54f9 Mon Sep 17 00:00:00 2001 From: robonen Date: Mon, 21 Oct 2024 06:49:57 +0700 Subject: [PATCH] feat(packages/vue): init useEventListener composable --- .../src/composables/useEventListener/index.ts | 136 ++++++++++++++++++ packages/vue/src/types/window.ts | 3 + 2 files changed, 139 insertions(+) create mode 100644 packages/vue/src/composables/useEventListener/index.ts create mode 100644 packages/vue/src/types/window.ts diff --git a/packages/vue/src/composables/useEventListener/index.ts b/packages/vue/src/composables/useEventListener/index.ts new file mode 100644 index 0000000..62e779f --- /dev/null +++ b/packages/vue/src/composables/useEventListener/index.ts @@ -0,0 +1,136 @@ +import { isArray, isString, noop, type Arrayable, type VoidFunction } from '@robonen/stdlib'; +import type { MaybeRefOrGetter } from 'vue'; +import { defaultWindow } from '../..'; + +// TODO: wip + +interface InferEventTarget { + addEventListener: (event: Events, listener?: any, options?: any) => any; + removeEventListener: (event: Events, listener?: any, options?: any) => any; +} + +export interface GeneralEventListener { + (evt: E): void; +} + +export type WindowEventName = keyof WindowEventMap; +export type DocumentEventName = keyof DocumentEventMap; +export type ElementEventName = keyof HTMLElementEventMap; + +/** + * @name useEventListener + * @category Elements + * @description Registers an event listener using the `addEventListener` on mounted and removes it automatically on unmounted + * + * Overload 1: Omitted window target + */ +export function useEventListener( + event: Arrayable, + listener: Arrayable<(this: Window, ev: WindowEventMap[E]) => any>, + options?: MaybeRefOrGetter +): VoidFunction; + +/** + * @name useEventListener + * @category Elements + * @description Registers an event listener using the `addEventListener` on mounted and removes it automatically on unmounted + * + * Overload 2: Explicit window target + */ +export function useEventListener( + target: Window, + event: Arrayable, + listener: Arrayable<(this: Window, ev: WindowEventMap[E]) => any>, + options?: MaybeRefOrGetter +): VoidFunction; + +/** + * @name useEventListener + * @category Elements + * @description Registers an event listener using the `addEventListener` on mounted and removes it automatically on unmounted + * + * Overload 3: Explicit document target + */ +export function useEventListener( + target: Document, + event: Arrayable, + listener: Arrayable<(this: Document, ev: DocumentEventMap[E]) => any>, + options?: MaybeRefOrGetter +): VoidFunction; + +/** + * @name useEventListener + * @category Elements + * @description Registers an event listener using the `addEventListener` on mounted and removes it automatically on unmounted + * + * Overload 4: Explicit HTMLElement target + */ +export function useEventListener( + target: MaybeRefOrGetter, + event: Arrayable, + listener: Arrayable<(this: HTMLElement, ev: HTMLElementEventMap[E]) => any>, + options?: MaybeRefOrGetter +): VoidFunction; + +/** + * @name useEventListener + * @category Elements + * @description Registers an event listener using the `addEventListener` on mounted and removes it automatically on unmounted + * + * Overload 5: Custom target with inferred event type + */ +export function useEventListener( + target: MaybeRefOrGetter | null | undefined>, + event: Arrayable, + listener: Arrayable>, + options?: MaybeRefOrGetter +) + +/** + * @name useEventListener + * @category Elements + * @description Registers an event listener using the `addEventListener` on mounted and removes it automatically on unmounted + * + * Overload 6: Custom event target fallback + */ +export function useEventListener( + target: MaybeRefOrGetter, + event: Arrayable, + listener: Arrayable>, + options?: MaybeRefOrGetter +): VoidFunction; + +export function useEventListener(...args: any[]) { + let target: MaybeRefOrGetter | undefined; + let events: Arrayable; + let listeners: Arrayable; + let options: MaybeRefOrGetter | undefined; + + if (isString(args[0]) || isArray(args[0])) { + [events, listeners, options] = args; + target = defaultWindow; + } else { + [target, events, listeners, options] = args; + } + + if (!target) + return noop; + + if (!isArray(events)) + events = [events]; + + if (!isArray(listeners)) + listeners = [listeners]; + + const cleanups: Function[] = []; + + const cleanup = () => { + cleanups.forEach(fn => fn()); + cleanups.length = 0; + } + + const register = (el: any, event: string, listener: any, options: any) => { + el.addEventListener(event, listener, options); + return () => el.removeEventListener(event, listener, options); + } +} \ No newline at end of file diff --git a/packages/vue/src/types/window.ts b/packages/vue/src/types/window.ts new file mode 100644 index 0000000..afa5061 --- /dev/null +++ b/packages/vue/src/types/window.ts @@ -0,0 +1,3 @@ +import { isClient } from '@robonen/platform'; + +export const defaultWindow = /* #__PURE__ */ isClient ? window : undefined \ No newline at end of file