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