Source: MiniSignals.js

/**
 * Listener function and context pack.
 */
class MiniSignalBinding {

  /**
  * MiniSignalBinding constructor.
  * @constructs MiniSignalBinding
  * @param {Function} fn Event handler to be called.
  * @param {Boolean} [once=false] Should this listener be removed after dispatch
  * @param {Mixed} [thisArg] The context of the callback function.
  * @private
  */
  constructor(fn, once = false, thisArg) {
    this._fn = fn;
    this._once = once;
    this._thisArg = thisArg;
    this._next = this._prev = this._owner = null;
  }

  /**
   * Disconnect this signal binding
   * @return {Boolean} True if succeed, otherwise false
   */
  detach() {
    if (this._owner === null) {return false;}
    this._owner.detach(this);
    return true;
  }

}

/**
* @private
* @param {*} self Dispatcher itself
* @param {*} node Node to add
* @return {*}     The node just added
*/
function _addMiniSignalBinding(self, node) {
  if (!self._head) {
    self._head = node;
    self._tail = node;
  }
  else {
    self._tail._next = node;
    node._prev = self._tail;
    self._tail = node;
  }

  node._owner = self;

  return node;
}

/**
 * MiniSignal constructor.
 * @public
 *
 * @example
 * let mySignal = new MiniSignal();
 * let binding = mySignal.add(onSignal);
 * mySignal.dispatch('foo', 'bar');
 * mySignal.detach(binding);
 */
class MiniSignal {
  /**
   * @constructor
   */
  constructor() {
    this._head = this._tail = undefined;
  }

  /**
  * Return an array of attached MiniSignalBinding.
  *
  * @param {Boolean} [exists=false] We only need to know if there are handlers.
  * @returns {MiniSignalBinding[]|Boolean} Array of attached MiniSignalBinding or Boolean if called with exists = true
  * @public
  */
  handlers(exists = false) {
    let node = this._head;

    if (exists) {return !!node;}

    const ee = [];

    while (node) {
      ee.push(node);
      node = node._next;
    }

    return ee;
  }

  /**
  * Return true if node is a MiniSignalBinding attached to this MiniSignal
  *
  * @param {MiniSignalBinding} node Node to check.
  * @returns {Boolean} True if node is attache to mini-signal
  * @public
  */
  has(node) {
    if (!(node instanceof MiniSignalBinding)) {
      throw new Error('MiniSignal#has(): First arg must be a MiniSignalBinding object.');
    }

    return node._owner === this;
  }

  /**
  * Dispaches a signal to all registered listeners.
  *
  * @returns {Boolean} Indication if we've emitted an event.
  * @public
  */
  dispatch() {
    let node = this._head;

    if (!node) {return false;}

    while (node) {
      if (node._once) {this.detach(node);}
      node._fn.apply(node._thisArg, arguments);
      node = node._next;
    }

    return true;
  }

  /**
  * Register a new listener.
  *
  * @param {Function} fn Callback function.
  * @param {Mixed} [thisArg] The context of the callback function.
  * @returns {MiniSignalBinding} The MiniSignalBinding node that was added.
  * @public
  */
  add(fn, thisArg = null) {
    if (typeof fn !== 'function') {
      throw new Error('MiniSignal#add(): First arg must be a Function.');
    }
    return _addMiniSignalBinding(this, new MiniSignalBinding(fn, false, thisArg));
  }

  /**
  * Register a new listener that will be executed only once.
  *
  * @param {Function} fn Callback function.
  * @param {Mixed} [thisArg] The context of the callback function.
  * @returns {MiniSignalBinding} The MiniSignalBinding node that was added.
  * @public
  */
  once(fn, thisArg = null) {
    if (typeof fn !== 'function') {
      throw new Error('MiniSignal#once(): First arg must be a Function.');
    }
    return _addMiniSignalBinding(this, new MiniSignalBinding(fn, true, thisArg));
  }

  /**
  * Remove binding object.
  *
  * @param {MiniSignalBinding} node The binding node that will be removed.
  * @returns {MiniSignal} The instance on which this method was called.
  * @public */
  detach(node) {
    if (!(node instanceof MiniSignalBinding)) {
      throw new Error('MiniSignal#detach(): First arg must be a MiniSignalBinding object.');
    }
    if (node._owner !== this) {return this;}  // todo: or error?

    if (node._prev) {node._prev._next = node._next;}
    if (node._next) {node._next._prev = node._prev;}

    if (node === this._head) {  // first node
      this._head = node._next;
      if (node._next === null) {
        this._tail = null;
      }
    }
    else if (node === this._tail) {  // last node
      this._tail = node._prev;
      this._tail._next = null;
    }

    node._owner = null;
    return this;
  }

  /**
  * Detach all listeners.
  *
  * @returns {MiniSignal} The instance on which this method was called.
  * @public
  */
  detachAll() {
    let node = this._head;
    if (!node) {return this;}

    this._head = this._tail = null;

    while (node) {
      node._owner = null;
      node = node._next;
    }
    return this;
  }
}

module.exports = MiniSignal;
module.exports.MiniSignalBinding = MiniSignalBinding;
module.exports.MiniSignal = MiniSignal;