event-emission 0.1.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/LICENSE +21 -0
- package/README.md +331 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/eventful.d.ts +153 -0
- package/dist/eventful.d.ts.map +1 -0
- package/dist/factory.d.ts +89 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/index.cjs +1126 -0
- package/dist/index.cjs.map +15 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1093 -0
- package/dist/index.js.map +15 -0
- package/dist/interop.d.ts +163 -0
- package/dist/interop.d.ts.map +1 -0
- package/dist/observe.d.ts +182 -0
- package/dist/observe.d.ts.map +1 -0
- package/dist/symbols.d.ts +6 -0
- package/dist/symbols.d.ts.map +1 -0
- package/dist/types.d.ts +139 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +107 -0
- package/src/errors.ts +10 -0
- package/src/eventful.ts +323 -0
- package/src/factory.ts +948 -0
- package/src/index.ts +71 -0
- package/src/interop.ts +271 -0
- package/src/observe.ts +734 -0
- package/src/symbols.ts +12 -0
- package/src/types.ts +206 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* event-emission
|
|
3
|
+
*
|
|
4
|
+
* Lightweight typed event emitter with DOM EventTarget
|
|
5
|
+
* and TC39 Observable compatibility.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Re-export from symbols
|
|
11
|
+
export { SymbolObservable } from './symbols';
|
|
12
|
+
|
|
13
|
+
// Augment the global Symbol interface to include observable
|
|
14
|
+
declare global {
|
|
15
|
+
interface SymbolConstructor {
|
|
16
|
+
readonly observable: symbol;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Re-export types
|
|
21
|
+
export type {
|
|
22
|
+
AddEventListenerOptionsLike,
|
|
23
|
+
AsyncIteratorOptions,
|
|
24
|
+
DOMEventLike,
|
|
25
|
+
DOMEventTargetLike,
|
|
26
|
+
EventfulEvent,
|
|
27
|
+
EventsIteratorOptions,
|
|
28
|
+
EventTargetLike,
|
|
29
|
+
InteropOptions,
|
|
30
|
+
Listener,
|
|
31
|
+
MinimalAbortSignal,
|
|
32
|
+
ObservableLike,
|
|
33
|
+
Observer,
|
|
34
|
+
OverflowStrategy,
|
|
35
|
+
Subscription,
|
|
36
|
+
WildcardEvent,
|
|
37
|
+
WildcardListener,
|
|
38
|
+
} from './types';
|
|
39
|
+
|
|
40
|
+
// Re-export errors
|
|
41
|
+
export { BufferOverflowError } from './errors';
|
|
42
|
+
|
|
43
|
+
// Re-export factory and options types
|
|
44
|
+
export type {
|
|
45
|
+
CreateEventTargetObserveOptions,
|
|
46
|
+
CreateEventTargetOptions,
|
|
47
|
+
} from './factory';
|
|
48
|
+
export { createEventTarget } from './factory';
|
|
49
|
+
|
|
50
|
+
// Re-export observation utilities and types
|
|
51
|
+
export type {
|
|
52
|
+
ArrayMutationDetail,
|
|
53
|
+
ArrayMutationMethod,
|
|
54
|
+
ObservableEventMap,
|
|
55
|
+
ObserveOptions,
|
|
56
|
+
PropertyChangeDetail,
|
|
57
|
+
} from './observe';
|
|
58
|
+
export {
|
|
59
|
+
getOriginal,
|
|
60
|
+
isObserved,
|
|
61
|
+
ORIGINAL_TARGET,
|
|
62
|
+
PROXY_MARKER,
|
|
63
|
+
setupEventForwarding,
|
|
64
|
+
} from './observe';
|
|
65
|
+
|
|
66
|
+
// Re-export interop
|
|
67
|
+
export type { FromEventTargetOptions } from './interop';
|
|
68
|
+
export { forwardToEventTarget, fromEventTarget, pipe } from './interop';
|
|
69
|
+
|
|
70
|
+
// Re-export Eventful class
|
|
71
|
+
export { Eventful } from './eventful';
|
package/src/interop.ts
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { createEventTarget } from './factory';
|
|
2
|
+
import type {
|
|
3
|
+
DOMEventLike,
|
|
4
|
+
DOMEventTargetLike,
|
|
5
|
+
EventTargetLike,
|
|
6
|
+
InteropOptions,
|
|
7
|
+
} from './types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Forward events from an Eventful target to a DOM EventTarget.
|
|
11
|
+
*
|
|
12
|
+
* This function sets up a wildcard listener on the source that forwards
|
|
13
|
+
* all events to the DOM target using dispatchEvent.
|
|
14
|
+
*
|
|
15
|
+
* @template E - Event map type of the source.
|
|
16
|
+
* @param source - The Eventful source to forward events from.
|
|
17
|
+
* @param target - The DOM EventTarget to forward events to.
|
|
18
|
+
* @param options - Optional configuration including abort signal.
|
|
19
|
+
* @returns An unsubscribe function that stops forwarding when called.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const events = createEventTarget<{ click: { x: number; y: number } }>();
|
|
24
|
+
* const button = document.getElementById('my-button');
|
|
25
|
+
*
|
|
26
|
+
* // Forward all events to the DOM button
|
|
27
|
+
* const unsubscribe = forwardToEventTarget(events, button);
|
|
28
|
+
*
|
|
29
|
+
* // Now when you dispatch events on the Eventful target,
|
|
30
|
+
* // they will also be dispatched on the DOM element
|
|
31
|
+
* events.dispatchEvent({ type: 'click', detail: { x: 100, y: 200 } });
|
|
32
|
+
*
|
|
33
|
+
* // Stop forwarding
|
|
34
|
+
* unsubscribe();
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export function forwardToEventTarget<E extends Record<string, unknown>>(
|
|
38
|
+
source: EventTargetLike<E>,
|
|
39
|
+
target: DOMEventTargetLike,
|
|
40
|
+
options?: InteropOptions,
|
|
41
|
+
): () => void {
|
|
42
|
+
const unsubscribe = source.addWildcardListener(
|
|
43
|
+
'*',
|
|
44
|
+
(event) => {
|
|
45
|
+
// Create a DOM-like event object
|
|
46
|
+
const domEvent: DOMEventLike = {
|
|
47
|
+
type: event.originalType,
|
|
48
|
+
detail: event.detail,
|
|
49
|
+
};
|
|
50
|
+
target.dispatchEvent(domEvent);
|
|
51
|
+
},
|
|
52
|
+
options,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return unsubscribe;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Options for creating an Eventful target from a DOM EventTarget.
|
|
60
|
+
*/
|
|
61
|
+
export interface FromEventTargetOptions extends InteropOptions {
|
|
62
|
+
/**
|
|
63
|
+
* Callback invoked when a listener throws an error.
|
|
64
|
+
* If not provided, errors will be re-thrown.
|
|
65
|
+
*/
|
|
66
|
+
onListenerError?: (type: string, error: unknown) => void;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Create an Eventful target that listens to events from a DOM EventTarget.
|
|
71
|
+
*
|
|
72
|
+
* This function wraps a DOM EventTarget and forwards specified events to a new
|
|
73
|
+
* Eventful target, enabling type-safe event handling and TC39 Observable compatibility.
|
|
74
|
+
*
|
|
75
|
+
* @template E - Event map type where keys are event names and values are event detail types.
|
|
76
|
+
* @param domTarget - The DOM EventTarget to listen to events from.
|
|
77
|
+
* @param eventTypes - Array of event type names to forward from the DOM target.
|
|
78
|
+
* @param options - Optional configuration including abort signal and error handler.
|
|
79
|
+
* @returns An Eventful target with a destroy() method for cleanup.
|
|
80
|
+
*
|
|
81
|
+
* @example Basic usage with DOM element
|
|
82
|
+
* ```typescript
|
|
83
|
+
* const button = document.getElementById('my-button');
|
|
84
|
+
*
|
|
85
|
+
* type ButtonEvents = {
|
|
86
|
+
* click: MouseEvent;
|
|
87
|
+
* focus: FocusEvent;
|
|
88
|
+
* };
|
|
89
|
+
*
|
|
90
|
+
* const events = fromEventTarget<ButtonEvents>(button, ['click', 'focus']);
|
|
91
|
+
*
|
|
92
|
+
* // Type-safe event handling
|
|
93
|
+
* events.addEventListener('click', (event) => {
|
|
94
|
+
* console.log('Button clicked!', event.detail);
|
|
95
|
+
* });
|
|
96
|
+
*
|
|
97
|
+
* // Clean up when done
|
|
98
|
+
* events.destroy();
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* @example With AbortSignal for automatic cleanup
|
|
102
|
+
* ```typescript
|
|
103
|
+
* const controller = new AbortController();
|
|
104
|
+
* const events = fromEventTarget<{ input: InputEvent }>(
|
|
105
|
+
* textField,
|
|
106
|
+
* ['input'],
|
|
107
|
+
* { signal: controller.signal }
|
|
108
|
+
* );
|
|
109
|
+
*
|
|
110
|
+
* // Later, abort to clean up all listeners
|
|
111
|
+
* controller.abort();
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* @example Using TC39 Observable features
|
|
115
|
+
* ```typescript
|
|
116
|
+
* const events = fromEventTarget<{ scroll: Event }>(window, ['scroll']);
|
|
117
|
+
*
|
|
118
|
+
* // Subscribe with observer pattern
|
|
119
|
+
* const subscription = events.subscribe({
|
|
120
|
+
* next: (event) => console.log('Scrolled!'),
|
|
121
|
+
* complete: () => console.log('Done'),
|
|
122
|
+
* });
|
|
123
|
+
*
|
|
124
|
+
* // Or use async iteration
|
|
125
|
+
* for await (const event of events.events('scroll')) {
|
|
126
|
+
* console.log('Scroll event:', event);
|
|
127
|
+
* }
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
export function fromEventTarget<E extends Record<string, unknown>>(
|
|
131
|
+
domTarget: DOMEventTargetLike,
|
|
132
|
+
eventTypes: Array<keyof E & string>,
|
|
133
|
+
options?: FromEventTargetOptions,
|
|
134
|
+
): EventTargetLike<E> & { destroy: () => void } {
|
|
135
|
+
const eventful = createEventTarget<E>({
|
|
136
|
+
onListenerError: options?.onListenerError,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const handlers = new Map<string, (event: DOMEventLike) => void>();
|
|
140
|
+
|
|
141
|
+
for (const type of eventTypes) {
|
|
142
|
+
const handler = (event: DOMEventLike) => {
|
|
143
|
+
eventful.dispatchEvent({
|
|
144
|
+
type,
|
|
145
|
+
detail: (event.detail ?? event) as E[typeof type],
|
|
146
|
+
});
|
|
147
|
+
};
|
|
148
|
+
handlers.set(type, handler);
|
|
149
|
+
domTarget.addEventListener(type, handler);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Track abort handler for cleanup
|
|
153
|
+
let onAbort: (() => void) | null = null;
|
|
154
|
+
|
|
155
|
+
// Handle abort signal
|
|
156
|
+
if (options?.signal) {
|
|
157
|
+
onAbort = () => {
|
|
158
|
+
for (const [type, handler] of handlers) {
|
|
159
|
+
domTarget.removeEventListener(type, handler);
|
|
160
|
+
}
|
|
161
|
+
handlers.clear();
|
|
162
|
+
eventful.complete();
|
|
163
|
+
};
|
|
164
|
+
options.signal.addEventListener('abort', onAbort, { once: true });
|
|
165
|
+
if (options.signal.aborted) onAbort();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
addEventListener: eventful.addEventListener,
|
|
170
|
+
removeEventListener: eventful.removeEventListener,
|
|
171
|
+
dispatchEvent: eventful.dispatchEvent,
|
|
172
|
+
clear: eventful.clear,
|
|
173
|
+
once: eventful.once,
|
|
174
|
+
removeAllListeners: eventful.removeAllListeners,
|
|
175
|
+
pipe: eventful.pipe,
|
|
176
|
+
addWildcardListener: eventful.addWildcardListener,
|
|
177
|
+
removeWildcardListener: eventful.removeWildcardListener,
|
|
178
|
+
subscribe: eventful.subscribe,
|
|
179
|
+
toObservable: eventful.toObservable,
|
|
180
|
+
complete: eventful.complete,
|
|
181
|
+
get completed() {
|
|
182
|
+
return eventful.completed;
|
|
183
|
+
},
|
|
184
|
+
events: eventful.events,
|
|
185
|
+
destroy: () => {
|
|
186
|
+
// Clean up abort signal listener to prevent memory leak
|
|
187
|
+
if (options?.signal && onAbort) {
|
|
188
|
+
options.signal.removeEventListener('abort', onAbort);
|
|
189
|
+
}
|
|
190
|
+
for (const [type, handler] of handlers) {
|
|
191
|
+
domTarget.removeEventListener(type, handler);
|
|
192
|
+
}
|
|
193
|
+
handlers.clear();
|
|
194
|
+
eventful.complete();
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Pipe events from one Eventful target to another.
|
|
201
|
+
*
|
|
202
|
+
* This function sets up a wildcard listener on the source that forwards all
|
|
203
|
+
* events to the target. Useful for composing event streams, creating event
|
|
204
|
+
* buses, or building hierarchical event systems.
|
|
205
|
+
*
|
|
206
|
+
* @template E - Event map type shared by both source and target.
|
|
207
|
+
* @param source - The Eventful target to pipe events from.
|
|
208
|
+
* @param target - The Eventful target to pipe events to.
|
|
209
|
+
* @param options - Optional configuration including abort signal.
|
|
210
|
+
* @returns An unsubscribe function that stops piping when called.
|
|
211
|
+
*
|
|
212
|
+
* @example Basic event piping
|
|
213
|
+
* ```typescript
|
|
214
|
+
* const userEvents = createEventTarget<{ login: { userId: string } }>();
|
|
215
|
+
* const globalBus = createEventTarget<{ login: { userId: string } }>();
|
|
216
|
+
*
|
|
217
|
+
* // Pipe all user events to global bus
|
|
218
|
+
* const unsubscribe = pipe(userEvents, globalBus);
|
|
219
|
+
*
|
|
220
|
+
* // Events on userEvents now also dispatch on globalBus
|
|
221
|
+
* globalBus.addEventListener('login', (event) => {
|
|
222
|
+
* console.log('User logged in:', event.detail.userId);
|
|
223
|
+
* });
|
|
224
|
+
*
|
|
225
|
+
* userEvents.dispatchEvent({ type: 'login', detail: { userId: '123' } });
|
|
226
|
+
*
|
|
227
|
+
* // Stop piping
|
|
228
|
+
* unsubscribe();
|
|
229
|
+
* ```
|
|
230
|
+
*
|
|
231
|
+
* @example With AbortSignal for automatic cleanup
|
|
232
|
+
* ```typescript
|
|
233
|
+
* const controller = new AbortController();
|
|
234
|
+
* pipe(source, target, { signal: controller.signal });
|
|
235
|
+
*
|
|
236
|
+
* // Later, abort to stop piping
|
|
237
|
+
* controller.abort();
|
|
238
|
+
* ```
|
|
239
|
+
*
|
|
240
|
+
* @example Creating an event hierarchy
|
|
241
|
+
* ```typescript
|
|
242
|
+
* const componentA = createEventTarget<Events>();
|
|
243
|
+
* const componentB = createEventTarget<Events>();
|
|
244
|
+
* const appBus = createEventTarget<Events>();
|
|
245
|
+
*
|
|
246
|
+
* // Both components pipe to the app bus
|
|
247
|
+
* pipe(componentA, appBus);
|
|
248
|
+
* pipe(componentB, appBus);
|
|
249
|
+
*
|
|
250
|
+
* // Listen to all events at the app level
|
|
251
|
+
* appBus.addWildcardListener('*', (event) => {
|
|
252
|
+
* console.log('App event:', event.originalType, event.detail);
|
|
253
|
+
* });
|
|
254
|
+
* ```
|
|
255
|
+
*/
|
|
256
|
+
export function pipe<E extends Record<string, unknown>>(
|
|
257
|
+
source: EventTargetLike<E>,
|
|
258
|
+
target: EventTargetLike<E>,
|
|
259
|
+
options?: InteropOptions,
|
|
260
|
+
): () => void {
|
|
261
|
+
return source.addWildcardListener(
|
|
262
|
+
'*',
|
|
263
|
+
(event) => {
|
|
264
|
+
target.dispatchEvent({
|
|
265
|
+
type: event.originalType,
|
|
266
|
+
detail: event.detail as E[keyof E & string],
|
|
267
|
+
});
|
|
268
|
+
},
|
|
269
|
+
options,
|
|
270
|
+
);
|
|
271
|
+
}
|