From 913af4d4c4fd8098a5223513527e9346f1f51092 Mon Sep 17 00:00:00 2001 From: Rene Haas Date: Mon, 24 Oct 2022 14:03:57 +0200 Subject: [PATCH] improve event listeners api --- README.md | 1 + package-lock.json | 10 +---- packages/overlayscrollbars/README.md | 1 + .../overlayscrollbars/src/eventListeners.ts | 6 +-- packages/overlayscrollbars/src/index.ts | 2 +- .../src/overlayscrollbars.ts | 12 +++-- .../src/support/eventListeners.ts | 44 +++++++++++-------- .../jest-jsdom/support/eventListeners.test.ts | 36 +++++++++++++++ 8 files changed, 79 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 4598918..2a1630f 100644 --- a/README.md +++ b/README.md @@ -408,6 +408,7 @@ interface OverlayScrollbars { elements(): Elements; + on(eventListeners: EventListeners): () => void; on(name: N, listener: EventListener): () => void; on(name: N, listener: EventListener[]): () => void; diff --git a/package-lock.json b/package-lock.json index 87deb16..bcb7798 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19520,10 +19520,6 @@ "find-parent-dir": "^0.3.0" } }, - "node_modules/node-starter": { - "resolved": "packages/test", - "link": true - }, "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -31507,7 +31503,8 @@ }, "packages/test": { "name": "node-starter", - "version": "0.0.0" + "version": "0.0.0", + "extraneous": true }, "website": { "dependencies": { @@ -45917,9 +45914,6 @@ "find-parent-dir": "^0.3.0" } }, - "node-starter": { - "version": "file:packages/test" - }, "nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", diff --git a/packages/overlayscrollbars/README.md b/packages/overlayscrollbars/README.md index 4598918..2a1630f 100644 --- a/packages/overlayscrollbars/README.md +++ b/packages/overlayscrollbars/README.md @@ -408,6 +408,7 @@ interface OverlayScrollbars { elements(): Elements; + on(eventListeners: EventListeners): () => void; on(name: N, listener: EventListener): () => void; on(name: N, listener: EventListener[]): () => void; diff --git a/packages/overlayscrollbars/src/eventListeners.ts b/packages/overlayscrollbars/src/eventListeners.ts index a3837eb..4895823 100644 --- a/packages/overlayscrollbars/src/eventListeners.ts +++ b/packages/overlayscrollbars/src/eventListeners.ts @@ -2,7 +2,7 @@ import type { OverlayScrollbars } from '~/overlayscrollbars'; import type { DeepPartial } from '~/typings'; import type { Options } from '~/options'; import type { - InitialEventListeners as GeneralInitialEventListeners, + EventListeners as GeneralEventListeners, EventListener as GeneralEventListener, } from '~/support/eventListeners'; @@ -48,13 +48,13 @@ export type EventListenerMap = { }; /** - * An object which describes the initial event listeners. + * An object which describes event listeners. * Simplified it looks like: * { * [eventName: string]: EventListener | EventListener[] * } */ -export type InitialEventListeners = GeneralInitialEventListeners; +export type EventListeners = GeneralEventListeners; /** An event listener. */ export type EventListener = GeneralEventListener< diff --git a/packages/overlayscrollbars/src/index.ts b/packages/overlayscrollbars/src/index.ts index 421b2e2..37b055c 100644 --- a/packages/overlayscrollbars/src/index.ts +++ b/packages/overlayscrollbars/src/index.ts @@ -12,7 +12,7 @@ export type { export type { EventListenerMap, EventListener, - InitialEventListeners, + EventListeners, OnUpdatedEventListenerArgs, } from '~/eventListeners'; export type { diff --git a/packages/overlayscrollbars/src/overlayscrollbars.ts b/packages/overlayscrollbars/src/overlayscrollbars.ts index 2488be7..74c6aae 100644 --- a/packages/overlayscrollbars/src/overlayscrollbars.ts +++ b/packages/overlayscrollbars/src/overlayscrollbars.ts @@ -20,7 +20,7 @@ import type { Options, ReadonlyOptions } from '~/options'; import type { Plugin, OptionsValidationPluginInstance, PluginInstance } from '~/plugins'; import type { InitializationTarget } from '~/initialization'; import type { DeepPartial, OverflowStyle } from '~/typings'; -import type { EventListenerMap, EventListener, InitialEventListeners } from '~/eventListeners'; +import type { EventListenerMap, EventListener, EventListeners } from '~/eventListeners'; import type { ScrollbarsSetupElement, ScrollbarStructure, @@ -48,7 +48,7 @@ export interface OverlayScrollbarsStatic { ( target: InitializationTarget, options: DeepPartial, - eventListeners?: InitialEventListeners + eventListeners?: EventListeners ): OverlayScrollbars; /** @@ -170,6 +170,12 @@ export interface OverlayScrollbars { */ options(newOptions: DeepPartial): Options; + /** + * Adds event listeners to the instance. + * @param eventListeners An object which contains the added listeners. + * @returns Returns a function which removes the added listeners. + */ + on(eventListeners: EventListeners): () => void; /** * Adds an event listener to the instance. * @param name The name of the event. @@ -227,7 +233,7 @@ const invokePluginInstance = ( export const OverlayScrollbars: OverlayScrollbarsStatic = ( target: InitializationTarget, options?: DeepPartial, - eventListeners?: InitialEventListeners + eventListeners?: EventListeners ) => { const { _getDefaultOptions, _getDefaultInitialization, _addListener } = getEnvironment(); const plugins = getPlugins(); diff --git a/packages/overlayscrollbars/src/support/eventListeners.ts b/packages/overlayscrollbars/src/support/eventListeners.ts index feed51a..68771f1 100644 --- a/packages/overlayscrollbars/src/support/eventListeners.ts +++ b/packages/overlayscrollbars/src/support/eventListeners.ts @@ -1,12 +1,12 @@ -import { isArray } from '~/support/utils/types'; +import { isArray, isString } from '~/support/utils/types'; import { keys } from '~/support/utils/object'; -import { each, from, isEmptyArray } from '~/support/utils/array'; +import { each, push, from, isEmptyArray, runEachAndClear } from '~/support/utils/array'; export type EventListener, N extends keyof EventMap> = ( ...args: EventMap[N] ) => void; -export type InitialEventListeners> = { +export type EventListeners> = { [K in keyof EventMap]?: EventListener | EventListener[]; }; @@ -18,7 +18,7 @@ const manageListener = , N extends keyof }; export const createEventListenerHub = >( - initialEventListeners?: InitialEventListeners + initialEventListeners?: EventListeners ) => { // eslint-disable-next-line @typescript-eslint/no-shadow type EventListener = (...args: EventMap[N]) => void; @@ -28,11 +28,12 @@ export const createEventListenerHub = >( (name?: N, listener?: EventListener | EventListener[]): void; }; type AddEvent = { + (eventListeners: EventListeners): () => void; (name: N, listener: EventListener): () => void; (name: N, listener: EventListener[]): () => void; ( - name: N, - listener: EventListener | EventListener[] + nameOrEventListeners: N | EventListeners, + listener?: EventListener | EventListener[] ): () => void; }; type TriggerEvent = { @@ -61,17 +62,27 @@ export const createEventListenerHub = >( }; const addEvent: AddEvent = ( - name: N, - listener: EventListener | EventListener[] + nameOrEventListeners: N | EventListeners, + listener?: EventListener | EventListener[] ): (() => void) => { - const eventSet = events.get(name) || new Set(); - events.set(name, eventSet); + if (isString(nameOrEventListeners)) { + const eventSet = events.get(nameOrEventListeners) || new Set(); + events.set(nameOrEventListeners, eventSet); - manageListener((currListener) => { - currListener && eventSet.add(currListener); - }, listener as any); + manageListener((currListener) => { + currListener && eventSet.add(currListener); + }, listener as any); - return removeEvent.bind(0, name as any, listener as any); + return removeEvent.bind(0, nameOrEventListeners as any, listener as any); + } + + const eventListenerKeys = keys(nameOrEventListeners) as (keyof EventListeners)[]; + const offFns: (() => void)[] = []; + each(eventListenerKeys, (key) => { + push(offFns, addEvent(key, (nameOrEventListeners as EventListeners)[key])); + }); + + return runEachAndClear.bind(0, offFns); }; const triggerEvent: TriggerEvent = ( @@ -89,10 +100,7 @@ export const createEventListenerHub = >( }); }; - const initialListenerKeys = keys(initialEventListeners) as Extract[]; - each(initialListenerKeys, (key) => { - addEvent(key, initialEventListeners![key] as any); - }); + addEvent(initialEventListeners || {}); return [addEvent, removeEvent, triggerEvent] as [AddEvent, RemoveEvent, TriggerEvent]; }; diff --git a/packages/overlayscrollbars/test/jest-jsdom/support/eventListeners.test.ts b/packages/overlayscrollbars/test/jest-jsdom/support/eventListeners.test.ts index 8deccd7..372cd2c 100644 --- a/packages/overlayscrollbars/test/jest-jsdom/support/eventListeners.test.ts +++ b/packages/overlayscrollbars/test/jest-jsdom/support/eventListeners.test.ts @@ -94,6 +94,42 @@ describe('eventListeners', () => { expect(something).toHaveBeenCalledTimes(1); }); + test('addEvent with event listeners', () => { + const onBooleanA = jest.fn((a: boolean, b: string) => a + b); + const onBooleanB = jest.fn(); + const onString = jest.fn(); + const onUndefined = jest.fn(); + const [addEvent, , triggerEvent] = createEventListenerHub(); + const removeEvents = addEvent({ + onBoolean: [onBooleanA, onBooleanB], + onString: [onString, onString], // multiple equal listeners are treated as one + onUndefined, + }); + + triggerEvent('onBoolean', [true, 'hi']); + triggerEvent('onString', ['hi']); + triggerEvent('onUndefined'); + + removeEvents(); + + triggerEvent('onBoolean', [true, 'hi']); + triggerEvent('onString', ['hi']); + triggerEvent('onUndefined'); + + expect(onBooleanA).toHaveBeenCalledTimes(1); + expect(onBooleanA).toHaveBeenLastCalledWith(true, 'hi'); + + expect(onBooleanB).toHaveBeenCalledTimes(1); + expect(onBooleanB).toHaveBeenLastCalledWith(true, 'hi'); + + // even though onString was registered twice, its only called once + expect(onString).toHaveBeenCalledTimes(1); + expect(onString).toHaveBeenLastCalledWith('hi'); + + expect(onUndefined).toHaveBeenCalledTimes(1); + expect(onUndefined).toHaveBeenLastCalledWith(); + }); + test('removeEvent', () => { const onBooleanA = jest.fn(); const onBooleanB = jest.fn();