export type Fn< Params extends readonly unknown[] = readonly unknown[], Result = unknown,> = (...params: Params) => Result;
export type TypedCustomEvent<Type extends string, Detail = unknown> = & CustomEvent<Detail> & { type: Type };
export type CustomEventCallbackAddEventListener< Type extends string = string, Detail = unknown,> = Fn<[event: TypedCustomEvent<Type, Detail>], void>;
export type CustomEventCallbackOn< Type extends string = string, Detail = unknown,> = Fn<[event: TypedCustomEvent<Type, Detail>["detail"]], void>;
export type EventCallbackFromCustomEvent< T extends TypedCustomEvent<string, unknown>,> = Fn<[event: T], void>;
export type CustomEventMap = Record<string, unknown>;
export type EventTargetCompatible = Extract< Parameters<EventTarget["addEventListener"]>[1], Fn>;
type CustomEventDetailParameter< T extends Record<string, unknown>, K extends keyof T,> = ( unknown extends T[K] ? [detail?: unknown] : undefined extends T[K] ? [detail?: T[K]] : T[K] extends never ? [] : [detail: T[K]]);
type CustomEventListenerMap<Type extends string = string, Detail = unknown> = Map< | CustomEventCallbackOn<Type, Detail> | CustomEventCallbackAddEventListener<Type, Detail>, CustomEventCallbackAddEventListener<Type, Detail> >;
export class EventEmitter<T extends CustomEventMap = Record<string, unknown>> extends EventTarget { protected __listeners__: Map< string, CustomEventListenerMap<string, any> > = new Map();
static createEvent<Type extends string, Detail>( type: Type, detail?: Detail, init?: Omit<CustomEventInit, "detail">, ): TypedCustomEvent<Type, Detail> { const evInit = { ...init, detail };
return new CustomEvent(type, evInit) as TypedCustomEvent<Type, Detail>; }
static detailPasser<T extends CustomEventMap, K extends keyof T & string>( callback: CustomEventCallbackOn<K, T[K]>, ) { const call = (event: TypedCustomEvent<K, T[K]>): void => { callback(event.detail); };
return call; }
protected getOrCreateListeners<K extends keyof T & string>( type: K, ): CustomEventListenerMap<K, any> { if (!this.__listeners__.has(type)) { this.__listeners__.set(type, new Map()); }
return this.__listeners__.get(type)!; }
addEventListener<K extends keyof T & string>( type: K, callback: CustomEventCallbackAddEventListener<K, T[K]>, options?: Parameters<EventTarget["addEventListener"]>[2], ): this { super.addEventListener( type, callback as EventTargetCompatible, options, );
this .getOrCreateListeners(type) .set(callback, callback);
return this; }
on<K extends keyof T & string>( type: K, callback: CustomEventCallbackOn<K, T[K]>, options?: Parameters<EventTarget["addEventListener"]>[2], ): this;
on<K extends keyof T & string>( types: K[], callback: CustomEventCallbackOn<K, T[K]>, options?: Parameters<EventTarget["addEventListener"]>[2], ): this;
on<K extends keyof T & string>( types: K | K[], callback: CustomEventCallbackOn<K, T[K]>, options?: Parameters<EventTarget["addEventListener"]>[2], ) { const detailOnly = EventEmitter.detailPasser(callback);
const addCallback = (type: K) => { super.addEventListener( type, detailOnly as EventTargetCompatible, options, );
this .getOrCreateListeners(type) .set(callback, detailOnly); };
if (typeof types === "string") { addCallback(types); } else { types.forEach((type) => { addCallback(type); }); }
return this; }
once<K extends keyof T & string>( type: K, callback: CustomEventCallbackOn<K, T[K]>, options?: Parameters<EventTarget["addEventListener"]>[2], ): this;
once<K extends keyof T & string>( types: K[], callback: CustomEventCallbackOn<K, T[K]>, options?: Parameters<EventTarget["addEventListener"]>[2], ): this;
once<K extends keyof T & string>( types: K | K[], callback: CustomEventCallbackOn<K, T[K]>, options?: Parameters<EventTarget["addEventListener"]>[2], ): this { options ||= {};
this.on( types, callback, Object.assign( options, { once: true, }, ), );
return this; }
removeEventListener<K extends keyof T & string>( type: K, callback: | CustomEventCallbackAddEventListener<K, T[K]> | CustomEventCallbackOn<K, T[K]>, options?: Parameters<EventTarget["removeEventListener"]>[2], ): this { const realCb = this.__listeners__.get(type)?.get(callback);
if (realCb) { super.removeEventListener( type, this.__listeners__.get(type)!.get( callback, )! as EventTargetCompatible, options, );
this .getOrCreateListeners(type) .delete(callback); }
return this; }
off<K extends keyof T & string>(): this;
off<K extends keyof T & string>( type: K, ): this;
off<K extends keyof T & string>( types: K[], ): this;
off<K extends keyof T & string>( type: K, callback: | CustomEventCallbackOn<K, T[K]> | CustomEventCallbackAddEventListener<K, T[K]>, ): this;
off<K extends keyof T & string>( types: K[], callback: | CustomEventCallbackOn<K, T[K]> | CustomEventCallbackAddEventListener<K, T[K]>, ): this;
off<K extends keyof T & string>( types?: K | K[], callback?: | CustomEventCallbackOn<K, T[K]> | CustomEventCallbackAddEventListener<K, T[K]>, ): this { const doRemove = ( type: K, optionalCallback?: CustomEventCallbackAddEventListener<K, T[K]>, ) => { const cb = optionalCallback ?? callback;
if (cb) { this.removeEventListener(type, cb); } };
if (!types && !callback) { this.__listeners__.forEach((map, type) => { map.forEach((_value, callback) => { doRemove(type as K, callback); }); });
this.__listeners__.clear(); } else if (types && !callback) { const removeAllForOneType = (type: K) => { const listeners = this.__listeners__.get(type);
listeners?.forEach((listener) => { doRemove(type, listener); });
this.__listeners__.delete(type); };
if (typeof types === "string") { removeAllForOneType(types); } else { types.forEach((type) => { removeAllForOneType(type); }); } } else if (types && callback) { if (typeof types === "string") { doRemove(types); } else { types.forEach((type) => { doRemove(type); }); } } else { throw new Error("Unknown case for removing event!"); }
return this; }
dispatchEvent<E extends Event>(type: E): boolean { return super.dispatchEvent(type); }
dispatch = this.emit;
emit<K extends keyof T & string>( type: K, ...[detail]: CustomEventDetailParameter<T, K> ): this { const event = EventEmitter.createEvent( type, detail, );
this.dispatchEvent(event);
return this; }
pull<K extends keyof T & string>(type: K): Promise<T[K]>;
pull<K extends keyof T & string>(type: K, timeout: number): Promise<T[K]>;
pull<K extends keyof T & string>(type: K, timeout?: number): Promise<T[K]> { return new Promise((resolve, reject) => { let timeoutId: number | null;
this.once(type, (event) => { timeoutId && clearTimeout(timeoutId);
resolve(event); });
if (timeout) { timeoutId = setTimeout(() => { clearTimeout(timeoutId!);
reject("Timed out!"); }); } }); }
publish = this.emit;
subscribe<K extends keyof T & string>( type: K, callback: CustomEventCallbackOn<K, T[K]>, options?: Parameters<EventTarget["addEventListener"]>[2], ): Fn<never, void> { this.on(type, callback, options);
return () => { this.off(type, callback); }; }
getListeners<K extends keyof T & string>( type: K, ): Set<CustomEventCallbackAddEventListener | CustomEventCallbackOn>;
getListeners(): Map< string, Set<CustomEventCallbackAddEventListener | CustomEventCallbackOn> >;
getListeners<K extends keyof T & string>( type?: K, ): | Set< | CustomEventCallbackAddEventListener<string, unknown> | CustomEventCallbackOn<string, unknown> > | Map< string, Set< | CustomEventCallbackAddEventListener<string, unknown> | CustomEventCallbackOn<string, unknown> > > { const getListenersOfType = (type: string) => { const listeners = new Set< CustomEventCallbackAddEventListener | CustomEventCallbackOn >();
const listenersSet = this.__listeners__.get(type);
if (listenersSet) { for (const listener of listenersSet.values()) { listeners.add(listener); } }
return listeners; };
if (!type) { const listeners = new Map< string, Set<CustomEventCallbackAddEventListener | CustomEventCallbackOn> >();
const entries = this.__listeners__.entries();
for (const [type] of entries) {
listeners.set(type, getListenersOfType(type)); }
return listeners; }
return getListenersOfType(type); }}
export default EventEmitter;