// Locally adapted from tiny-emitter; upstream reference 2.1.0. See THIRD_PARTY_NOTICES. type EventMap = { [Name in keyof Events]: readonly unknown[] } type EventArgs, Name extends PropertyKey, Custom extends unknown[]> = Name extends keyof Events ? [...Events[Name]] : Custom type Listener = ((...args: Args) => unknown) & { _?: (...args: Args) => unknown } interface Registration { fn: Listener ctx: unknown } type Registry = Partial[]>> const owns = (object: object, name: PropertyKey) => Object.prototype.hasOwnProperty.call(object, name) export default class Emitter = Record> { declare e?: Registry on(name: Name, fn: (this: Context, ...args: EventArgs) => unknown, ctx?: Context): this { const e: Registry = this.e || (this.e = {}) let listeners = owns(e, name) ? e[name] : undefined if (!listeners) { listeners = [] Object.defineProperty(e, name, { value: listeners, enumerable: true, configurable: true, writable: true }) } listeners.push({ fn, ctx }) return this } once(name: Name, fn: (this: Context, ...args: EventArgs) => unknown, ctx?: Context): this { // eslint-disable-next-line ts/no-this-alias -- Preserve the owning emitter independently of callback ctx. const self = this const callback: Listener> = fn let fired = false function listener(...args: EventArgs) { if (fired) return fired = true self.off(name, listener) callback.apply(ctx, args) } listener._ = fn return this.on(name, listener, ctx) } emit(name: Name, ...data: EventArgs): this { const e: Registry = this.e || (this.e = {}) const evtArr = (owns(e, name) ? e[name] || [] : []).slice() for (let i = 0; i < evtArr.length; i += 1) { // The registry erases heterogeneous listener tuples; emit checks its own payload. (evtArr[i]!.fn as Listener).apply(evtArr[i]!.ctx, data) } return this } off(name: Name, callback?: Listener>): this { const e: Registry = this.e || (this.e = {}) const evts = owns(e, name) ? e[name] : undefined const liveEvents: Registration[] = [] if (evts && callback) { for (let i = 0, len = evts.length; i < len; i += 1) { if (evts[i]!.fn !== callback && evts[i]!.fn._ !== callback) liveEvents.push(evts[i]!) } } if (liveEvents.length) { e[name] = liveEvents } else { delete e[name] } return this } }