improve event listeners api

This commit is contained in:
Rene Haas
2022-10-24 14:03:57 +02:00
parent b1f98a01de
commit 913af4d4c4
8 changed files with 79 additions and 33 deletions
+1
View File
@@ -408,6 +408,7 @@ interface OverlayScrollbars {
elements(): Elements; elements(): Elements;
on(eventListeners: EventListeners): () => void;
on<N extends keyof EventListenerMap>(name: N, listener: EventListener<N>): () => void; on<N extends keyof EventListenerMap>(name: N, listener: EventListener<N>): () => void;
on<N extends keyof EventListenerMap>(name: N, listener: EventListener<N>[]): () => void; on<N extends keyof EventListenerMap>(name: N, listener: EventListener<N>[]): () => void;
+2 -8
View File
@@ -19520,10 +19520,6 @@
"find-parent-dir": "^0.3.0" "find-parent-dir": "^0.3.0"
} }
}, },
"node_modules/node-starter": {
"resolved": "packages/test",
"link": true
},
"node_modules/nopt": { "node_modules/nopt": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
@@ -31507,7 +31503,8 @@
}, },
"packages/test": { "packages/test": {
"name": "node-starter", "name": "node-starter",
"version": "0.0.0" "version": "0.0.0",
"extraneous": true
}, },
"website": { "website": {
"dependencies": { "dependencies": {
@@ -45917,9 +45914,6 @@
"find-parent-dir": "^0.3.0" "find-parent-dir": "^0.3.0"
} }
}, },
"node-starter": {
"version": "file:packages/test"
},
"nopt": { "nopt": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
+1
View File
@@ -408,6 +408,7 @@ interface OverlayScrollbars {
elements(): Elements; elements(): Elements;
on(eventListeners: EventListeners): () => void;
on<N extends keyof EventListenerMap>(name: N, listener: EventListener<N>): () => void; on<N extends keyof EventListenerMap>(name: N, listener: EventListener<N>): () => void;
on<N extends keyof EventListenerMap>(name: N, listener: EventListener<N>[]): () => void; on<N extends keyof EventListenerMap>(name: N, listener: EventListener<N>[]): () => void;
@@ -2,7 +2,7 @@ import type { OverlayScrollbars } from '~/overlayscrollbars';
import type { DeepPartial } from '~/typings'; import type { DeepPartial } from '~/typings';
import type { Options } from '~/options'; import type { Options } from '~/options';
import type { import type {
InitialEventListeners as GeneralInitialEventListeners, EventListeners as GeneralEventListeners,
EventListener as GeneralEventListener, EventListener as GeneralEventListener,
} from '~/support/eventListeners'; } 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: * Simplified it looks like:
* { * {
* [eventName: string]: EventListener | EventListener[] * [eventName: string]: EventListener | EventListener[]
* } * }
*/ */
export type InitialEventListeners = GeneralInitialEventListeners<EventListenerMap>; export type EventListeners = GeneralEventListeners<EventListenerMap>;
/** An event listener. */ /** An event listener. */
export type EventListener<N extends keyof EventListenerMap> = GeneralEventListener< export type EventListener<N extends keyof EventListenerMap> = GeneralEventListener<
+1 -1
View File
@@ -12,7 +12,7 @@ export type {
export type { export type {
EventListenerMap, EventListenerMap,
EventListener, EventListener,
InitialEventListeners, EventListeners,
OnUpdatedEventListenerArgs, OnUpdatedEventListenerArgs,
} from '~/eventListeners'; } from '~/eventListeners';
export type { export type {
@@ -20,7 +20,7 @@ import type { Options, ReadonlyOptions } from '~/options';
import type { Plugin, OptionsValidationPluginInstance, PluginInstance } from '~/plugins'; import type { Plugin, OptionsValidationPluginInstance, PluginInstance } from '~/plugins';
import type { InitializationTarget } from '~/initialization'; import type { InitializationTarget } from '~/initialization';
import type { DeepPartial, OverflowStyle } from '~/typings'; import type { DeepPartial, OverflowStyle } from '~/typings';
import type { EventListenerMap, EventListener, InitialEventListeners } from '~/eventListeners'; import type { EventListenerMap, EventListener, EventListeners } from '~/eventListeners';
import type { import type {
ScrollbarsSetupElement, ScrollbarsSetupElement,
ScrollbarStructure, ScrollbarStructure,
@@ -48,7 +48,7 @@ export interface OverlayScrollbarsStatic {
( (
target: InitializationTarget, target: InitializationTarget,
options: DeepPartial<Options>, options: DeepPartial<Options>,
eventListeners?: InitialEventListeners eventListeners?: EventListeners
): OverlayScrollbars; ): OverlayScrollbars;
/** /**
@@ -170,6 +170,12 @@ export interface OverlayScrollbars {
*/ */
options(newOptions: DeepPartial<Options>): Options; options(newOptions: DeepPartial<Options>): 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. * Adds an event listener to the instance.
* @param name The name of the event. * @param name The name of the event.
@@ -227,7 +233,7 @@ const invokePluginInstance = (
export const OverlayScrollbars: OverlayScrollbarsStatic = ( export const OverlayScrollbars: OverlayScrollbarsStatic = (
target: InitializationTarget, target: InitializationTarget,
options?: DeepPartial<Options>, options?: DeepPartial<Options>,
eventListeners?: InitialEventListeners eventListeners?: EventListeners
) => { ) => {
const { _getDefaultOptions, _getDefaultInitialization, _addListener } = getEnvironment(); const { _getDefaultOptions, _getDefaultInitialization, _addListener } = getEnvironment();
const plugins = getPlugins(); const plugins = getPlugins();
@@ -1,12 +1,12 @@
import { isArray } from '~/support/utils/types'; import { isArray, isString } from '~/support/utils/types';
import { keys } from '~/support/utils/object'; 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<EventMap extends Record<string, any[]>, N extends keyof EventMap> = ( export type EventListener<EventMap extends Record<string, any[]>, N extends keyof EventMap> = (
...args: EventMap[N] ...args: EventMap[N]
) => void; ) => void;
export type InitialEventListeners<EventMap extends Record<string, any[]>> = { export type EventListeners<EventMap extends Record<string, any[]>> = {
[K in keyof EventMap]?: EventListener<EventMap, K> | EventListener<EventMap, K>[]; [K in keyof EventMap]?: EventListener<EventMap, K> | EventListener<EventMap, K>[];
}; };
@@ -18,7 +18,7 @@ const manageListener = <EventMap extends Record<string, any[]>, N extends keyof
}; };
export const createEventListenerHub = <EventMap extends Record<string, any[]>>( export const createEventListenerHub = <EventMap extends Record<string, any[]>>(
initialEventListeners?: InitialEventListeners<EventMap> initialEventListeners?: EventListeners<EventMap>
) => { ) => {
// eslint-disable-next-line @typescript-eslint/no-shadow // eslint-disable-next-line @typescript-eslint/no-shadow
type EventListener<N extends keyof EventMap> = (...args: EventMap[N]) => void; type EventListener<N extends keyof EventMap> = (...args: EventMap[N]) => void;
@@ -28,11 +28,12 @@ export const createEventListenerHub = <EventMap extends Record<string, any[]>>(
<N extends keyof EventMap>(name?: N, listener?: EventListener<N> | EventListener<N>[]): void; <N extends keyof EventMap>(name?: N, listener?: EventListener<N> | EventListener<N>[]): void;
}; };
type AddEvent = { type AddEvent = {
(eventListeners: EventListeners<EventMap>): () => void;
<N extends keyof EventMap>(name: N, listener: EventListener<N>): () => void; <N extends keyof EventMap>(name: N, listener: EventListener<N>): () => void;
<N extends keyof EventMap>(name: N, listener: EventListener<N>[]): () => void; <N extends keyof EventMap>(name: N, listener: EventListener<N>[]): () => void;
<N extends keyof EventMap>( <N extends keyof EventMap>(
name: N, nameOrEventListeners: N | EventListeners<EventMap>,
listener: EventListener<N> | EventListener<N>[] listener?: EventListener<N> | EventListener<N>[]
): () => void; ): () => void;
}; };
type TriggerEvent = { type TriggerEvent = {
@@ -61,17 +62,27 @@ export const createEventListenerHub = <EventMap extends Record<string, any[]>>(
}; };
const addEvent: AddEvent = <N extends keyof EventMap>( const addEvent: AddEvent = <N extends keyof EventMap>(
name: N, nameOrEventListeners: N | EventListeners<EventMap>,
listener: EventListener<N> | EventListener<N>[] listener?: EventListener<N> | EventListener<N>[]
): (() => void) => { ): (() => void) => {
const eventSet = events.get(name) || new Set(); if (isString(nameOrEventListeners)) {
events.set(name, eventSet); const eventSet = events.get(nameOrEventListeners) || new Set();
events.set(nameOrEventListeners, eventSet);
manageListener((currListener) => { manageListener((currListener) => {
currListener && eventSet.add(currListener); currListener && eventSet.add(currListener);
}, listener as any); }, 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<EventMap>)[];
const offFns: (() => void)[] = [];
each(eventListenerKeys, (key) => {
push(offFns, addEvent(key, (nameOrEventListeners as EventListeners<EventMap>)[key]));
});
return runEachAndClear.bind(0, offFns);
}; };
const triggerEvent: TriggerEvent = <N extends keyof EventMap>( const triggerEvent: TriggerEvent = <N extends keyof EventMap>(
@@ -89,10 +100,7 @@ export const createEventListenerHub = <EventMap extends Record<string, any[]>>(
}); });
}; };
const initialListenerKeys = keys(initialEventListeners) as Extract<keyof EventMap, string>[]; addEvent(initialEventListeners || {});
each(initialListenerKeys, (key) => {
addEvent(key, initialEventListeners![key] as any);
});
return [addEvent, removeEvent, triggerEvent] as [AddEvent, RemoveEvent, TriggerEvent]; return [addEvent, removeEvent, triggerEvent] as [AddEvent, RemoveEvent, TriggerEvent];
}; };
@@ -94,6 +94,42 @@ describe('eventListeners', () => {
expect(something).toHaveBeenCalledTimes(1); 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<EventMap>();
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', () => { test('removeEvent', () => {
const onBooleanA = jest.fn(); const onBooleanA = jest.fn();
const onBooleanB = jest.fn(); const onBooleanB = jest.fn();