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.
- package/README.md +158 -25
- package/dist/event-emission.d.ts +63 -14
- package/dist/event-emission.d.ts.map +1 -1
- package/dist/factory.d.ts +1 -1
- package/dist/factory.d.ts.map +1 -1
- package/dist/index.cjs +614 -291
- package/dist/index.cjs.map +8 -9
- package/dist/index.d.ts +1 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +589 -284
- package/dist/index.js.map +8 -9
- package/dist/interoperability.cjs +1719 -0
- package/dist/interoperability.cjs.map +15 -0
- package/dist/{interop.d.ts → interoperability.d.ts} +2 -1
- package/dist/interoperability.d.ts.map +1 -0
- package/dist/interoperability.js +1669 -0
- package/dist/interoperability.js.map +15 -0
- package/dist/observable.cjs +286 -0
- package/dist/observable.cjs.map +11 -0
- package/dist/observable.js +253 -0
- package/dist/observable.js.map +11 -0
- package/dist/observe.cjs +344 -0
- package/dist/observe.cjs.map +10 -0
- package/dist/observe.d.ts +6 -1
- package/dist/observe.d.ts.map +1 -1
- package/dist/observe.js +313 -0
- package/dist/observe.js.map +10 -0
- package/dist/symbols.d.ts +1 -1
- package/dist/types.cjs +35 -0
- package/dist/types.cjs.map +9 -0
- package/dist/types.d.ts +73 -25
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -0
- package/dist/types.js.map +9 -0
- package/package.json +25 -1
- package/src/event-emission.ts +140 -21
- package/src/factory.ts +686 -230
- package/src/index.ts +4 -33
- package/src/{interop.ts → interoperability.ts} +34 -6
- package/src/observe.ts +54 -17
- package/src/symbols.ts +1 -1
- package/src/types.ts +115 -33
- 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
|
-
|
|
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:
|
|
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,
|
|
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
|
|
288
|
+
* Helper to create a DOM-compatible event.
|
|
219
289
|
*/
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
{
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
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.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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 = (
|
|
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
|
|
295
|
-
const
|
|
296
|
-
|
|
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
|
|
324
|
-
type,
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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
|
-
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
353
|
-
|
|
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
|
-
|
|
356
|
-
if (
|
|
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
|
|
410
|
-
|
|
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
|
-
|
|
413
|
-
|
|
692
|
+
state.currentTarget = target;
|
|
693
|
+
state.target = target;
|
|
694
|
+
state.eventPhase = event.AT_TARGET;
|
|
414
695
|
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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(
|
|
718
|
+
handleListenerError(eventType, error);
|
|
437
719
|
} finally {
|
|
438
|
-
if (rec.
|
|
720
|
+
if (rec.passive) state.inPassiveListenerFlag = false;
|
|
439
721
|
}
|
|
722
|
+
|
|
723
|
+
if (state.stopImmediatePropagationFlag) break;
|
|
440
724
|
}
|
|
441
|
-
|
|
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
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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
|
|
488
|
-
|
|
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 = {
|
|
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
|
-
|
|
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
|
|
555
|
-
if (
|
|
556
|
-
|
|
557
|
-
|
|
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
|
|
568
|
-
for (const record of
|
|
569
|
-
|
|
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
|
-
*
|
|
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
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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
|
-
|
|
983
|
+
mapped as unknown as Parameters<typeof target.dispatchEvent>[0],
|
|
624
984
|
);
|
|
625
985
|
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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
|
-
|
|
638
|
-
unsub();
|
|
639
|
-
}
|
|
997
|
+
unsubscribe();
|
|
640
998
|
};
|
|
641
999
|
completionCallbacks.add(completionUnsub);
|
|
642
1000
|
|
|
643
1001
|
return () => {
|
|
644
1002
|
completionCallbacks.delete(completionUnsub);
|
|
645
|
-
|
|
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
|
|
673
|
-
for (const record of
|
|
674
|
-
|
|
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
|
|
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(
|
|
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:
|
|
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(
|
|
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(
|
|
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]>>>(
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
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
|
-
|
|
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
|