All files / events emitter.ts

100% Statements 32/32
100% Branches 10/10
100% Functions 10/10
100% Lines 28/28

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81                                                          485x 485x       1299x 1299x 1299x 1299x 1299x 1299x 1262x 1255x 1255x 1255x     485x     3x 3x 3x     8x 8x 8x   8x     2409x 2409x     79x     65x     2409x 2409x 2407x     482x        
import type { RuntimeEvent, RuntimeEventListener, Unsubscribe } from '../core/types';
 
/** Typed event emitter with deterministic delivery and cancellable events. @internal */
export interface TypedEventEmitter<TEvents extends object> {
  /** Registers a listener and returns an idempotent unsubscribe function. */
  on<TKey extends keyof TEvents>(
    type: TKey,
    listener: RuntimeEventListener<TEvents[TKey]>,
  ): Unsubscribe;
  /** Removes a listener registered for the specified event. */
  off<TKey extends keyof TEvents>(type: TKey, listener: RuntimeEventListener<TEvents[TKey]>): void;
  /** Registers a listener for one delivery. */
  once<TKey extends keyof TEvents>(
    type: TKey,
    listener: RuntimeEventListener<TEvents[TKey]>,
  ): Unsubscribe;
  /** Delivers a typed payload and returns false when a listener cancelled it. */
  emit<TKey extends keyof TEvents>(type: TKey, detail: TEvents[TKey]): boolean;
  /** Removes every listener. */
  clear(): void;
}
 
/**
 * Creates an emitter whose listener snapshot is stable during each emission.
 *
 * @remarks Listener additions/removals during emission affect the next emission only.
 * @internal
 */
export function createEventEmitter<TEvents extends object>(): TypedEventEmitter<TEvents> {
  const listeners = new Map<keyof TEvents, Set<RuntimeEventListener<unknown>>>();
  const on = <TKey extends keyof TEvents>(
    type: TKey,
    listener: RuntimeEventListener<TEvents[TKey]>,
  ): Unsubscribe => {
    const bucket = listeners.get(type) ?? new Set<RuntimeEventListener<unknown>>();
    listeners.set(type, bucket);
    const genericListener = listener as RuntimeEventListener<unknown>;
    bucket.add(genericListener);
    let active = true;
    return () => {
      if (!active) return;
      active = false;
      bucket.delete(genericListener);
      if (bucket.size === 0) listeners.delete(type);
    };
  };
  return {
    on,
    off(type, listener) {
      const bucket = listeners.get(type);
      bucket?.delete(listener as RuntimeEventListener<unknown>);
      if (bucket?.size === 0) listeners.delete(type);
    },
    once(type, listener) {
      const unsubscribe: Unsubscribe = on(type, (event) => {
        unsubscribe();
        listener(event);
      });
      return unsubscribe;
    },
    emit(type, detail) {
      let prevented = false;
      const event: RuntimeEvent<TEvents[typeof type]> = {
        detail,
        get defaultPrevented() {
          return prevented;
        },
        preventDefault() {
          prevented = true;
        },
      };
      const snapshot = [...(listeners.get(type) ?? [])];
      for (const listener of snapshot) listener(event);
      return !prevented;
    },
    clear() {
      listeners.clear();
    },
  };
}