@irrelon/emitter 5.0.3 → 5.0.5

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.
@@ -0,0 +1,483 @@
1
+ export var EventReturnFlag;
2
+ (function (EventReturnFlag) {
3
+ EventReturnFlag[EventReturnFlag["none"] = 0] = "none";
4
+ EventReturnFlag[EventReturnFlag["cancel"] = 1] = "cancel";
5
+ })(EventReturnFlag || (EventReturnFlag = {}));
6
+ /**
7
+ * Creates a new class with the capability to emit events.
8
+ */
9
+ export class Emitter {
10
+ _eventsEmitting = false;
11
+ _eventRemovalQueue = [];
12
+ _eventListeners = {};
13
+ _eventStaticEmitters = {};
14
+ _eventsAllowDefer = false;
15
+ _eventsDeferTimeouts = {};
16
+ /**
17
+ * Attach an event listener to the passed event only if the passed
18
+ * id matches the id for the event being fired.
19
+ * @param eventName The name of the event to listen for.
20
+ * @param id The id to match against.
21
+ * @param listener The method to call when the event is fired.
22
+ * @returns The emitter instance.
23
+ */
24
+ _on(eventName, id, listener) {
25
+ const generateTimeout = (emitter) => {
26
+ setTimeout(() => {
27
+ listener(...emitter.args);
28
+ }, 1);
29
+ };
30
+ this._eventListeners = this._eventListeners || {};
31
+ const nonNullableEventListeners = this._eventListeners[eventName] = this._eventListeners[eventName] || {};
32
+ nonNullableEventListeners[id] = nonNullableEventListeners[id] || [];
33
+ nonNullableEventListeners[id].push(listener);
34
+ // Check for any static emitters, and fire the event if any exist
35
+ if (!this._eventStaticEmitters || !this._eventStaticEmitters[eventName] || !this._eventStaticEmitters[eventName].length)
36
+ return this;
37
+ // Emit events for each emitter
38
+ for (let i = 0; i < this._eventStaticEmitters[eventName].length; i++) {
39
+ const emitter = this._eventStaticEmitters[eventName][i];
40
+ if (id === "*" || emitter.id === id) {
41
+ // Call the listener out of process so that any code that expects a listener
42
+ // to be called at some point in the future rather than immediately on registration
43
+ // will not fail
44
+ generateTimeout(emitter);
45
+ }
46
+ }
47
+ return this;
48
+ }
49
+ /**
50
+ * Attach an event listener to the passed event only if the passed
51
+ * id matches the document id for the event being fired.
52
+ * @param eventName The name of the event to listen for.
53
+ * @param id The id to match against.
54
+ * @param listener The method to call when the event is fired.
55
+ * @returns The emitter instance.
56
+ */
57
+ _once(eventName, id, listener) {
58
+ let fired = false;
59
+ let originalReturnData;
60
+ const internalCallback = (...args) => {
61
+ if (fired)
62
+ return originalReturnData;
63
+ fired = true;
64
+ this.off(eventName, id, internalCallback);
65
+ originalReturnData = listener(...args);
66
+ return originalReturnData;
67
+ };
68
+ return this.on(eventName, id, internalCallback);
69
+ }
70
+ /**
71
+ * Cancels an event listener based on an event name, id and listener function.
72
+ * @param eventName The event to cancel listener for.
73
+ * @param id The ID of the event to cancel listening for.
74
+ * @param listener The event listener function used in the on()
75
+ * or once() call to cancel.
76
+ * @returns The emitter instance.
77
+ */
78
+ _off(eventName, id, listener) {
79
+ // If the event name doesn't have any listeners, exit early
80
+ if (!this._eventListeners || !this._eventListeners[eventName] || !this._eventListeners[eventName][id])
81
+ return this;
82
+ // If we are emitting events at the moment, don't remove this listener
83
+ // until the process has completed, so we queue for removal instead
84
+ if (this._eventsEmitting) {
85
+ this._eventRemovalQueue = this._eventRemovalQueue || [];
86
+ this._eventRemovalQueue.push(() => {
87
+ this.off(eventName, id, listener);
88
+ });
89
+ return this;
90
+ }
91
+ // Check if we have no specific listener... in this case
92
+ // we want to remove all listeners for an id
93
+ if (!listener) {
94
+ if (id === "*") {
95
+ // The id is "all" and no listener was provided so delete all
96
+ // event listeners for this event name
97
+ delete this._eventListeners[eventName];
98
+ return this;
99
+ }
100
+ // No listener provided, delete all listeners for this id
101
+ delete this._eventListeners[eventName][id];
102
+ return this;
103
+ }
104
+ const arr = this._eventListeners[eventName][id] || [];
105
+ const index = arr.indexOf(listener);
106
+ if (index > -1) {
107
+ arr.splice(index, 1);
108
+ }
109
+ return this;
110
+ }
111
+ on(eventName, idOrListener, listener) {
112
+ if (typeof idOrListener === "string") {
113
+ return this._on(eventName, idOrListener, listener);
114
+ }
115
+ return this._on(eventName, "*", idOrListener);
116
+ }
117
+ once(eventName, ...rest) {
118
+ const restTypes = rest.map((arg) => typeof arg);
119
+ if (restTypes[0] === "function") {
120
+ return this._once(eventName, "*", rest[0]);
121
+ }
122
+ return this._once(eventName, rest[0], rest[1]);
123
+ }
124
+ overwrite(eventName, ...rest) {
125
+ const restTypes = rest.map((arg) => typeof arg);
126
+ if (restTypes[0] === "function") {
127
+ this.off(eventName);
128
+ return this._on(eventName, "*", rest[0]);
129
+ }
130
+ this.off(eventName, rest[0]);
131
+ return this._on(eventName, rest[0], rest[1]);
132
+ }
133
+ off(eventName, ...rest) {
134
+ if (rest.length === 0) {
135
+ // Only event was provided, use * as the id to mean "any without"
136
+ // a specific id
137
+ return this._off(eventName, "*");
138
+ }
139
+ const restTypes = rest.map((arg) => typeof arg);
140
+ if (restTypes[0] === "function") {
141
+ // The first arg after the event name was a function (listener)
142
+ // so remove listening for events for this specific listener
143
+ return this._off(eventName, "*", rest[0]);
144
+ }
145
+ // Both id and listener were provided, remove for the specific id
146
+ return this._off(eventName, rest[0], rest[1]);
147
+ }
148
+ /**
149
+ * Emit an event by name.
150
+ * @param eventName The name of the event to emit.
151
+ * @param {...any} data The arguments to send to any listening methods.
152
+ * If you are sending multiple arguments, separate them with a comma so
153
+ * that they are received by the function as separate arguments.
154
+ * @return {number}
155
+ * @example #Emit an Event
156
+ * // Emit the event named "hello"
157
+ * myEntity.emit('hello');
158
+ * @example #Emit an Event With Data Object
159
+ * // Emit the event named "hello"
160
+ * myEntity.emit('hello', {moo: true});
161
+ * @example #Emit an Event With Multiple Data Values
162
+ * // Emit the event named "hello"
163
+ * myEntity.emit('hello', {moo: true}, 'someString');
164
+ * @example #Listen for Event Data
165
+ * // Set a listener to listen for the data (multiple values emitted
166
+ * // from an event are passed as function arguments)
167
+ * myEntity.on('hello', function (arg1, arg2) {
168
+ * console.log(arg1, arg2);
169
+ * }
170
+ *
171
+ * // Emit the event named "hello"
172
+ * myEntity.emit('hello', 'data1', 'data2');
173
+ *
174
+ * // The console output is:
175
+ * // data1, data2
176
+ */
177
+ async emit(eventName, ...data) {
178
+ if (!this._eventListeners) {
179
+ return EventReturnFlag.none;
180
+ }
181
+ const id = "*";
182
+ let returnFlag = EventReturnFlag.none;
183
+ this._eventsEmitting = true;
184
+ if (this._eventListeners[eventName] && this._eventListeners[eventName][id]) {
185
+ // Handle global emit
186
+ const arr = this._eventListeners[eventName][id];
187
+ const promiseArr = arr.map((tmpFunc) => {
188
+ if (typeof tmpFunc !== "function")
189
+ return;
190
+ return tmpFunc(...data);
191
+ });
192
+ const result = (await Promise.all(promiseArr)).find((tmpResult) => tmpResult !== EventReturnFlag.none);
193
+ if (result) {
194
+ returnFlag = EventReturnFlag.cancel;
195
+ }
196
+ }
197
+ this._eventsEmitting = false;
198
+ this._processRemovalQueue();
199
+ return returnFlag;
200
+ }
201
+ emitId(eventName, id, ...data) {
202
+ if (!this._eventListeners) {
203
+ return EventReturnFlag.none;
204
+ }
205
+ this._eventListeners = this._eventListeners || {};
206
+ this._eventsEmitting = true;
207
+ if (!this._eventListeners[eventName]) {
208
+ this._eventsEmitting = false;
209
+ this._processRemovalQueue();
210
+ return EventReturnFlag.none;
211
+ }
212
+ // Handle id emit
213
+ if (this._eventListeners[eventName][id]) {
214
+ const arr = this._eventListeners[eventName][id];
215
+ const arrCount = arr.length;
216
+ for (let arrIndex = 0; arrIndex < arrCount; arrIndex++) {
217
+ // Check we have a function to execute
218
+ const tmpFunc = arr[arrIndex];
219
+ if (typeof tmpFunc === "function") {
220
+ tmpFunc.call(this, ...data);
221
+ }
222
+ }
223
+ }
224
+ // Handle global emit
225
+ if (this._eventListeners[eventName]["*"]) {
226
+ const arr = this._eventListeners[eventName]["*"];
227
+ const arrCount = arr.length;
228
+ for (let arrIndex = 0; arrIndex < arrCount; arrIndex++) {
229
+ // Check we have a function to execute
230
+ const tmpFunc = arr[arrIndex];
231
+ if (typeof tmpFunc === "function") {
232
+ tmpFunc.call(this, ...data);
233
+ }
234
+ }
235
+ }
236
+ this._eventsEmitting = false;
237
+ this._processRemovalQueue();
238
+ return EventReturnFlag.none;
239
+ }
240
+ /**
241
+ * Creates a persistent emitter record that will fire a listener if
242
+ * one is added for this event after the emitStatic() call has been
243
+ * made.
244
+ * @param eventName The name of the event to emit.
245
+ * @param {...any} data Optional arguments to emit with the event.
246
+ * @returns The emitter instance.
247
+ * @private
248
+ */
249
+ emitStatic(eventName, ...data) {
250
+ const id = "*";
251
+ this._eventListeners = this._eventListeners || {};
252
+ this._eventsEmitting = true;
253
+ if (this._eventListeners[eventName] && this._eventListeners[eventName][id]) {
254
+ // Handle global emit
255
+ const arr = this._eventListeners[eventName][id];
256
+ const arrCount = arr.length;
257
+ for (let arrIndex = 0; arrIndex < arrCount; arrIndex++) {
258
+ // Check we have a function to execute
259
+ const tmpFunc = arr[arrIndex];
260
+ if (typeof tmpFunc === "function") {
261
+ tmpFunc.call(this, ...data);
262
+ }
263
+ }
264
+ }
265
+ this._eventsEmitting = false;
266
+ this._eventStaticEmitters = this._eventStaticEmitters || {};
267
+ this._eventStaticEmitters[eventName] = this._eventStaticEmitters[eventName] || [];
268
+ this._eventStaticEmitters[eventName].push({
269
+ "id": "*",
270
+ "args": data
271
+ });
272
+ this._processRemovalQueue();
273
+ return this;
274
+ }
275
+ /**
276
+ * Creates a persistent emitter record that will fire a listener if
277
+ * one is added for this event after the emitStatic() call has been
278
+ * made.
279
+ * @param eventName The name of the event to emit.
280
+ * @param id The id of the event to emit.
281
+ * @param {...any} data Optional arguments to emit with the event.
282
+ * @returns The emitter instance.
283
+ * @private
284
+ */
285
+ emitStaticId(eventName, id, ...data) {
286
+ if (!id)
287
+ throw new Error("Missing id from emitId call!");
288
+ this._eventListeners = this._eventListeners || {};
289
+ this._eventsEmitting = true;
290
+ if (this._eventListeners[eventName]) {
291
+ // Handle id emit
292
+ if (this._eventListeners[eventName][id]) {
293
+ const arr = this._eventListeners[eventName][id];
294
+ const arrCount = arr.length;
295
+ for (let arrIndex = 0; arrIndex < arrCount; arrIndex++) {
296
+ // Check we have a function to execute
297
+ const tmpFunc = arr[arrIndex];
298
+ if (typeof tmpFunc === "function") {
299
+ tmpFunc.call(this, ...data);
300
+ }
301
+ }
302
+ }
303
+ // Handle global emit
304
+ if (this._eventListeners[eventName]["*"]) {
305
+ const arr = this._eventListeners[eventName]["*"];
306
+ const arrCount = arr.length;
307
+ for (let arrIndex = 0; arrIndex < arrCount; arrIndex++) {
308
+ // Check we have a function to execute
309
+ const tmpFunc = arr[arrIndex];
310
+ if (typeof tmpFunc === "function") {
311
+ tmpFunc.call(this, ...data);
312
+ }
313
+ }
314
+ }
315
+ }
316
+ this._eventsEmitting = false;
317
+ this._eventStaticEmitters = this._eventStaticEmitters || {};
318
+ this._eventStaticEmitters[eventName] = this._eventStaticEmitters[eventName] || [];
319
+ this._eventStaticEmitters[eventName].push({
320
+ id,
321
+ "args": data
322
+ });
323
+ this._processRemovalQueue();
324
+ return this;
325
+ }
326
+ /**
327
+ * Handles removing emitters, is an internal method not called directly.
328
+ * @param eventName The event to remove static emitter for.
329
+ * @returns The emitter instance.
330
+ * @private
331
+ */
332
+ cancelStatic(eventName) {
333
+ this._eventStaticEmitters = this._eventStaticEmitters || {};
334
+ this._eventStaticEmitters[eventName] = [];
335
+ return this;
336
+ }
337
+ /**
338
+ * Checks if an event has any event listeners or not.
339
+ * @param eventName The name of the event to check for.
340
+ * @returns True if one or more event listeners are registered for
341
+ * the event. False if none are found.
342
+ */
343
+ willEmit(eventName) {
344
+ const id = "*";
345
+ if (!this._eventListeners || !this._eventListeners[eventName]) {
346
+ return false;
347
+ }
348
+ const arr = this._eventListeners[eventName][id];
349
+ const arrCount = arr.length;
350
+ for (let arrIndex = 0; arrIndex < arrCount; arrIndex++) {
351
+ // Check we have a function to execute
352
+ const tmpFunc = arr[arrIndex];
353
+ if (typeof tmpFunc === "function") {
354
+ return true;
355
+ }
356
+ }
357
+ return false;
358
+ }
359
+ /**
360
+ * Checks if an event has any event listeners or not based on the passed id.
361
+ * @param eventName The name of the event to check for.
362
+ * @param id The event ID to check for.
363
+ * @returns True if one or more event listeners are registered for
364
+ * the event. False if none are found.
365
+ */
366
+ willEmitId(eventName, id) {
367
+ if (!this._eventListeners || !this._eventListeners[eventName]) {
368
+ return false;
369
+ }
370
+ // Handle id emit
371
+ if (this._eventListeners[eventName][id]) {
372
+ const arr = this._eventListeners[eventName][id];
373
+ const arrCount = arr.length;
374
+ for (let arrIndex = 0; arrIndex < arrCount; arrIndex++) {
375
+ // Check we have a function to execute
376
+ const tmpFunc = arr[arrIndex];
377
+ if (typeof tmpFunc === "function") {
378
+ return true;
379
+ }
380
+ }
381
+ }
382
+ // Handle global emit
383
+ if (this._eventListeners[eventName]["*"]) {
384
+ const arr = this._eventListeners[eventName]["*"];
385
+ const arrCount = arr.length;
386
+ for (let arrIndex = 0; arrIndex < arrCount; arrIndex++) {
387
+ // Check we have a function to execute
388
+ const tmpFunc = arr[arrIndex];
389
+ if (typeof tmpFunc === "function") {
390
+ return true;
391
+ }
392
+ }
393
+ }
394
+ return false;
395
+ }
396
+ /**
397
+ * Queues an event to be fired. This has automatic de-bouncing so that any
398
+ * events of the same type that occur within 100 milliseconds of a previous
399
+ * one will all be wrapped into a single emit rather than emitting tons of
400
+ * events for lots of chained inserts etc. Only the data from the last
401
+ * de-bounced event will be emitted.
402
+ * @param eventName The name of the event to emit.
403
+ * @param {...any} data Optional arguments to emit with the event.
404
+ * @returns The emitter instance.
405
+ */
406
+ deferEmit(eventName, ...data) {
407
+ if (!this._eventsAllowDefer) {
408
+ // Check for an existing timeout
409
+ this._eventsDeferTimeouts = this._eventsDeferTimeouts || {};
410
+ if (this._eventsDeferTimeouts[eventName]) {
411
+ clearTimeout(this._eventsDeferTimeouts[eventName]);
412
+ }
413
+ // Set a timeout
414
+ this._eventsDeferTimeouts[eventName] = setTimeout(() => {
415
+ void this.emit.call(this, eventName, ...data);
416
+ }, 1);
417
+ }
418
+ else {
419
+ void this.emit.call(this, eventName, ...data);
420
+ }
421
+ return this;
422
+ }
423
+ /**
424
+ * If events are cleared with the off() method while the event emitter is
425
+ * actively processing any events then the off() calls get added to a
426
+ * queue to be executed after the event emitter is finished. This stops
427
+ * errors that might occur by potentially modifying the event queue while
428
+ * the emitter is running through them. This method is called after the
429
+ * event emitter is finished processing.
430
+ * @private
431
+ */
432
+ _processRemovalQueue() {
433
+ if (!this._eventRemovalQueue || !this._eventRemovalQueue.length) {
434
+ return;
435
+ }
436
+ // Execute each removal call
437
+ for (let i = 0; i < this._eventRemovalQueue.length; i++) {
438
+ this._eventRemovalQueue[i]();
439
+ }
440
+ // Clear the removal queue
441
+ this._eventRemovalQueue = [];
442
+ }
443
+ }
444
+ export function makeEmitter(obj, prototypeMode) {
445
+ let operateOnObject;
446
+ if (obj === undefined && prototypeMode === undefined) {
447
+ obj = {};
448
+ prototypeMode = false;
449
+ }
450
+ if (typeof obj === "boolean" && prototypeMode === undefined) {
451
+ prototypeMode = obj;
452
+ obj = {};
453
+ }
454
+ if (prototypeMode === undefined) {
455
+ prototypeMode = true;
456
+ }
457
+ if (typeof obj !== "object" && typeof obj !== "function") {
458
+ throw new Error("Cannot operate on a non-object / non-function passed as first argument!");
459
+ }
460
+ if (prototypeMode) {
461
+ if (obj.prototype === undefined) {
462
+ throw new Error("Cannot modify prototype of passed object, it has no prototype property! Was it instantiated with the new operator correctly?");
463
+ }
464
+ operateOnObject = obj.prototype;
465
+ }
466
+ else {
467
+ operateOnObject = obj;
468
+ }
469
+ // Convert the object prototype to have eventing capability
470
+ operateOnObject.on = Emitter.prototype.on;
471
+ operateOnObject.off = Emitter.prototype.off;
472
+ operateOnObject.once = Emitter.prototype.once;
473
+ operateOnObject.emit = Emitter.prototype.emit;
474
+ operateOnObject.emitId = Emitter.prototype.emitId;
475
+ operateOnObject.emitStatic = Emitter.prototype.emitStatic;
476
+ operateOnObject.emitStaticId = Emitter.prototype.emitStaticId;
477
+ operateOnObject.cancelStatic = Emitter.prototype.cancelStatic;
478
+ operateOnObject.deferEmit = Emitter.prototype.deferEmit;
479
+ operateOnObject.willEmit = Emitter.prototype.willEmit;
480
+ operateOnObject.willEmitId = Emitter.prototype.willEmitId;
481
+ operateOnObject._processRemovalQueue = Emitter.prototype._processRemovalQueue;
482
+ return obj;
483
+ }
@@ -0,0 +1 @@
1
+ export {};