export type Listener = (data: Data[Key], key: Key) => void; export type AnyListener = (data: Partial, key: string) => void; export type Unsubscribe = () => void; export interface PropertyChange { source: Source; previous: Value; } export interface AnyEvent { key: string; data: Partial; } export interface Events { on(eventKey: Key, listener: Listener): void; off(eventKey: Key, listener: Listener): void; onAny(listener: AnyListener): void; offAny(listener: AnyListener): void; } export class EventSource implements Events { private listeners = new Map>>(); private anyListeners: Array> | undefined; on(eventKey: Key, listener: Listener): void { let listeners = this.listeners.get(eventKey); if (!listeners) { listeners = []; this.listeners.set(eventKey, listeners); } listeners.push(listener); } onAny(listener: AnyListener): void { let listeners = this.anyListeners; if (!listeners) { listeners = []; this.anyListeners = listeners; } listeners.push(listener); } off(eventKey: Key, listener: Listener): void { const listeners = this.listeners.get(eventKey); if (!listeners) { return; } const index = listeners.indexOf(listener); if (index >= 0) { listeners.splice(index, 1); } } offAny(listener: AnyListener): void { const listeners = this.anyListeners; if (!listeners) { return; } const index = listeners.indexOf(listener); if (index >= 0) { listeners.splice(index, 1); } } trigger(eventKey: Key, data: Data[Key]): void { const listeners = this.listeners.get(eventKey); if (listeners) { for (const listener of listeners) { listener(data, eventKey); } } if (this.anyListeners) { for (const anyListener of this.anyListeners) { anyListener({[eventKey]: data} as any, eventKey as string); } } } } export class EventObserver { private unsubscribeByKey = new Map(); private onDispose: Array = []; listen( events: Events, eventKey: Key, listener: Listener ) { events.on(eventKey, listener); this.onDispose.push(() => events.off(eventKey, listener)); } listenAny(events: Events, listener: AnyListener) { events.onAny(listener); this.onDispose.push(() => events.offAny(listener)); } listenOnce( events: Events, eventKey: Key, listener: Listener ) { let handled = false; const onceListener: Listener = (data, key) => { handled = true; events.off(eventKey, onceListener); listener(data, key); }; events.on(eventKey, onceListener); this.onDispose.push(() => { if (handled) { return; } events.off(eventKey, onceListener); }); } stopListening() { for (const unsubscribe of this.onDispose) { unsubscribe(); } this.onDispose.length = 0; this.unsubscribeByKey.forEach(unsubscribers => { for (const unsubscribe of unsubscribers) { unsubscribe(); } }); this.unsubscribeByKey.clear(); } }