event-emission 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +158 -25
  2. package/dist/event-emission.d.ts +63 -14
  3. package/dist/event-emission.d.ts.map +1 -1
  4. package/dist/factory.d.ts +1 -1
  5. package/dist/factory.d.ts.map +1 -1
  6. package/dist/index.cjs +614 -291
  7. package/dist/index.cjs.map +8 -9
  8. package/dist/index.d.ts +1 -7
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +589 -284
  11. package/dist/index.js.map +8 -9
  12. package/dist/interoperability.cjs +1719 -0
  13. package/dist/interoperability.cjs.map +15 -0
  14. package/dist/{interop.d.ts → interoperability.d.ts} +2 -1
  15. package/dist/interoperability.d.ts.map +1 -0
  16. package/dist/interoperability.js +1669 -0
  17. package/dist/interoperability.js.map +15 -0
  18. package/dist/observable.cjs +286 -0
  19. package/dist/observable.cjs.map +11 -0
  20. package/dist/observable.js +253 -0
  21. package/dist/observable.js.map +11 -0
  22. package/dist/observe.cjs +344 -0
  23. package/dist/observe.cjs.map +10 -0
  24. package/dist/observe.d.ts +6 -1
  25. package/dist/observe.d.ts.map +1 -1
  26. package/dist/observe.js +313 -0
  27. package/dist/observe.js.map +10 -0
  28. package/dist/symbols.d.ts +1 -1
  29. package/dist/types.cjs +35 -0
  30. package/dist/types.cjs.map +9 -0
  31. package/dist/types.d.ts +73 -25
  32. package/dist/types.d.ts.map +1 -1
  33. package/dist/types.js +3 -0
  34. package/dist/types.js.map +9 -0
  35. package/package.json +25 -1
  36. package/src/event-emission.ts +140 -21
  37. package/src/factory.ts +686 -230
  38. package/src/index.ts +4 -33
  39. package/src/{interop.ts → interoperability.ts} +34 -6
  40. package/src/observe.ts +54 -17
  41. package/src/symbols.ts +1 -1
  42. package/src/types.ts +115 -33
  43. package/dist/interop.d.ts.map +0 -1
package/src/factory.ts CHANGED
@@ -7,8 +7,10 @@ import {
7
7
  } from './observe';
8
8
  import { SymbolObservable } from './symbols';
