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;
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;
+2 -8
View File
@@ -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",
+1
View File
@@ -408,6 +408,7 @@ interface OverlayScrollbars {
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;
@@ -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<EventListenerMap>;
export type EventListeners = GeneralEventListeners<EventListenerMap>;
/** An event listener. */
export type EventListener<N extends keyof EventListenerMap> = GeneralEventListener<
+1 -1
View File
@@ -12,7 +12,7 @@ export type {
export type {
EventListenerMap,
EventListener,
InitialEventListeners,
EventListeners,
OnUpdatedEventListenerArgs,
} from '~/eventListeners';
export type {
@@ -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<Options>,
eventListeners?: InitialEventListeners
eventListeners?: EventListeners
): OverlayScrollbars;
/**
@@ -170,6 +170,12 @@ export interface OverlayScrollbars {
*/
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.
* @param name The name of the event.
@@ -227,7 +233,7 @@ const invokePluginInstance = (
export const OverlayScrollbars: OverlayScrollbarsStatic = (
target: InitializationTarget,
options?: DeepPartial<Options>,
eventListeners?: InitialEventListeners
eventListeners?: EventListeners
) => {
const { _getDefaultOptions, _getDefaultInitialization, _addListener } = getEnvironment();
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 { 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> = (
...args: EventMap[N]
) => 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>[];
};
@@ -18,7 +18,7 @@ const manageListener = <EventMap extends Record<string, any[]>, N extends keyof
};
export const createEventListenerHub = <EventMap extends Record<string, any[]>>(
initialEventListeners?: InitialEventListeners<EventMap>
initialEventListeners?: EventListeners<EventMap>
) => {
// eslint-disable-next-line @typescript-eslint/no-shadow
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;
};
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> | EventListener<N>[]
nameOrEventListeners: N | EventListeners<EventMap>,
listener?: EventListener<N> | EventListener<N>[]
): () => void;
};
type TriggerEvent = {
@@ -61,17 +62,27 @@ export const createEventListenerHub = <EventMap extends Record<string, any[]>>(
};
const addEvent: AddEvent = <N extends keyof EventMap>(
name: N,
listener: EventListener<N> | EventListener<N>[]
nameOrEventListeners: N | EventListeners<EventMap>,
listener?: EventListener<N> | EventListener<N>[]
): (() => 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<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>(
@@ -89,10 +100,7 @@ export const createEventListenerHub = <EventMap extends Record<string, any[]>>(
});
};
const initialListenerKeys = keys(initialEventListeners) as Extract<keyof EventMap, string>[];
each(initialListenerKeys, (key) => {
addEvent(key, initialEventListeners![key] as any);
});
addEvent(initialEventListeners || {});
return [addEvent, removeEvent, triggerEvent] as [AddEvent, RemoveEvent, TriggerEvent];
};
@@ -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<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', () => {
const onBooleanA = jest.fn();
const onBooleanB = jest.fn();