UNPKG

19.3 kBJavaScriptView Raw
1(function (global, factory) {
2 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@lumino/algorithm')) :
3 typeof define === 'function' && define.amd ? define(['exports', '@lumino/algorithm'], factory) :
4 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.lumino_signaling = {}, global.lumino_algorithm));
5}(this, (function (exports, algorithm) { 'use strict';
6
7 // Copyright (c) Jupyter Development Team.
8 /**
9 * A concrete implementation of `ISignal`.
10 *
11 * #### Example
12 * ```typescript
13 * import { ISignal, Signal } from '@lumino/signaling';
14 *
15 * class SomeClass {
16 *
17 * constructor(name: string) {
18 * this.name = name;
19 * }
20 *
21 * readonly name: string;
22 *
23 * get valueChanged: ISignal<this, number> {
24 * return this._valueChanged;
25 * }
26 *
27 * get value(): number {
28 * return this._value;
29 * }
30 *
31 * set value(value: number) {
32 * if (value === this._value) {
33 * return;
34 * }
35 * this._value = value;
36 * this._valueChanged.emit(value);
37 * }
38 *
39 * private _value = 0;
40 * private _valueChanged = new Signal<this, number>(this);
41 * }
42 *
43 * function logger(sender: SomeClass, value: number): void {
44 * console.log(sender.name, value);
45 * }
46 *
47 * let m1 = new SomeClass('foo');
48 * let m2 = new SomeClass('bar');
49 *
50 * m1.valueChanged.connect(logger);
51 * m2.valueChanged.connect(logger);
52 *
53 * m1.value = 42; // logs: foo 42
54 * m2.value = 17; // logs: bar 17
55 * ```
56 */
57 exports.Signal = /** @class */ (function () {
58 /**
59 * Construct a new signal.
60 *
61 * @param sender - The sender which owns the signal.
62 */
63 function Signal(sender) {
64 this.sender = sender;
65 }
66 /**
67 * Connect a slot to the signal.
68 *
69 * @param slot - The slot to invoke when the signal is emitted.
70 *
71 * @param thisArg - The `this` context for the slot. If provided,
72 * this must be a non-primitive object.
73 *
74 * @returns `true` if the connection succeeds, `false` otherwise.
75 */
76 Signal.prototype.connect = function (slot, thisArg) {
77 return Private.connect(this, slot, thisArg);
78 };
79 /**
80 * Disconnect a slot from the signal.
81 *
82 * @param slot - The slot to disconnect from the signal.
83 *
84 * @param thisArg - The `this` context for the slot. If provided,
85 * this must be a non-primitive object.
86 *
87 * @returns `true` if the connection is removed, `false` otherwise.
88 */
89 Signal.prototype.disconnect = function (slot, thisArg) {
90 return Private.disconnect(this, slot, thisArg);
91 };
92 /**
93 * Emit the signal and invoke the connected slots.
94 *
95 * @param args - The args to pass to the connected slots.
96 *
97 * #### Notes
98 * Slots are invoked synchronously in connection order.
99 *
100 * Exceptions thrown by connected slots will be caught and logged.
101 */
102 Signal.prototype.emit = function (args) {
103 Private.emit(this, args);
104 };
105 return Signal;
106 }());
107 /**
108 * The namespace for the `Signal` class statics.
109 */
110 (function (Signal) {
111 /**
112 * Remove all connections between a sender and receiver.
113 *
114 * @param sender - The sender object of interest.
115 *
116 * @param receiver - The receiver object of interest.
117 *
118 * #### Notes
119 * If a `thisArg` is provided when connecting a signal, that object
120 * is considered the receiver. Otherwise, the `slot` is considered
121 * the receiver.
122 */
123 function disconnectBetween(sender, receiver) {
124 Private.disconnectBetween(sender, receiver);
125 }
126 Signal.disconnectBetween = disconnectBetween;
127 /**
128 * Remove all connections where the given object is the sender.
129 *
130 * @param sender - The sender object of interest.
131 */
132 function disconnectSender(sender) {
133 Private.disconnectSender(sender);
134 }
135 Signal.disconnectSender = disconnectSender;
136 /**
137 * Remove all connections where the given object is the receiver.
138 *
139 * @param receiver - The receiver object of interest.
140 *
141 * #### Notes
142 * If a `thisArg` is provided when connecting a signal, that object
143 * is considered the receiver. Otherwise, the `slot` is considered
144 * the receiver.
145 */
146 function disconnectReceiver(receiver) {
147 Private.disconnectReceiver(receiver);
148 }
149 Signal.disconnectReceiver = disconnectReceiver;
150 /**
151 * Remove all connections where an object is the sender or receiver.
152 *
153 * @param object - The object of interest.
154 *
155 * #### Notes
156 * If a `thisArg` is provided when connecting a signal, that object
157 * is considered the receiver. Otherwise, the `slot` is considered
158 * the receiver.
159 */
160 function disconnectAll(object) {
161 Private.disconnectAll(object);
162 }
163 Signal.disconnectAll = disconnectAll;
164 /**
165 * Clear all signal data associated with the given object.
166 *
167 * @param object - The object for which the data should be cleared.
168 *
169 * #### Notes
170 * This removes all signal connections and any other signal data
171 * associated with the object.
172 */
173 function clearData(object) {
174 Private.disconnectAll(object);
175 }
176 Signal.clearData = clearData;
177 /**
178 * Get the signal exception handler.
179 *
180 * @returns The current exception handler.
181 *
182 * #### Notes
183 * The default exception handler is `console.error`.
184 */
185 function getExceptionHandler() {
186 return Private.exceptionHandler;
187 }
188 Signal.getExceptionHandler = getExceptionHandler;
189 /**
190 * Set the signal exception handler.
191 *
192 * @param handler - The function to use as the exception handler.
193 *
194 * @returns The old exception handler.
195 *
196 * #### Notes
197 * The exception handler is invoked when a slot throws an exception.
198 */
199 function setExceptionHandler(handler) {
200 var old = Private.exceptionHandler;
201 Private.exceptionHandler = handler;
202 return old;
203 }
204 Signal.setExceptionHandler = setExceptionHandler;
205 })(exports.Signal || (exports.Signal = {}));
206 /**
207 * The namespace for the module implementation details.
208 */
209 var Private;
210 (function (Private) {
211 /**
212 * The signal exception handler function.
213 */
214 Private.exceptionHandler = function (err) {
215 console.error(err);
216 };
217 /**
218 * Connect a slot to a signal.
219 *
220 * @param signal - The signal of interest.
221 *
222 * @param slot - The slot to invoke when the signal is emitted.
223 *
224 * @param thisArg - The `this` context for the slot. If provided,
225 * this must be a non-primitive object.
226 *
227 * @returns `true` if the connection succeeds, `false` otherwise.
228 */
229 function connect(signal, slot, thisArg) {
230 // Coerce a `null` `thisArg` to `undefined`.
231 thisArg = thisArg || undefined;
232 // Ensure the sender's array of receivers is created.
233 var receivers = receiversForSender.get(signal.sender);
234 if (!receivers) {
235 receivers = [];
236 receiversForSender.set(signal.sender, receivers);
237 }
238 // Bail if a matching connection already exists.
239 if (findConnection(receivers, signal, slot, thisArg)) {
240 return false;
241 }
242 // Choose the best object for the receiver.
243 var receiver = thisArg || slot;
244 // Ensure the receiver's array of senders is created.
245 var senders = sendersForReceiver.get(receiver);
246 if (!senders) {
247 senders = [];
248 sendersForReceiver.set(receiver, senders);
249 }
250 // Create a new connection and add it to the end of each array.
251 var connection = { signal: signal, slot: slot, thisArg: thisArg };
252 receivers.push(connection);
253 senders.push(connection);
254 // Indicate a successful connection.
255 return true;
256 }
257 Private.connect = connect;
258 /**
259 * Disconnect a slot from a signal.
260 *
261 * @param signal - The signal of interest.
262 *
263 * @param slot - The slot to disconnect from the signal.
264 *
265 * @param thisArg - The `this` context for the slot. If provided,
266 * this must be a non-primitive object.
267 *
268 * @returns `true` if the connection is removed, `false` otherwise.
269 */
270 function disconnect(signal, slot, thisArg) {
271 // Coerce a `null` `thisArg` to `undefined`.
272 thisArg = thisArg || undefined;
273 // Lookup the list of receivers, and bail if none exist.
274 var receivers = receiversForSender.get(signal.sender);
275 if (!receivers || receivers.length === 0) {
276 return false;
277 }
278 // Bail if no matching connection exits.
279 var connection = findConnection(receivers, signal, slot, thisArg);
280 if (!connection) {
281 return false;
282 }
283 // Choose the best object for the receiver.
284 var receiver = thisArg || slot;
285 // Lookup the array of senders, which is now known to exist.
286 var senders = sendersForReceiver.get(receiver);
287 // Clear the connection and schedule cleanup of the arrays.
288 connection.signal = null;
289 scheduleCleanup(receivers);
290 scheduleCleanup(senders);
291 // Indicate a successful disconnection.
292 return true;
293 }
294 Private.disconnect = disconnect;
295 /**
296 * Remove all connections between a sender and receiver.
297 *
298 * @param sender - The sender object of interest.
299 *
300 * @param receiver - The receiver object of interest.
301 */
302 function disconnectBetween(sender, receiver) {
303 // If there are no receivers, there is nothing to do.
304 var receivers = receiversForSender.get(sender);
305 if (!receivers || receivers.length === 0) {
306 return;
307 }
308 // If there are no senders, there is nothing to do.
309 var senders = sendersForReceiver.get(receiver);
310 if (!senders || senders.length === 0) {
311 return;
312 }
313 // Clear each connection between the sender and receiver.
314 algorithm.each(senders, function (connection) {
315 // Skip connections which have already been cleared.
316 if (!connection.signal) {
317 return;
318 }
319 // Clear the connection if it matches the sender.
320 if (connection.signal.sender === sender) {
321 connection.signal = null;
322 }
323 });
324 // Schedule a cleanup of the senders and receivers.
325 scheduleCleanup(receivers);
326 scheduleCleanup(senders);
327 }
328 Private.disconnectBetween = disconnectBetween;
329 /**
330 * Remove all connections where the given object is the sender.
331 *
332 * @param sender - The sender object of interest.
333 */
334 function disconnectSender(sender) {
335 // If there are no receivers, there is nothing to do.
336 var receivers = receiversForSender.get(sender);
337 if (!receivers || receivers.length === 0) {
338 return;
339 }
340 // Clear each receiver connection.
341 algorithm.each(receivers, function (connection) {
342 // Skip connections which have already been cleared.
343 if (!connection.signal) {
344 return;
345 }
346 // Choose the best object for the receiver.
347 var receiver = connection.thisArg || connection.slot;
348 // Clear the connection.
349 connection.signal = null;
350 // Cleanup the array of senders, which is now known to exist.
351 scheduleCleanup(sendersForReceiver.get(receiver));
352 });
353 // Schedule a cleanup of the receivers.
354 scheduleCleanup(receivers);
355 }
356 Private.disconnectSender = disconnectSender;
357 /**
358 * Remove all connections where the given object is the receiver.
359 *
360 * @param receiver - The receiver object of interest.
361 */
362 function disconnectReceiver(receiver) {
363 // If there are no senders, there is nothing to do.
364 var senders = sendersForReceiver.get(receiver);
365 if (!senders || senders.length === 0) {
366 return;
367 }
368 // Clear each sender connection.
369 algorithm.each(senders, function (connection) {
370 // Skip connections which have already been cleared.
371 if (!connection.signal) {
372 return;
373 }
374 // Lookup the sender for the connection.
375 var sender = connection.signal.sender;
376 // Clear the connection.
377 connection.signal = null;
378 // Cleanup the array of receivers, which is now known to exist.
379 scheduleCleanup(receiversForSender.get(sender));
380 });
381 // Schedule a cleanup of the list of senders.
382 scheduleCleanup(senders);
383 }
384 Private.disconnectReceiver = disconnectReceiver;
385 /**
386 * Remove all connections where an object is the sender or receiver.
387 *
388 * @param object - The object of interest.
389 */
390 function disconnectAll(object) {
391 // Remove all connections where the given object is the sender.
392 disconnectSender(object);
393 // Remove all connections where the given object is the receiver.
394 disconnectReceiver(object);
395 }
396 Private.disconnectAll = disconnectAll;
397 /**
398 * Emit a signal and invoke its connected slots.
399 *
400 * @param signal - The signal of interest.
401 *
402 * @param args - The args to pass to the connected slots.
403 *
404 * #### Notes
405 * Slots are invoked synchronously in connection order.
406 *
407 * Exceptions thrown by connected slots will be caught and logged.
408 */
409 function emit(signal, args) {
410 // If there are no receivers, there is nothing to do.
411 var receivers = receiversForSender.get(signal.sender);
412 if (!receivers || receivers.length === 0) {
413 return;
414 }
415 // Invoke the slots for connections with a matching signal.
416 // Any connections added during emission are not invoked.
417 for (var i = 0, n = receivers.length; i < n; ++i) {
418 var connection = receivers[i];
419 if (connection.signal === signal) {
420 invokeSlot(connection, args);
421 }
422 }
423 }
424 Private.emit = emit;
425 /**
426 * A weak mapping of sender to array of receiver connections.
427 */
428 var receiversForSender = new WeakMap();
429 /**
430 * A weak mapping of receiver to array of sender connections.
431 */
432 var sendersForReceiver = new WeakMap();
433 /**
434 * A set of connection arrays which are pending cleanup.
435 */
436 var dirtySet = new Set();
437 /**
438 * A function to schedule an event loop callback.
439 */
440 var schedule = (function () {
441 var ok = typeof requestAnimationFrame === 'function';
442 // @ts-ignore
443 return ok ? requestAnimationFrame : setImmediate;
444 })();
445 /**
446 * Find a connection which matches the given parameters.
447 */
448 function findConnection(connections, signal, slot, thisArg) {
449 return algorithm.find(connections, function (connection) {
450 return connection.signal === signal &&
451 connection.slot === slot &&
452 connection.thisArg === thisArg;
453 });
454 }
455 /**
456 * Invoke a slot with the given parameters.
457 *
458 * The connection is assumed to be valid.
459 *
460 * Exceptions in the slot will be caught and logged.
461 */
462 function invokeSlot(connection, args) {
463 var signal = connection.signal, slot = connection.slot, thisArg = connection.thisArg;
464 try {
465 slot.call(thisArg, signal.sender, args);
466 }
467 catch (err) {
468 Private.exceptionHandler(err);
469 }
470 }
471 /**
472 * Schedule a cleanup of a connection array.
473 *
474 * This will add the array to the dirty set and schedule a deferred
475 * cleanup of the array contents. On cleanup, any connection with a
476 * `null` signal will be removed from the array.
477 */
478 function scheduleCleanup(array) {
479 if (dirtySet.size === 0) {
480 schedule(cleanupDirtySet);
481 }
482 dirtySet.add(array);
483 }
484 /**
485 * Cleanup the connection lists in the dirty set.
486 *
487 * This function should only be invoked asynchronously, when the
488 * stack frame is guaranteed to not be on the path of user code.
489 */
490 function cleanupDirtySet() {
491 dirtySet.forEach(cleanupConnections);
492 dirtySet.clear();
493 }
494 /**
495 * Cleanup the dirty connections in a connections array.
496 *
497 * This will remove any connection with a `null` signal.
498 *
499 * This function should only be invoked asynchronously, when the
500 * stack frame is guaranteed to not be on the path of user code.
501 */
502 function cleanupConnections(connections) {
503 algorithm.ArrayExt.removeAllWhere(connections, isDeadConnection);
504 }
505 /**
506 * Test whether a connection is dead.
507 *
508 * A dead connection has a `null` signal.
509 */
510 function isDeadConnection(connection) {
511 return connection.signal === null;
512 }
513 })(Private || (Private = {}));
514
515 Object.defineProperty(exports, '__esModule', { value: true });
516
517})));
518//# sourceMappingURL=index.js.map