9
9
  import type {
10
+ AddEventListenerOptionsLike,
10
11
  AsyncIteratorOptions,
11
12
  EmissionEvent,
13
+ EventListenerLike,
12
14
  EventTargetLike,
13
15
  Listener,
14
16
  ObservableLike,
@@ -23,13 +25,40 @@ import type {
23
25
  */
24
26
  function matchesWildcard(eventType: string, pattern: string): boolean {
25
27
  if (pattern === '*') return true;
26
- if (pattern.endsWith(':*')) {
27
- const namespace = pattern.slice(0, -2);
28
- return eventType.startsWith(namespace + ':');
29
- }
30
- return false;
28
+ return pattern.endsWith(':*') && eventType.startsWith(pattern.slice(0, -2) + ':');
31
29
  }
32
30
 
31
+ type EventPathStruct = {
32
+ invocationTarget: unknown;
33
+ invocationTargetInShadowTree: boolean;
34
+ shadowAdjustedTarget: unknown;
35
+ relatedTarget: unknown;
36
+ touchTargets: unknown[];
37
+ rootOfClosedTree: boolean;
38
+ slotInClosedTree: boolean;
39
+ };
40
+
41
+ type EventDispatchState = {
42
+ dispatchFlag: boolean;
43
+ initializedFlag: boolean;
44
+ stopPropagationFlag: boolean;
45
+ stopImmediatePropagationFlag: boolean;
46
+ canceledFlag: boolean;
47
+ inPassiveListenerFlag: boolean;
48
+ composedFlag: boolean;
49
+ eventPhase: number;
50
+ currentTarget: unknown;
51
+ target: unknown;
52
+ timeStamp: number;
53
+ path: EventPathStruct[];
54
+ type: string;
55
+ bubbles: boolean;
56
+ cancelable: boolean;
57
+ isTrusted: boolean;
58
+ };
59
+
60
+ const EVENT_STATE = Symbol('event-emission:event-state');
61
+
33
62
  /**
34
63
  * Options for createEventTarget.
35
64
  *
@@ -46,7 +75,7 @@ export interface CreateEventTargetOptions {
46
75
  * Extends CreateEventTargetOptions with proxy observation settings.
47
76
  *
48
77
  * @property observe - Must be true to enable observation mode.
49
- * @property deep - If true, nested objects are also observed (default: false).
78
+ * @property deep - If true, nested objects are also observed (default: true).
50
79
  * @property cloneStrategy - Strategy for cloning previous state: 'shallow', 'deep', or 'path'.
51
80
  */
52
81
  export interface CreateEventTargetObserveOptions
@@ -159,6 +188,7 @@ export function createEventTarget<T extends object, E extends Record<string, any
159
188
  const proxy = createObservableProxy(target, eventTarget, {
160
189
  deep: opts.deep,
161
190
  cloneStrategy: opts.cloneStrategy,
191
+ deepClone: opts.deepClone,
162
192
  });
163
193
 
164
194
  // Copy eventTarget methods onto the proxy
@@ -178,6 +208,16 @@ export function createEventTarget<T extends object, E extends Record<string, any
178
208
  'subscribe',
179
209
  'toObservable',
180
210
  'events',
211
+ 'emit',
212
+ 'off',
213
+ 'addListener',
214
+ 'removeListener',
215
+ 'prependListener',
216
+ 'prependOnceListener',
217
+ 'listeners',
218
+ 'rawListeners',
219
+ 'listenerCount',
220
+ 'eventNames',
181
221
  ] as const;
182
222
 
183
223
  for (const name of methodNames) {
@@ -209,50 +249,238 @@ export function createEventTarget<T extends object, E extends Record<string, any
209
249
  function createEventTargetInternal<E extends Record<string, any>>(
210
250
  opts?: CreateEventTargetOptions,
211
251
  ): EventTargetLike<E> {
212
- const listeners = new Map<string, Set<Listener<E[keyof E]>>>();
252
+ const listeners = new Map<string, Array<Listener<E[keyof E]>>>();
213
253
  const wildcardListeners = new Set<WildcardListener<E>>();
214
254
  let isCompleted = false;
215
255
  const completionCallbacks = new Set<() => void>();
216
256
 
257
+ const now = () =>
258
+ typeof globalThis.performance?.now === 'function'
259
+ ? globalThis.performance.now()
260
+ : Date.now();
261
+
262
+ const initializeEventState = (
263
+ state: EventDispatchState,
264
+ type: string,
265
+ bubbles: boolean,
266
+ cancelable: boolean,
267
+ ) => {
268
+ state.initializedFlag = true;
269
+ state.stopPropagationFlag = false;
270
+ state.stopImmediatePropagationFlag = false;
271
+ state.canceledFlag = false;
272
+ state.isTrusted = false;
273
+ state.target = null;
274
+ state.currentTarget = null;
275
+ state.eventPhase = 0;
276
+ state.type = type;
277
+ state.bubbles = bubbles;
278
+ state.cancelable = cancelable;
279
+ };
280
+
281
+ const setCanceledFlag = (state: EventDispatchState) => {
282
+ if (state.cancelable && !state.inPassiveListenerFlag) {
283
+ state.canceledFlag = true;
284
+ }
285
+ };
286
+
217
287
  /**
218
- * Helper to create a DOM-compatible augmented event.
288
+ * Helper to create a DOM-compatible event.
219
289
  */
220
- const augmentEvent = <T>(type: string, detail: T): EmissionEvent<T> => {
221
- const baseEvent = {
222
- bubbles: false,
223
- cancelable: false,
224
- composed: false,
225
- defaultPrevented: false,
226
- eventPhase: 2, // AT_TARGET
290
+ const createEvent = <T>(
291
+ type: string,
292
+ detail: T,
293
+ init?: {
294
+ bubbles?: boolean;
295
+ cancelable?: boolean;
296
+ composed?: boolean;
297
+ timeStamp?: number;
298
+ target?: unknown;
299
+ currentTarget?: unknown;
300
+ eventPhase?: number;
301
+ },
302
+ ): EmissionEvent<T> => {
303
+ const state: EventDispatchState = {
304
+ dispatchFlag: false,
305
+ initializedFlag: true,
306
+ stopPropagationFlag: false,
307
+ stopImmediatePropagationFlag: false,
308
+ canceledFlag: false,
309
+ inPassiveListenerFlag: false,
310
+ composedFlag: Boolean(init?.composed),
311
+ eventPhase: init?.eventPhase ?? 0,
312
+ currentTarget: init?.currentTarget ?? init?.target ?? null,
313
+ target: init?.target ?? null,
314
+ timeStamp: init?.timeStamp ?? now(),
315
+ path: [],
316
+ type,
317
+ bubbles: Boolean(init?.bubbles),
318
+ cancelable: Boolean(init?.cancelable),
227
319
  isTrusted: false,
228
- timeStamp: Date.now(),
229
- NONE: 0,
230
- CAPTURING_PHASE: 1,
231
- AT_TARGET: 2,
232
- BUBBLING_PHASE: 3,
233
- composedPath: () => [target],
234
- stopImmediatePropagation: () => {},
235
- stopPropagation: () => {},
236
320
  };
237
321
 
238
- return Object.defineProperties(
239
- { ...baseEvent, type, detail },
240
- {
241
- target: { value: target, enumerable: true, configurable: true },
242
- currentTarget: { value: target, enumerable: true, configurable: true },
243
- preventDefault: {
244
- value: function (this: EmissionEvent<unknown>) {
245
- Object.defineProperty(this, 'defaultPrevented', {
246
- value: true,
247
- enumerable: true,
248
- configurable: true,
249
- });
250
- },
251
- enumerable: true,
252
- configurable: true,
322
+ const event = { detail } as EmissionEvent<T>;
323
+ Object.defineProperties(event, {
324
+ type: {
325
+ get: () => state.type,
326
+ enumerable: true,
327
+ configurable: true,
328
+ },
329
+ bubbles: {
330
+ get: () => state.bubbles,
331
+ enumerable: true,
332
+ configurable: true,
333
+ },
334
+ cancelable: {
335
+ get: () => state.cancelable,
336
+ enumerable: true,
337
+ configurable: true,
338
+ },
339
+ cancelBubble: {
340
+ get: () => state.stopPropagationFlag,
341
+ set: (value: boolean) => {
342
+ if (value) state.stopPropagationFlag = true;
343
+ },
344
+ enumerable: true,
345
+ configurable: true,
346
+ },
347
+ composed: {
348
+ get: () => state.composedFlag,
349
+ enumerable: true,
350
+ configurable: true,
351
+ },
352
+ currentTarget: {
353
+ get: () => state.currentTarget,
354
+ enumerable: true,
355
+ configurable: true,
356
+ },
357
+ defaultPrevented: {
358
+ get: () => state.canceledFlag,
359
+ enumerable: true,
360
+ configurable: true,
361
+ },
362
+ eventPhase: {
363
+ get: () => state.eventPhase,
364
+ enumerable: true,
365
+ configurable: true,
366
+ },
367
+ isTrusted: {
368
+ get: () => state.isTrusted,
369
+ enumerable: true,
370
+ configurable: true,
371
+ },
372
+ returnValue: {
373
+ get: () => !state.canceledFlag,
374
+ set: (value: boolean) => {
375
+ if (value === false) setCanceledFlag(state);
253
376
  },
377
+ enumerable: true,
378
+ configurable: true,
254
379
  },
255
- ) as EmissionEvent<T>;
380
+ srcElement: {
381
+ get: () => state.target,
382
+ enumerable: true,
383
+ configurable: true,
384
+ },
385
+ target: {
386
+ get: () => state.target,
387
+ enumerable: true,
388
+ configurable: true,
389
+ },
390
+ timeStamp: {
391
+ get: () => state.timeStamp,
392
+ enumerable: true,
393
+ configurable: true,
394
+ },
395
+ composedPath: {
396
+ value: () => state.path.map((entry) => entry.invocationTarget),
397
+ enumerable: true,
398
+ configurable: true,
399
+ },
400
+ initEvent: {
401
+ value: (newType: string, bubbles = false, cancelable = false) => {
402
+ if (state.dispatchFlag) return;
403
+ initializeEventState(state, newType, Boolean(bubbles), Boolean(cancelable));
404
+ },
405
+ enumerable: true,
406
+ configurable: true,
407
+ },
408
+ preventDefault: {
409
+ value: () => setCanceledFlag(state),
410
+ enumerable: true,
411
+ configurable: true,
412
+ },
413
+ stopImmediatePropagation: {
414
+ value: () => {
415
+ state.stopPropagationFlag = true;
416
+ state.stopImmediatePropagationFlag = true;
417
+ },
418
+ enumerable: true,
419
+ configurable: true,
420
+ },
421
+ stopPropagation: {
422
+ value: () => {
423
+ state.stopPropagationFlag = true;
424
+ },
425
+ enumerable: true,
426
+ configurable: true,
427
+ },
428
+ NONE: { value: 0, enumerable: true, configurable: true },
429
+ CAPTURING_PHASE: { value: 1, enumerable: true, configurable: true },
430
+ AT_TARGET: { value: 2, enumerable: true, configurable: true },
431
+ BUBBLING_PHASE: { value: 3, enumerable: true, configurable: true },
432
+ [EVENT_STATE]: {
433
+ value: state,
434
+ enumerable: false,
435
+ configurable: false,
436
+ },
437
+ });
438
+
439
+ return event;
440
+ };
441
+
442
+ const getEventState = (event: EmissionEvent<unknown>): EventDispatchState | undefined =>
443
+ (event as { [EVENT_STATE]?: EventDispatchState })[EVENT_STATE];
444
+
445
+ const normalizeAddListenerOptions = (
446
+ options?: AddEventListenerOptionsLike | boolean,
447
+ ) => {
448
+ if (typeof options === 'boolean') {
449
+ return {
450
+ capture: options,
451
+ passive: false,
452
+ once: false,
453
+ signal: null as AddEventListenerOptionsLike['signal'] | null,
454
+ };
455
+ }
456
+
457
+ return {
458
+ capture: Boolean(options?.capture),
459
+ passive: Boolean(options?.passive),
460
+ once: Boolean(options?.once),
461
+ signal: options?.signal ?? null,
462
+ };
463
+ };
464
+
465
+ const normalizeCaptureOption = (options?: { capture?: boolean } | boolean) => {
466
+ if (typeof options === 'boolean') return options;
467
+ return Boolean(options?.capture);
468
+ };
469
+
470
+ const removeListenerRecord = (type: string, record: Listener<E[keyof E]>) => {
471
+ if (record.removed) return;
472
+ record.removed = true;
473
+
474
+ const list = listeners.get(type);
475
+ if (list) {
476
+ const idx = list.indexOf(record);
477
+ if (idx >= 0) list.splice(idx, 1);
478
+ if (list.length === 0) listeners.delete(type);
479
+ }
480
+
481
+ if (record.signal && record.abortHandler) {
482
+ record.signal.removeEventListener('abort', record.abortHandler);
483
+ }
256
484
  };
257
485
 
258
486
  // Helper to handle listener errors: emit 'error' event or re-throw if no listener
@@ -267,33 +495,35 @@ function createEventTargetInternal<E extends Record<string, any>>(
267
495
  }
268
496
 
269
497
  const errorListeners = listeners.get('error');
270
- if (errorListeners && errorListeners.size > 0) {
271
- // Emit 'error' event with the error as detail
272
- const errorEvent = augmentEvent('error', error);
273
- for (const rec of Array.from(errorListeners)) {
274
- try {
275
- const fn = rec.fn as (event: EmissionEvent<unknown>) => void | Promise<void>;
276
- void fn(errorEvent as EmissionEvent<E[keyof E]>);
277
- } catch {
278
- // Swallow errors from error handlers to prevent infinite loops
279
- }
280
- if (rec.once) errorListeners.delete(rec);
281
- }
498
+ if (errorListeners && errorListeners.length > 0) {
499
+ dispatchEvent({ type: 'error', detail: error } as Parameters<
500
+ EventTargetLike<E>['dispatchEvent']
501
+ >[0]);
282
502
  } else {
283
503
  // No 'error' listener - re-throw (Node.js behavior)
284
504
  throw error;
285
505
  }
286
506
  };
287
507
 
288
- const notifyWildcardListeners = (eventType: string, detail: E[keyof E]) => {
508
+ const notifyWildcardListeners = (
509
+ eventType: string,
510
+ event: EmissionEvent<E[keyof E]>,
511
+ ) => {
289
512
  if (wildcardListeners.size === 0) return;
290
513
 
291
514
  for (const rec of Array.from(wildcardListeners)) {
292
515
  if (!matchesWildcard(eventType, rec.pattern)) continue;
293
516
 
294
- // Create wildcard event based on augmented event
295
- const baseAugmented = augmentEvent(rec.pattern, detail);
296
- const wildcardEvent: WildcardEvent<E> = Object.defineProperties(baseAugmented, {
517
+ // Create wildcard event based on a DOM-compatible event
518
+ const baseEvent = createEvent(rec.pattern, event.detail, {
519
+ target,
520
+ currentTarget: target,
521
+ eventPhase: 2,
522
+ bubbles: event.bubbles,
523
+ cancelable: event.cancelable,
524
+ composed: event.composed,
525
+ });
526
+ const wildcardEvent: WildcardEvent<E> = Object.defineProperties(baseEvent, {
297
527
  originalType: { value: eventType, enumerable: true, configurable: true },
298
528
  }) as WildcardEvent<E>;
299
529
 
@@ -317,47 +547,87 @@ function createEventTargetInternal<E extends Record<string, any>>(
317
547
  } finally {
318
548
  if (rec.once) wildcardListeners.delete(rec);
319
549
  }
550
+
551
+ const state = getEventState(wildcardEvent);
552
+ if (state?.stopImmediatePropagationFlag || state?.stopPropagationFlag) {
553
+ break;
554
+ }
320
555
  }
321
556
  };
322
557
 
323
- const addEventListener: EventTargetLike<E>['addEventListener'] = (
324
- type,
325
- listener,
326
- options,
327
- ) => {
328
- if (isCompleted) {
329
- // Return no-op unsubscribe if already completed
558
+ const addListenerInternal = (
559
+ type: string,
560
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Internal helper; callers provide type-safe wrappers
561
+ listener: EventListenerLike<any> | null,
562
+ options: AddEventListenerOptionsLike | boolean | undefined,
563
+ position: 'append' | 'prepend',
564
+ ): (() => void) => {
565
+ if (isCompleted || !listener) {
330
566
  return () => {};
331
567
  }
332
568
 
333
- const opts2 = options ?? {};
334
- const record: Listener<E[keyof E]> = {
335
- fn: listener as Listener<E[keyof E]>['fn'],
336
- once: opts2.once,
337
- signal: opts2.signal,
338
- };
339
- let set = listeners.get(type);
340
- if (!set) {
341
- set = new Set();
342
- listeners.set(type, set);
569
+ const { capture, passive, once, signal } = normalizeAddListenerOptions(options);
570
+ let list = listeners.get(type);
571
+ if (!list) {
572
+ list = [];
573
+ listeners.set(type, list);
343
574
  }
344
- set.add(record);
345
- const unsubscribe = () => {
346
- const setNow = listeners.get(type);
347
- setNow?.delete(record);
348
- if (record.signal && record.abortHandler) {
349
- record.signal.removeEventListener('abort', record.abortHandler);
575
+
576
+ for (const existing of list) {
577
+ if (existing.original === listener && existing.capture === capture) {
578
+ return () =>
579
+ removeEventListener(type, listener, options as boolean | { capture?: boolean });
350
580
  }
581
+ }
582
+
583
+ const original = listener as Listener<E[keyof E]>['original'];
584
+ const callback =
585
+ typeof listener === 'function'
586
+ ? (listener as Listener<E[keyof E]>['callback'])
587
+ : (event: EmissionEvent<E[keyof E]>) =>
588
+ (
589
+ listener as {
590
+ handleEvent: Listener<E[keyof E]>['callback'];
591
+ }
592
+ ).handleEvent(event);
593
+
594
+ const record: Listener<E[keyof E]> = {
595
+ type,
596
+ original,
597
+ callback,
598
+ capture,
599
+ passive,
600
+ once,
601
+ signal,
602
+ removed: false,
351
603
  };
352
- if (opts2.signal) {
353
- const onAbort = () => unsubscribe();
604
+
605
+ if (position === 'prepend') {
606
+ list.unshift(record);
607
+ } else {
608
+ list.push(record);
609
+ }
610
+
611
+ const unsubscribe = () => removeListenerRecord(type, record);
612
+
613
+ if (signal) {
614
+ const onAbort = () => removeListenerRecord(type, record);
354
615
  record.abortHandler = onAbort;
355
- opts2.signal.addEventListener('abort', onAbort, { once: true });
356
- if (opts2.signal.aborted) onAbort();
616
+ signal.addEventListener('abort', onAbort, { once: true });
617
+ if (signal.aborted) onAbort();
357
618
  }
619
+
358
620
  return unsubscribe;
359
621
  };
360
622
 
623
+ const addEventListener: EventTargetLike<E>['addEventListener'] = (
624
+ type,
625
+ listener,
626
+ options,
627
+ ) => {
628
+ return addListenerInternal(type, listener, options, 'append');
629
+ };
630
+
361
631
  const addWildcardListener: EventTargetLike<E>['addWildcardListener'] = (
362
632
  pattern,
363
633
  listener,
@@ -366,6 +636,11 @@ function createEventTargetInternal<E extends Record<string, any>>(
366
636
  if (isCompleted) return () => {};
367
637
 
368
638
  const opts2 = options ?? {};
639
+ for (const existing of wildcardListeners) {
640
+ if (existing.pattern === pattern && existing.fn === listener) {
641
+ return () => removeWildcardListener(pattern, listener);
642
+ }
643
+ }
369
644
  const record: WildcardListener<E> = {
370
645
  fn: listener,
371
646
  pattern,
@@ -395,37 +670,44 @@ function createEventTargetInternal<E extends Record<string, any>>(
395
670
  pattern,
396
671
  listener,
397
672
  ) => {
398
- for (const record of wildcardListeners) {
673
+ for (const record of Array.from(wildcardListeners)) {
399
674
  if (record.pattern === pattern && record.fn === listener) {
400
675
  wildcardListeners.delete(record);
401
676
  if (record.signal && record.abortHandler) {
402
677
  record.signal.removeEventListener('abort', record.abortHandler);
403
678
  }
404
- break;
405
679
  }
406
680
  }
407
681
  };
408
682
 
409
- const dispatchEvent: EventTargetLike<E>['dispatchEvent'] = (event) => {
410
- if (isCompleted) return false;
683
+ const invokeListeners = (
684
+ eventType: string,
685
+ event: EmissionEvent<E[keyof E]>,
686
+ phase: 'capturing' | 'bubbling',
687
+ listenersSnapshot: Array<Listener<E[keyof E]>>,
688
+ ) => {
689
+ const state = getEventState(event);
690
+ if (!state || state.stopPropagationFlag) return;
411
691
 
412
- // Augment event with DOM properties to ensure it's a superset of DOM Event
413
- const augmentedEvent = augmentEvent(event.type, event.detail);
692
+ state.currentTarget = target;
693
+ state.target = target;
694
+ state.eventPhase = event.AT_TARGET;
414
695
 
415
- // Notify wildcard listeners first (no overhead if none registered)
416
- notifyWildcardListeners(event.type, event.detail as E[keyof E]);
696
+ for (const rec of listenersSnapshot) {
697
+ if (rec.removed) continue;
698
+ if (phase === 'capturing' && !rec.capture) continue;
699
+ if (phase === 'bubbling' && rec.capture) continue;
700
+
701
+ if (rec.once) removeListenerRecord(rec.type, rec);
417
702
 
418
- const set = listeners.get(event.type);
419
- if (!set || set.size === 0) return true;
420
- for (const rec of Array.from(set)) {
703
+ if (rec.passive) state.inPassiveListenerFlag = true;
421
704
  try {
422
- const res = rec.fn(augmentedEvent as EmissionEvent<E[keyof E]>);
705
+ const res = rec.callback.call(state.currentTarget, event);
423
706
  if (res && typeof res.then === 'function') {
424
707
  res.catch((error) => {
425
708
  try {
426
- handleListenerError(event.type, error);
709
+ handleListenerError(eventType, error);
427
710
  } catch (rethrown) {
428
- // Re-throw async errors via queueMicrotask to preserve stack trace
429
711
  queueMicrotask(() => {
430
712
  throw rethrown;
431
713
  });
@@ -433,41 +715,116 @@ function createEventTargetInternal<E extends Record<string, any>>(
433
715
  });
434
716
  }
435
717
  } catch (error) {
436
- handleListenerError(event.type, error);
718
+ handleListenerError(eventType, error);
437
719
  } finally {
438
- if (rec.once) set.delete(rec);
720
+ if (rec.passive) state.inPassiveListenerFlag = false;
439
721
  }
722
+
723
+ if (state.stopImmediatePropagationFlag) break;
440
724
  }
441
- return true;
725
+ };
726
+
727
+ const dispatchEvent: EventTargetLike<E>['dispatchEvent'] = (eventInput) => {
728
+ if (isCompleted) return false;
729
+
730
+ let event: EmissionEvent<E[keyof E]>;
731
+ let state: EventDispatchState | undefined;
732
+
733
+ if (eventInput && typeof eventInput === 'object') {
734
+ state = getEventState(eventInput as EmissionEvent<unknown>);
735
+ if (state) {
736
+ event = eventInput as EmissionEvent<E[keyof E]>;
737
+ } else {
738
+ const input = eventInput as {
739
+ type: string;
740
+ detail?: E[keyof E];
741
+ bubbles?: boolean;
742
+ cancelable?: boolean;
743
+ composed?: boolean;
744
+ timeStamp?: number;
745
+ };
746
+ if (typeof input.type !== 'string') {
747
+ throw new TypeError('Event type must be a string');
748
+ }
749
+ event = createEvent(input.type, input.detail as E[keyof E], {
750
+ bubbles: input.bubbles,
751
+ cancelable: input.cancelable,
752
+ composed: input.composed,
753
+ timeStamp: input.timeStamp,
754
+ });
755
+ state = getEventState(event)!;
756
+ }
757
+ } else {
758
+ throw new TypeError('dispatchEvent expects an event object');
759
+ }
760
+
761
+ const dispatchState = state ?? getEventState(event)!;
762
+
763
+ if (dispatchState.dispatchFlag || !dispatchState.initializedFlag) {
764
+ const message =
765
+ 'Failed to execute dispatchEvent: event is already being dispatched';
766
+ if (typeof globalThis.DOMException === 'function') {
767
+ throw new globalThis.DOMException(message, 'InvalidStateError');
768
+ }
769
+ const err = new Error(message);
770
+ err.name = 'InvalidStateError';
771
+ throw err;
772
+ }
773
+
774
+ dispatchState.isTrusted = false;
775
+ dispatchState.dispatchFlag = true;
776
+ dispatchState.path = [
777
+ {
778
+ invocationTarget: target,
779
+ invocationTargetInShadowTree: false,
780
+ shadowAdjustedTarget: target,
781
+ relatedTarget: null,
782
+ touchTargets: [],
783
+ rootOfClosedTree: false,
784
+ slotInClosedTree: false,
785
+ },
786
+ ];
787
+
788
+ // Notify wildcard listeners first (no overhead if none registered)
789
+ notifyWildcardListeners(dispatchState.type, event);
790
+
791
+ const list = listeners.get(dispatchState.type);
792
+ const snapshot = list ? list.slice() : [];
793
+ invokeListeners(dispatchState.type, event, 'capturing', snapshot);
794
+ invokeListeners(dispatchState.type, event, 'bubbling', snapshot);
795
+
796
+ dispatchState.eventPhase = event.NONE;
797
+ dispatchState.currentTarget = null;
798
+ dispatchState.path = [];
799
+ dispatchState.dispatchFlag = false;
800
+ dispatchState.stopPropagationFlag = false;
801
+ dispatchState.stopImmediatePropagationFlag = false;
802
+
803
+ return !dispatchState.canceledFlag;
442
804
  };
443
805
 
444
806
  const removeEventListener: EventTargetLike<E>['removeEventListener'] = (
445
807
  type,
446
808
  listener,
809
+ options,
447
810
  ) => {
448
- const set = listeners.get(type);
449
- if (!set) return;
450
-
451
- for (const record of set) {
452
- if (record.fn === listener) {
453
- set.delete(record);
454
- if (record.signal && record.abortHandler) {
455
- record.signal.removeEventListener('abort', record.abortHandler);
456
- }
457
- break;
811
+ if (!listener) return;
812
+ const capture = normalizeCaptureOption(options);
813
+ const list = listeners.get(type);
814
+ if (!list) return;
815
+
816
+ for (const record of [...list]) {
817
+ if (record.original === listener && record.capture === capture) {
818
+ removeListenerRecord(type, record);
458
819
  }
459
820
  }
460
821
  };
461
822
 
462
823
  const clear = () => {
463
- // Clean up abort handlers before clearing
464
- for (const set of listeners.values()) {
465
- for (const record of set) {
466
- if (record.signal && record.abortHandler) {
467
- record.signal.removeEventListener('abort', record.abortHandler);
468
- }
824
+ for (const [type, list] of Array.from(listeners.entries())) {
825
+ for (const record of [...list]) {
826
+ removeListenerRecord(type, record);
469
827
  }
470
- set.clear();
471
828
  }
472
829
  listeners.clear();
473
830
 
@@ -484,14 +841,33 @@ function createEventTargetInternal<E extends Record<string, any>>(
484
841
 
485
842
  // New ergonomics
486
843
 
487
- const on: EventTargetLike<E>['on'] = (type, options) => {
488
- /* eslint-disable @typescript-eslint/no-explicit-any */
844
+ const isListenerArg = (
845
+ arg: unknown,
846
+ ): arg is EventListenerLike<EmissionEvent<unknown>> => {
847
+ if (typeof arg === 'function') return true;
848
+ if (
849
+ arg &&
850
+ typeof arg === 'object' &&
851
+ typeof (arg as { handleEvent?: unknown }).handleEvent === 'function'
852
+ )
853
+ return true;
854
+ return false;
855
+ };
856
+
857
+ /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument */
858
+ const on: EventTargetLike<E>['on'] = ((type: any, optionsOrListener: any) => {
859
+ // Node-style overload: on(type, listener) → () => void
860
+ if (isListenerArg(optionsOrListener)) {
861
+ return addEventListener(type, optionsOrListener);
862
+ }
863
+
864
+ // Observable-style: on(type, options?) → ObservableLike
865
+ const options = optionsOrListener as OnOptions | boolean | undefined;
489
866
  return new Observable<EmissionEvent<any>>(
490
867
  (observer: SubscriptionObserver<EmissionEvent<any>>) => {
491
- /* eslint-enable @typescript-eslint/no-explicit-any */
492
868
  let opts: OnOptions;
493
869
  if (typeof options === 'boolean') {
494
- opts = { signal: undefined }; // Map boolean capture to options if needed, but our internal target doesn't use capture
870
+ opts = { capture: options };
495
871
  } else {
496
872
  opts = options ?? {};
497
873
  }
@@ -518,7 +894,6 @@ function createEventTargetInternal<E extends Record<string, any>>(
518
894
  observer.error(e.detail);
519
895
  };
520
896
 
521
- /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument */
522
897
  const unsubscribeEvent = addEventListener(
523
898
  type as keyof E & string,
524
899
  eventHandler as any,
@@ -533,7 +908,6 @@ function createEventTargetInternal<E extends Record<string, any>>(
533
908
  opts,
534
909
  );
535
910
  }
536
- /* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument */
537
911
 
538
912
  return () => {
539
913
  unsubscribeEvent();
@@ -543,34 +917,31 @@ function createEventTargetInternal<E extends Record<string, any>>(
543
917
  };
544
918
  },
545
919
  );
546
- };
920
+ }) as EventTargetLike<E>['on'];
921
+ /* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument */
547
922
 
548
923
  const onceMethod: EventTargetLike<E>['once'] = (type, listener, options) => {
549
- return addEventListener(type, listener, { ...options, once: true });
924
+ if (typeof options === 'boolean') {
925
+ return addEventListener(type, listener, { capture: options, once: true });
926
+ }
927
+ return addEventListener(type, listener, { ...(options ?? {}), once: true });
550
928
  };
551
929
 
552
930
  const removeAllListeners: EventTargetLike<E>['removeAllListeners'] = (type) => {
553
931
  if (type !== undefined) {
554
- const set = listeners.get(type);
555
- if (set) {
556
- // Clean up abort handlers before clearing
557
- for (const record of set) {
558
- if (record.signal && record.abortHandler) {
559
- record.signal.removeEventListener('abort', record.abortHandler);
560
- }
932
+ const list = listeners.get(type);
933
+ if (list) {
934
+ for (const record of [...list]) {
935
+ removeListenerRecord(type, record);
561
936
  }
562
- set.clear();
563
937
  listeners.delete(type);
564
938
  }
565
939
  } else {
566
940
  // Clear all listeners for all types
567
- for (const set of listeners.values()) {
568
- for (const record of set) {
569
- if (record.signal && record.abortHandler) {
570
- record.signal.removeEventListener('abort', record.abortHandler);
571
- }
941
+ for (const [eventType, list] of Array.from(listeners.entries())) {
942
+ for (const record of [...list]) {
943
+ removeListenerRecord(eventType, record);
572
944
  }
573
- set.clear();
574
945
  }
575
946
  listeners.clear();
576
947
 
@@ -587,64 +958,49 @@ function createEventTargetInternal<E extends Record<string, any>>(
587
958
  /**
588
959
  * Pipe events from this emitter to another target.
589
960
  *
590
- * **Limitation**: Only forwards events for types that already have listeners
591
- * when pipe() is called. Events for types registered afterward won't be piped.
592
- *
593
- * To ensure all events are piped, add at least one listener for each event type
594
- * before calling pipe().
961
+ * Forwards all events. If mapFn returns null, the event is skipped.
595
962
  */
596
963
  const pipe: EventTargetLike<E>['pipe'] = (target, mapFn) => {
597
964
  if (isCompleted) {
598
965
  return () => {};
599
966
  }
600
967
 
601
- const unsubscribes: Array<() => void> = [];
602
-
603
- // Subscribe to all current and future events by listening to each event type
604
- // We need to track event types we've subscribed to
605
- const subscribedTypes = new Set<string>();
606
-
607
- const subscribeToType = (type: string) => {
608
- if (subscribedTypes.has(type)) return;
609
- subscribedTypes.add(type);
610
-
611
- const unsub = addEventListener(type as keyof E & string, (event) => {
612
- if (mapFn) {
613
- const mapped = mapFn(event);
614
- if (mapped !== null) {
615
- // Type assertion via unknown is needed because mapFn output type matches target's event map
616
- target.dispatchEvent(
617
- mapped as unknown as Parameters<typeof target.dispatchEvent>[0],
618
- );
619
- }
620
- } else {
621
- // Type assertion via unknown is needed because caller ensures E and T are compatible
968
+ const unsubscribe = addWildcardListener('*', (event) => {
969
+ if (mapFn) {
970
+ const mapped = mapFn(
971
+ createEvent(event.originalType, event.detail, {
972
+ target,
973
+ currentTarget: target,
974
+ eventPhase: 2,
975
+ bubbles: event.bubbles,
976
+ cancelable: event.cancelable,
977
+ composed: event.composed,
978
+ }) as EmissionEvent<E[keyof E & string], keyof E & string>,
979
+ );
980
+ if (mapped !== null) {
981
+ // Type assertion via unknown is needed because mapFn output type matches target's event map
622
982
  target.dispatchEvent(
623
- event as unknown as Parameters<typeof target.dispatchEvent>[0],
983
+ mapped as unknown as Parameters<typeof target.dispatchEvent>[0],
624
984
  );
625
985
  }
626
- });
627
- unsubscribes.push(unsub);
628
- };
629
-
630
- // Subscribe to all existing event types
631
- for (const type of listeners.keys()) {
632
- subscribeToType(type);
633
- }
986
+ } else {
987
+ // Type assertion via unknown is needed because caller ensures E and T are compatible
988
+ target.dispatchEvent({
989
+ type: event.originalType,
990
+ detail: event.detail,
991
+ } as unknown as Parameters<typeof target.dispatchEvent>[0]);
992
+ }
993
+ });
634
994
 
635
995
  // Clean up on completion
636
996
  const completionUnsub = () => {
637
- for (const unsub of unsubscribes) {
638
- unsub();
639
- }
997
+ unsubscribe();
640
998
  };
641
999
  completionCallbacks.add(completionUnsub);
642
1000
 
643
1001
  return () => {
644
1002
  completionCallbacks.delete(completionUnsub);
645
- for (const unsub of unsubscribes) {
646
- unsub();
647
- }
1003
+ unsubscribe();
648
1004
  };
649
1005
  };
650
1006
 
@@ -669,13 +1025,10 @@ function createEventTargetInternal<E extends Record<string, any>>(
669
1025
  completionCallbacks.clear();
670
1026
 
671
1027
  // Clear all listeners
672
- for (const set of listeners.values()) {
673
- for (const record of set) {
674
- if (record.signal && record.abortHandler) {
675
- record.signal.removeEventListener('abort', record.abortHandler);
676
- }
1028
+ for (const [eventType, list] of Array.from(listeners.entries())) {
1029
+ for (const record of [...list]) {
1030
+ removeListenerRecord(eventType, record);
677
1031
  }
678
- set.clear();
679
1032
  }
680
1033
  listeners.clear();
681
1034
 
@@ -688,7 +1041,7 @@ function createEventTargetInternal<E extends Record<string, any>>(
688
1041
  wildcardListeners.clear();
689
1042
  };
690
1043
 
691
- // Observable interop
1044
+ // Observable interoperability
692
1045
  const subscribe: EventTargetLike<E>['subscribe'] = (
693
1046
  type,
694
1047
  observerOrNext,
@@ -782,7 +1135,16 @@ function createEventTargetInternal<E extends Record<string, any>>(
782
1135
 
783
1136
  const wildcardListener = (event: WildcardEvent<E>) => {
784
1137
  // Emit an augmented event with the actual type
785
- observer.next(augmentEvent(event.originalType, event.detail));
1138
+ observer.next(
1139
+ createEvent(event.originalType, event.detail, {
1140
+ target,
1141
+ currentTarget: target,
1142
+ eventPhase: 2,
1143
+ bubbles: event.bubbles,
1144
+ cancelable: event.cancelable,
1145
+ composed: event.composed,
1146
+ }),
1147
+ );
786
1148
  };
787
1149
 
788
1150
  const unsubscribe = addWildcardListener('*', wildcardListener);
@@ -804,22 +1166,22 @@ function createEventTargetInternal<E extends Record<string, any>>(
804
1166
  function events<K extends keyof E & string>(
805
1167
  type: K,
806
1168
  options?: AsyncIteratorOptions,
807
- ): AsyncIterableIterator<EmissionEvent<E[K]>> {
1169
+ ): AsyncIterableIterator<EmissionEvent<E[K], K>> {
808
1170
  // If already completed, return an iterator that immediately yields done
809
1171
  if (isCompleted) {
810
1172
  return {
811
1173
  [Symbol.asyncIterator]() {
812
1174
  return this;
813
1175
  },
814
- next(): Promise<IteratorResult<EmissionEvent<E[K]>>> {
1176
+ next(): Promise<IteratorResult<EmissionEvent<E[K], K>>> {
815
1177
  return Promise.resolve({
816
- value: undefined as unknown as EmissionEvent<E[K]>,
1178
+ value: undefined as unknown as EmissionEvent<E[K], K>,
817
1179
  done: true,
818
1180
  });
819
1181
  },
820
- return(): Promise<IteratorResult<EmissionEvent<E[K]>>> {
1182
+ return(): Promise<IteratorResult<EmissionEvent<E[K], K>>> {
821
1183
  return Promise.resolve({
822
- value: undefined as unknown as EmissionEvent<E[K]>,
1184
+ value: undefined as unknown as EmissionEvent<E[K], K>,
823
1185
  done: true,
824
1186
  });
825
1187
  },
@@ -830,26 +1192,33 @@ function createEventTargetInternal<E extends Record<string, any>>(
830
1192
  const bufferSize = options?.bufferSize ?? Infinity;
831
1193
  const overflowStrategy = options?.overflowStrategy ?? 'drop-oldest';
832
1194
 
833
- const buffer: Array<EmissionEvent<E[K]>> = [];
834
- let resolve: ((result: IteratorResult<EmissionEvent<E[K]>>) => void) | null = null;
1195
+ const buffer: Array<EmissionEvent<E[K], K>> = [];
1196
+ let resolve: ((result: IteratorResult<EmissionEvent<E[K], K>>) => void) | null = null;
835
1197
  let done = false;
836
1198
  let hasOverflow = false;
1199
+ let onAbort: (() => void) | null = null;
1200
+ const cleanupAbortListener = () => {
1201
+ if (signal && onAbort) {
1202
+ signal.removeEventListener('abort', onAbort);
1203
+ }
1204
+ };
837
1205
 
838
1206
  const unsub = addEventListener(type, (event) => {
839
1207
  if (done) return;
1208
+ const typedEvent = event;
840
1209
 
841
1210
  if (resolve) {
842
1211
  // Someone is waiting, resolve immediately
843
1212
  const r = resolve;
844
1213
  resolve = null;
845
- r({ value: event, done: false });
1214
+ r({ value: typedEvent, done: false });
846
1215
  } else {
847
1216
  // Buffer the event
848
1217
  if (buffer.length >= bufferSize && bufferSize !== Infinity) {
849
1218
  switch (overflowStrategy) {
850
1219
  case 'drop-oldest':
851
1220
  buffer.shift();
852
- buffer.push(event);
1221
+ buffer.push(typedEvent);
853
1222
  break;
854
1223
  case 'drop-latest':
855
1224
  // Don't add the new event
@@ -859,10 +1228,11 @@ function createEventTargetInternal<E extends Record<string, any>>(
859
1228
  completionCallbacks.delete(onComplete);
860
1229
  done = true;
861
1230
  hasOverflow = true;
1231
+ cleanupAbortListener();
862
1232
  return;
863
1233
  }
864
1234
  } else {
865
- buffer.push(event);
1235
+ buffer.push(typedEvent);
866
1236
  }
867
1237
  }
868
1238
  });
@@ -870,16 +1240,16 @@ function createEventTargetInternal<E extends Record<string, any>>(
870
1240
  // Handle completion
871
1241
  const onComplete = () => {
872
1242
  done = true;
1243
+ cleanupAbortListener();
873
1244
  if (resolve) {
874
1245
  const r = resolve;
875
1246
  resolve = null;
876
- r({ value: undefined as unknown as EmissionEvent<E[K]>, done: true });
1247
+ r({ value: undefined as unknown as EmissionEvent<E[K], K>, done: true });
877
1248
  }
878
1249
  };
879
1250
  completionCallbacks.add(onComplete);
880
1251
 
881
1252
  // Handle abort signal
882
- let onAbort: (() => void) | null = null;
883
1253
  if (signal) {
884
1254
  onAbort = () => {
885
1255
  done = true;
@@ -888,33 +1258,23 @@ function createEventTargetInternal<E extends Record<string, any>>(
888
1258
  if (resolve) {
889
1259
  const r = resolve;
890
1260
  resolve = null;
891
- r({ value: undefined as unknown as EmissionEvent<E[K]>, done: true });
1261
+ r({ value: undefined as unknown as EmissionEvent<E[K], K>, done: true });
892
1262
  }
893
1263
  };
894
1264
  signal.addEventListener('abort', onAbort, { once: true });
895
1265
  if (signal.aborted) onAbort();
896
1266
  }
897
1267
 
898
- const iterator: AsyncIterableIterator<EmissionEvent<E[K]>> = {
1268
+ const iterator: AsyncIterableIterator<EmissionEvent<E[K], K>> = {
899
1269
  [Symbol.asyncIterator]() {
900
1270
  return this;
901
1271
  },
902
- async next(): Promise<IteratorResult<EmissionEvent<E[K]>>> {
1272
+ async next(): Promise<IteratorResult<EmissionEvent<E[K], K>>> {
903
1273
  // Drain buffered events first, even if done
904
1274
  if (buffer.length > 0) {
905
1275
  return { value: buffer.shift()!, done: false };
906
1276
  }
907
1277
 
908
- // After buffer is drained, check for overflow error
909
- if (hasOverflow) {
910
- hasOverflow = false;
911
- throw new BufferOverflowError(type, bufferSize);
912
- }
913
-
914
- if (done) {
915
- return { value: undefined as unknown as EmissionEvent<E[K]>, done: true };
916
- }
917
-
918
1278
  // Prevent concurrent next() calls - if there's already a pending promise, reject
919
1279
  if (resolve !== null) {
920
1280
  return Promise.reject(
@@ -925,25 +1285,30 @@ function createEventTargetInternal<E extends Record<string, any>>(
925
1285
  }
926
1286
 
927
1287
  // Wait for next event
928
- return new Promise<IteratorResult<EmissionEvent<E[K]>>>((_resolve, _reject) => {
929
- if (done) {
930
- _resolve({ value: undefined as unknown as EmissionEvent<E[K]>, done: true });
931
- return;
932
- }
933
- if (hasOverflow) {
934
- hasOverflow = false;
935
- _reject(new BufferOverflowError(type, bufferSize));
936
- return;
937
- }
938
- resolve = _resolve;
939
- });
1288
+ return new Promise<IteratorResult<EmissionEvent<E[K], K>>>(
1289
+ (_resolve, _reject) => {
1290
+ if (hasOverflow) {
1291
+ hasOverflow = false;
1292
+ _reject(new BufferOverflowError(type, bufferSize));
1293
+ return;
1294
+ }
1295
+ if (done) {
1296
+ _resolve({
1297
+ value: undefined as unknown as EmissionEvent<E[K], K>,
1298
+ done: true,
1299
+ });
1300
+ return;
1301
+ }
1302
+ resolve = _resolve;
1303
+ },
1304
+ );
940
1305
  },
941
- return(): Promise<IteratorResult<EmissionEvent<E[K]>>> {
1306
+ return(): Promise<IteratorResult<EmissionEvent<E[K], K>>> {
942
1307
  // Resolve any pending promise before cleanup
943
1308
  if (resolve) {
944
1309
  const r = resolve;
945
1310
  resolve = null;
946
- r({ value: undefined as unknown as EmissionEvent<E[K]>, done: true });
1311
+ r({ value: undefined as unknown as EmissionEvent<E[K], K>, done: true });
947
1312
  }
948
1313
 
949
1314
  done = true;
@@ -951,12 +1316,10 @@ function createEventTargetInternal<E extends Record<string, any>>(
951
1316
  unsub();
952
1317
 
953
1318
  // Clean up abort signal listener
954
- if (signal && onAbort) {
955
- signal.removeEventListener('abort', onAbort);
956
- }
1319
+ cleanupAbortListener();
957
1320
 
958
1321
  return Promise.resolve({
959
- value: undefined as unknown as EmissionEvent<E[K]>,
1322
+ value: undefined as unknown as EmissionEvent<E[K], K>,
960
1323
  done: true,
961
1324
  });
962
1325
  },
@@ -965,6 +1328,89 @@ function createEventTargetInternal<E extends Record<string, any>>(
965
1328
  return iterator;
966
1329
  }
967
1330
 
1331
+ // Node.js EventEmitter compatibility methods
1332
+
1333
+ const emit: EventTargetLike<E>['emit'] = (type, detail) => {
1334
+ if (isCompleted) return false;
1335
+
1336
+ const list = listeners.get(type);
1337
+ const hasListeners = list !== undefined && list.length > 0;
1338
+ let hasWildcard = false;
1339
+ if (wildcardListeners.size > 0) {
1340
+ for (const rec of wildcardListeners) {
1341
+ if (matchesWildcard(type, rec.pattern)) {
1342
+ hasWildcard = true;
1343
+ break;
1344
+ }
1345
+ }
1346
+ }
1347
+
1348
+ dispatchEvent({ type, detail } as Parameters<EventTargetLike<E>['dispatchEvent']>[0]);
1349
+ return hasListeners || hasWildcard;
1350
+ };
1351
+
1352
+ const off: EventTargetLike<E>['off'] = (type, listener) => {
1353
+ removeEventListener(type, listener);
1354
+ };
1355
+
1356
+ const addListener: EventTargetLike<E>['addListener'] = (type, listener) => {
1357
+ return addEventListener(type, listener);
1358
+ };
1359
+
1360
+ const removeListener: EventTargetLike<E>['removeListener'] = (type, listener) => {
1361
+ removeEventListener(type, listener);
1362
+ };
1363
+
1364
+ const prependListener: EventTargetLike<E>['prependListener'] = (type, listener) => {
1365
+ return addListenerInternal(type, listener, undefined, 'prepend');
1366
+ };
1367
+
1368
+ const prependOnceListener: EventTargetLike<E>['prependOnceListener'] = (
1369
+ type,
1370
+ listener,
1371
+ ) => {
1372
+ return addListenerInternal(type, listener, { once: true }, 'prepend');
1373
+ };
1374
+
1375
+ const getListeners: EventTargetLike<E>['listeners'] = (type) => {
1376
+ const list = listeners.get(type);
1377
+ if (!list) return [];
1378
+ return list.filter((r) => !r.removed).map((r) => r.original);
1379
+ };
1380
+
1381
+ const rawListenersMethod: EventTargetLike<E>['rawListeners'] = (type) => {
1382
+ const list = listeners.get(type);
1383
+ if (!list) return [];
1384
+ /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */
1385
+ return list
1386
+ .filter((r) => !r.removed)
1387
+ .map((r) => {
1388
+ if (r.once) {
1389
+ const wrapper = ((...args: any[]) => (r.callback as any)(...args)) as any;
1390
+ wrapper.listener = r.original;
1391
+ return wrapper;
1392
+ }
1393
+ return r.original;
1394
+ });
1395
+ /* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */
1396
+ };
1397
+
1398
+ const listenerCount: EventTargetLike<E>['listenerCount'] = (type) => {
1399
+ const list = listeners.get(type);
1400
+ if (!list) return 0;
1401
+ return list.filter((r) => !r.removed).length;
1402
+ };
1403
+
1404
+ const eventNamesMethod: EventTargetLike<E>['eventNames'] = () => {
1405
+ const names: string[] = [];
1406
+ for (const [type, list] of listeners) {
1407
+ if (list.some((r) => !r.removed)) {
1408
+ names.push(type);
1409
+ }
1410
+ }
1411
+ return names;
1412
+ };
1413
+
968
1414
  const target: EventTargetLike<E> = {
969
1415
  addEventListener,
970
1416
  removeEventListener,
@@ -983,6 +1429,16 @@ function createEventTargetInternal<E extends Record<string, any>>(
983
1429
  subscribe,
984
1430
  toObservable,
985
1431
  events,
1432
+ emit,
1433
+ off,
1434
+ addListener,
1435
+ removeListener,
1436
+ prependListener,
1437
+ prependOnceListener,
1438
+ listeners: getListeners,
1439
+ rawListeners: rawListenersMethod,
1440
+ listenerCount,
1441
+ eventNames: eventNamesMethod,
986
1442
  };
987
1443
 
988
1444
  // Add Symbol.observable - return an observable that emits all events from all types