From e6674d4efd5f1dbe036196d4a0ab34c64af2c8bc Mon Sep 17 00:00:00 2001 From: vaakian Date: Thu, 8 Sep 2022 18:07:32 +0800 Subject: [PATCH 1/5] feat: accept array of `event`s or `listener`s --- packages/core/useEventListener/index.test.ts | 131 +++++++++++++++++++ packages/core/useEventListener/index.ts | 50 ++++--- packages/shared/utils/types.ts | 2 + 3 files changed, 165 insertions(+), 18 deletions(-) create mode 100644 packages/core/useEventListener/index.test.ts diff --git a/packages/core/useEventListener/index.test.ts b/packages/core/useEventListener/index.test.ts new file mode 100644 index 00000000000..f434cbebf19 --- /dev/null +++ b/packages/core/useEventListener/index.test.ts @@ -0,0 +1,131 @@ +import type { Fn } from '@vueuse/shared' +import type { Mock, SpyInstance } from 'vitest' +import { useEventListener } from '.' + +describe('useEventListener', () => { + describe('given both none array', () => { + let target: HTMLDivElement + let stop: Fn + let listener: Mock + let remove: SpyInstance + const event = 'click' + + beforeEach(() => { + target = document.createElement('div') + listener = vi.fn() + stop = useEventListener(target, event, listener) + remove = vitest.spyOn(target, 'removeEventListener') + }) + + it('should trigger listener', () => { + expect(listener).not.toBeCalled() + target.dispatchEvent(new MouseEvent(event)) + expect(listener).toBeCalledTimes(1) + }) + + it('should remove listener', () => { + expect(remove).not.toBeCalled() + stop() + expect(remove).toBeCalledTimes(1) + }) + }) + + describe('given array of events but single listener', () => { + let target: HTMLDivElement + let stop: Fn + let listener: Mock + let remove: SpyInstance + const events = ['click', 'scroll', 'blur', 'resize'] + const options = { capture: false } + + beforeEach(() => { + target = document.createElement('div') + listener = vi.fn() + stop = useEventListener(target, events, listener, options) + remove = vitest.spyOn(target, 'removeEventListener') + }) + + it('should trigger listener with all events', () => { + expect(listener).not.toBeCalled() + events.forEach((event, index) => { + target.dispatchEvent(new Event(event)) + expect(listener).toBeCalledTimes(index + 1) + }) + }) + + it('should remove listener with all events', () => { + expect(remove).not.toBeCalled() + + stop() + + expect(remove).toBeCalledTimes(events.length) + events.forEach(event => expect(remove).toBeCalledWith(event, listener, options)) + }) + }) + + describe('given single event but array of listeners', () => { + let target: HTMLDivElement + let stop: Fn + let listeners: Mock[] + let remove: SpyInstance + const event = 'click' + const options = { capture: true } + + beforeEach(() => { + target = document.createElement('div') + listeners = [vi.fn(), vi.fn(), vi.fn()] + stop = useEventListener(target, event, listeners, options) + remove = vitest.spyOn(target, 'removeEventListener') + }) + + it('should call all listeners with single click event', () => { + listeners.forEach(listener => expect(listener).not.toBeCalled()) + + target.dispatchEvent(new Event(event)) + + listeners.forEach(listener => expect(listener).toBeCalledTimes(1)) + }) + + it('should remove listeners', () => { + expect(remove).not.toBeCalled() + + stop() + + expect(remove).toBeCalledTimes(listeners.length) + listeners.forEach(listener => expect(remove).toHaveBeenCalledWith(event, listener, options)) + }) + }) + + describe('given both array of events and listeners', () => { + let target: HTMLDivElement + let stop: Fn + let listeners: Mock[] + let remove: SpyInstance + const events = ['click', 'scroll', 'blur', 'resize'] + const options = { capture: true } + + beforeEach(() => { + target = document.createElement('div') + listeners = [vi.fn(), vi.fn(), vi.fn()] + stop = useEventListener(target, events, listeners, options) + remove = vitest.spyOn(target, 'removeEventListener') + }) + + it('should call all listeners with all events', () => { + events.forEach((event, index) => { + target.dispatchEvent(new Event(event)) + listeners.forEach(listener => expect(listener).toBeCalledTimes(index + 1)) + }) + }) + + it('should remove all listeners with all events', () => { + stop() + + listeners.forEach((listener) => { + events.forEach((event) => { + expect(remove).toBeCalledWith(event, listener, options) + }) + }) + }) + }) +}) diff --git a/packages/core/useEventListener/index.ts b/packages/core/useEventListener/index.ts index a321ae7334e..1f35bbd593d 100644 --- a/packages/core/useEventListener/index.ts +++ b/packages/core/useEventListener/index.ts @@ -1,4 +1,4 @@ -import type { Fn, MaybeComputedRef } from '@vueuse/shared' +import type { Fn, MaybeArray, MaybeComputedRef } from '@vueuse/shared' import { isString, noop, tryOnScopeDispose } from '@vueuse/shared' import { watch } from 'vue-demi' import type { MaybeElementRef } from '../unrefElement' @@ -28,8 +28,8 @@ export interface GeneralEventListener { * @param options */ export function useEventListener( - event: E, - listener: (this: Window, ev: WindowEventMap[E]) => any, + event: MaybeArray, + listener: MaybeArray<(this: Window, ev: WindowEventMap[E]) => any>, options?: boolean | AddEventListenerOptions ): Fn @@ -46,8 +46,8 @@ export function useEventListener( */ export function useEventListener( target: Window, - event: E, - listener: (this: Window, ev: WindowEventMap[E]) => any, + event: MaybeArray, + listener: MaybeArray<(this: Window, ev: WindowEventMap[E]) => any>, options?: boolean | AddEventListenerOptions ): Fn @@ -64,8 +64,8 @@ export function useEventListener( */ export function useEventListener( target: Document, - event: E, - listener: (this: Document, ev: DocumentEventMap[E]) => any, + event: MaybeArray, + listener: MaybeArray<(this: Document, ev: DocumentEventMap[E]) => any>, options?: boolean | AddEventListenerOptions ): Fn @@ -82,8 +82,8 @@ export function useEventListener( */ export function useEventListener( target: InferEventTarget, - event: Names, - listener: GeneralEventListener, + event: MaybeArray, + listener: MaybeArray>, options?: boolean | AddEventListenerOptions ): Fn @@ -100,30 +100,42 @@ export function useEventListener( */ export function useEventListener( target: MaybeComputedRef, - event: string, - listener: GeneralEventListener, + event: MaybeArray, + listener: MaybeArray>, options?: boolean | AddEventListenerOptions ): Fn export function useEventListener(...args: any[]) { let target: MaybeComputedRef | undefined - let event: string - let listener: any + let events: MaybeArray + let listeners: MaybeArray let options: any - if (isString(args[0])) { - [event, listener, options] = args + if (isString(args[0]) || Array.isArray(args[0])) { + [events, listeners, options] = args target = defaultWindow } else { - [target, event, listener, options] = args + [target, events, listeners, options] = args } if (!target) return noop + if (!Array.isArray(events)) + events = [events] + if (!Array.isArray(listeners)) + listeners = [listeners] + let cleanup = noop + const register = (el: any, event: string, listener: any) => { + el.addEventListener(event, listener, options) + return () => { + el.removeEventListener(event, listener, options) + } + } + const stopWatch = watch( () => unrefElement(target as unknown as MaybeElementRef), (el) => { @@ -131,10 +143,12 @@ export function useEventListener(...args: any[]) { if (!el) return - el.addEventListener(event, listener, options) + const cleanups = (events as string[]).map((event) => { + return (listeners as []).map(listener => register(el, event, listener)) + }).flat() cleanup = () => { - el.removeEventListener(event, listener, options) + cleanups.forEach(unregister => unregister()) cleanup = noop } }, diff --git a/packages/shared/utils/types.ts b/packages/shared/utils/types.ts index ddac273e445..f5592d3c03d 100644 --- a/packages/shared/utils/types.ts +++ b/packages/shared/utils/types.ts @@ -60,6 +60,8 @@ export type DeepMaybeRef = T extends Ref ? { [K in keyof T]: DeepMaybeRef } : MaybeRef +export type MaybeArray = T[] | T + /** * Infers the element type of an array */ From 96bfcd2a54227b32d1c422c98689c0f9a2771bc8 Mon Sep 17 00:00:00 2001 From: vaakian Date: Thu, 8 Sep 2022 19:28:31 +0800 Subject: [PATCH 2/5] fix: add test cases --- packages/core/useEventListener/index.test.ts | 86 ++++++++++++-------- 1 file changed, 52 insertions(+), 34 deletions(-) diff --git a/packages/core/useEventListener/index.test.ts b/packages/core/useEventListener/index.test.ts index f434cbebf19..8a0042959bc 100644 --- a/packages/core/useEventListener/index.test.ts +++ b/packages/core/useEventListener/index.test.ts @@ -1,20 +1,34 @@ import type { Fn } from '@vueuse/shared' -import type { Mock, SpyInstance } from 'vitest' +import type { SpyInstance } from 'vitest' import { useEventListener } from '.' describe('useEventListener', () => { + let target: HTMLDivElement + let removeSpy: SpyInstance + let addSpy: SpyInstance + + beforeEach(() => { + target = document.createElement('div') + removeSpy = vitest.spyOn(target, 'removeEventListener') + addSpy = vitest.spyOn(target, 'addEventListener') + }) + + it('should be defined', () => { + expect(useEventListener).toBeDefined() + }) + describe('given both none array', () => { - let target: HTMLDivElement let stop: Fn - let listener: Mock - let remove: SpyInstance + const listener = vitest.fn() const event = 'click' beforeEach(() => { - target = document.createElement('div') - listener = vi.fn() + listener.mockReset() stop = useEventListener(target, event, listener) - remove = vitest.spyOn(target, 'removeEventListener') + }) + + it('should add listener', () => { + expect(addSpy).toBeCalledTimes(1) }) it('should trigger listener', () => { @@ -24,25 +38,25 @@ describe('useEventListener', () => { }) it('should remove listener', () => { - expect(remove).not.toBeCalled() + expect(removeSpy).not.toBeCalled() stop() - expect(remove).toBeCalledTimes(1) + expect(removeSpy).toBeCalledTimes(1) }) }) describe('given array of events but single listener', () => { - let target: HTMLDivElement let stop: Fn - let listener: Mock - let remove: SpyInstance + const listener = vitest.fn() const events = ['click', 'scroll', 'blur', 'resize'] const options = { capture: false } beforeEach(() => { - target = document.createElement('div') - listener = vi.fn() + listener.mockReset() stop = useEventListener(target, events, listener, options) - remove = vitest.spyOn(target, 'removeEventListener') + }) + + it('should add listener for all events', () => { + events.forEach(event => expect(addSpy).toBeCalledWith(event, listener, options)) }) it('should trigger listener with all events', () => { @@ -54,28 +68,28 @@ describe('useEventListener', () => { }) it('should remove listener with all events', () => { - expect(remove).not.toBeCalled() + expect(removeSpy).not.toBeCalled() stop() - expect(remove).toBeCalledTimes(events.length) - events.forEach(event => expect(remove).toBeCalledWith(event, listener, options)) + expect(removeSpy).toBeCalledTimes(events.length) + events.forEach(event => expect(removeSpy).toBeCalledWith(event, listener, options)) }) }) describe('given single event but array of listeners', () => { - let target: HTMLDivElement let stop: Fn - let listeners: Mock[] - let remove: SpyInstance + const listeners = [vitest.fn(), vitest.fn(), vitest.fn()] const event = 'click' const options = { capture: true } beforeEach(() => { - target = document.createElement('div') - listeners = [vi.fn(), vi.fn(), vi.fn()] + listeners.forEach(listener => listener.mockReset()) stop = useEventListener(target, event, listeners, options) - remove = vitest.spyOn(target, 'removeEventListener') + }) + + it('should add all listeners', () => { + listeners.forEach(listener => expect(addSpy).toBeCalledWith(event, listener, options)) }) it('should call all listeners with single click event', () => { @@ -87,28 +101,32 @@ describe('useEventListener', () => { }) it('should remove listeners', () => { - expect(remove).not.toBeCalled() + expect(removeSpy).not.toBeCalled() stop() - expect(remove).toBeCalledTimes(listeners.length) - listeners.forEach(listener => expect(remove).toHaveBeenCalledWith(event, listener, options)) + expect(removeSpy).toBeCalledTimes(listeners.length) + listeners.forEach(listener => expect(removeSpy).toHaveBeenCalledWith(event, listener, options)) }) }) describe('given both array of events and listeners', () => { - let target: HTMLDivElement let stop: Fn - let listeners: Mock[] - let remove: SpyInstance + const listeners = [vitest.fn(), vitest.fn(), vitest.fn()] const events = ['click', 'scroll', 'blur', 'resize'] const options = { capture: true } beforeEach(() => { - target = document.createElement('div') - listeners = [vi.fn(), vi.fn(), vi.fn()] + listeners.forEach(listener => listener.mockReset()) stop = useEventListener(target, events, listeners, options) - remove = vitest.spyOn(target, 'removeEventListener') + }) + + it('should add all listeners for all events', () => { + listeners.forEach((listener) => { + events.forEach((event) => { + expect(addSpy).toBeCalledWith(event, listener, options) + }) + }) }) it('should call all listeners with all events', () => { @@ -123,7 +141,7 @@ describe('useEventListener', () => { listeners.forEach((listener) => { events.forEach((event) => { - expect(remove).toBeCalledWith(event, listener, options) + expect(removeSpy).toBeCalledWith(event, listener, options) }) }) }) From ed988190ee087443e845603f54f00b52b4a2d353 Mon Sep 17 00:00:00 2001 From: vaakian Date: Thu, 8 Sep 2022 19:45:32 +0800 Subject: [PATCH 3/5] chore: formatting --- packages/core/useEventListener/index.test.ts | 18 ++++++++---------- packages/core/useEventListener/index.ts | 4 +--- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/packages/core/useEventListener/index.test.ts b/packages/core/useEventListener/index.test.ts index 8a0042959bc..36442e46854 100644 --- a/packages/core/useEventListener/index.test.ts +++ b/packages/core/useEventListener/index.test.ts @@ -3,6 +3,8 @@ import type { SpyInstance } from 'vitest' import { useEventListener } from '.' describe('useEventListener', () => { + const options = { capture: true } + let stop: Fn let target: HTMLDivElement let removeSpy: SpyInstance let addSpy: SpyInstance @@ -18,13 +20,12 @@ describe('useEventListener', () => { }) describe('given both none array', () => { - let stop: Fn const listener = vitest.fn() const event = 'click' beforeEach(() => { listener.mockReset() - stop = useEventListener(target, event, listener) + stop = useEventListener(target, event, listener, options) }) it('should add listener', () => { @@ -39,16 +40,17 @@ describe('useEventListener', () => { it('should remove listener', () => { expect(removeSpy).not.toBeCalled() + stop() + expect(removeSpy).toBeCalledTimes(1) + expect(removeSpy).toBeCalledWith(event, listener, options) }) }) describe('given array of events but single listener', () => { - let stop: Fn const listener = vitest.fn() const events = ['click', 'scroll', 'blur', 'resize'] - const options = { capture: false } beforeEach(() => { listener.mockReset() @@ -78,10 +80,8 @@ describe('useEventListener', () => { }) describe('given single event but array of listeners', () => { - let stop: Fn const listeners = [vitest.fn(), vitest.fn(), vitest.fn()] const event = 'click' - const options = { capture: true } beforeEach(() => { listeners.forEach(listener => listener.mockReset()) @@ -106,15 +106,13 @@ describe('useEventListener', () => { stop() expect(removeSpy).toBeCalledTimes(listeners.length) - listeners.forEach(listener => expect(removeSpy).toHaveBeenCalledWith(event, listener, options)) + listeners.forEach(listener => expect(removeSpy).toBeCalledWith(event, listener, options)) }) }) describe('given both array of events and listeners', () => { - let stop: Fn const listeners = [vitest.fn(), vitest.fn(), vitest.fn()] - const events = ['click', 'scroll', 'blur', 'resize'] - const options = { capture: true } + const events = ['click', 'scroll', 'blur', 'resize', 'custom-event'] beforeEach(() => { listeners.forEach(listener => listener.mockReset()) diff --git a/packages/core/useEventListener/index.ts b/packages/core/useEventListener/index.ts index 1f35bbd593d..d81ea28dbd4 100644 --- a/packages/core/useEventListener/index.ts +++ b/packages/core/useEventListener/index.ts @@ -131,9 +131,7 @@ export function useEventListener(...args: any[]) { const register = (el: any, event: string, listener: any) => { el.addEventListener(event, listener, options) - return () => { - el.removeEventListener(event, listener, options) - } + return () => el.removeEventListener(event, listener, options) } const stopWatch = watch( From cbe69fa6bf64631ac5a806379c86a0fb2c2c0b4c Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Wed, 9 Nov 2022 07:37:38 +0800 Subject: [PATCH 4/5] chore: update docs style --- packages/.vitepress/plugins/markdownTransform.ts | 2 +- packages/.vitepress/theme/components/TeamMember.vue | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/.vitepress/plugins/markdownTransform.ts b/packages/.vitepress/plugins/markdownTransform.ts index 2ec1c704b27..c2d85437b0e 100644 --- a/packages/.vitepress/plugins/markdownTransform.ts +++ b/packages/.vitepress/plugins/markdownTransform.ts @@ -78,7 +78,7 @@ export async function getFunctionMarkdown(pkg: string, name: string) { ## Type Declarations
-Show Type Declarations +Show Type Declarations ${code} diff --git a/packages/.vitepress/theme/components/TeamMember.vue b/packages/.vitepress/theme/components/TeamMember.vue index 2fb97146a61..012a5f2d0a2 100644 --- a/packages/.vitepress/theme/components/TeamMember.vue +++ b/packages/.vitepress/theme/components/TeamMember.vue @@ -45,9 +45,9 @@ defineProps<{ :aria-label="`Sponsor ${data.name}`" /> -
+