import * as errorUtils from './errors'; export interface IEvent { type: T; target: unknown; defaultPrevented: boolean; } export class Event implements IEvent { target: unknown; defaultPrevented = false; constructor( public type: T, ) {} } export type EventCallback> = (event: T) => void; interface EventTypeDict { [type: string]: Event; } export interface IEventTarget { addEventListener: ( type: K, callback: EventCallback, ) => void; removeEventListener: ( type: K, callback: EventCallback, ) => void; removeAllEventListeners: () => void; dispatchEvent: ( event: T[K] & Event, ) => void; } export class EventTarget implements IEventTarget { private eventRegistry?: {[type: string]: EventCallback>[]}; addEventListener( type: K, callback: EventCallback, ): void { const eventRegistry = this.eventRegistry || {}; const listeners = eventRegistry[type as string] || []; eventRegistry[type as string] = [ ...listeners, callback as EventCallback>, ]; this.eventRegistry = eventRegistry; } removeEventListener( type: K, callback: EventCallback, ): void { const eventRegistry = this.eventRegistry || {}; const listeners = eventRegistry[type as string] || []; eventRegistry[type as string] = listeners.filter( (listener: EventCallback) => listener !== callback, ); this.eventRegistry = eventRegistry; } removeAllEventListeners(): void { this.eventRegistry = {}; } dispatchEvent(event: T[K]): boolean { const eventRegistry = this.eventRegistry || {}; const listeners = eventRegistry[event.type] || []; listeners.forEach((listener: EventCallback) => { try { listener(event); } catch (exc) { const err = errorUtils.getErrorOrThrow(exc); console.error(`Event Listener failed (${String(event.type)}): ${err.message}`); console.info(err.stack); } }); return !event.defaultPrevented; } }