(function (global, factory) {
	typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
	typeof define === 'function' && define.amd ? define(factory) :
	(global.Hand = factory());
}(this, function () { 'use strict';

	var babelHelpers = {};

	babelHelpers.classCallCheck = function (instance, Constructor) {
	  if (!(instance instanceof Constructor)) {
	    throw new TypeError("Cannot call a class as a function");
	  }
	};

	babelHelpers.createClass = function () {
	  function defineProperties(target, props) {
	    for (var i = 0; i < props.length; i++) {
	      var descriptor = props[i];
	      descriptor.enumerable = descriptor.enumerable || false;
	      descriptor.configurable = true;
	      if ("value" in descriptor) descriptor.writable = true;
	      Object.defineProperty(target, descriptor.key, descriptor);
	    }
	  }

	  return function (Constructor, protoProps, staticProps) {
	    if (protoProps) defineProperties(Constructor.prototype, protoProps);
	    if (staticProps) defineProperties(Constructor, staticProps);
	    return Constructor;
	  };
	}();

	babelHelpers.extends = Object.assign || function (target) {
	  for (var i = 1; i < arguments.length; i++) {
	    var source = arguments[i];

	    for (var key in source) {
	      if (Object.prototype.hasOwnProperty.call(source, key)) {
	        target[key] = source[key];
	      }
	    }
	  }

	  return target;
	};

	babelHelpers;

	var IvansFinger = 'data:image/png;base64, ';

	// Detects some browser capabilities. Only for internal use, not exposed to the
	// API user.

	// eventConstructors
	// `true` if the browser can run `new Event()`.
	// If `false`, then the library will resort to the (deprecated) `window.createEvent()`
	// and `event.initMouseEvent()`. This is needed for legacy browsers and PhantomJS.
	var eventConstructors = true;

	try {
		var foo = new Touch({ identifier: 0, target: document });
		// 	var foo = new MouseEvent();
	} catch (e) {
		eventConstructors = false;
	}

	// mouseEventConstructor, for MouseEvent
	var mouseEventConstructor = true;

	try {
		var foo = new MouseEvent('mousedown');
	} catch (e) {
		mouseEventConstructor = false;
	}

	// touchConstructor, for Touch
	var touchConstructor = true;

	try {
		var foo = new Touch({ identifier: 0, target: document });
	} catch (e) {
		touchConstructor = false;
	}

	// touchEventConstructor, for TouchEvent
	// Weirdly, Safari on iOS has Touch constructor but no TouchEvent constructor.
	var touchEventConstructor = true;

	try {
		var foo = new TouchEvent('touchdown');
	} catch (e) {
		touchEventConstructor = false;
	}

	// pointerEventConstructor, for PointerEvent
	var pointerEventConstructor = true;

	try {
		var foo = new PointerEvent('pointerdown');
	} catch (e) {
		pointerEventConstructor = false;
	}

	// mouse: `true` if the browser implements `MouseEvent`
	var mouse = !!('MouseEvent' in window);

	// touch: `true` if the browser implements `TouchEvent`
	var touch = !!('TouchEvent' in window);

	// pointer: `true` if the browser implements `PointerEvent`
	var pointer = !!('PointerEvent' in window);

	// Borrowed from Leaflet's L.Browser

	var ua = navigator.userAgent.toLowerCase();
	var webkit = ua.indexOf('webkit') !== -1;
	var phantomjs = ua.indexOf('phantom') !== -1;
	var android23 = ua.search('android [23]') !== -1;
	var chrome = ua.indexOf('chrome') !== -1;
	var gecko = ua.indexOf('gecko') !== -1 && !webkit && !window.opera && !ie;
	var ie = ie;
	var webkit = webkit;
	var android = ua.indexOf('android') !== -1;
	var chrome = chrome;
	var safari = !chrome && ua.indexOf('safari') !== -1;

	// Self-incrementing identifier for touch ID and pointer ID.
	// Fingers can either keep the same ID for their life, or request a new
	//   ID whenever they go down.
	var fingerIdSequence = 1;

	// 🖑class Finger
	// Represents a finger, capable of performing single touch/pointer/mouse synthetic
	// events.

	/*
	🖑example

	```js
	var h = new Hand();
	var fatFinger = h.growFinger('pointer', { pointerType: 'touch', pressure: 0.9 });
	var lightFinger = h.growFinger('pointer', { pointerType: 'touch', pressure: 0.1 });

	fatFinger.wait(500).moveTo(200, 250, 0).down().moveBy(100, 150, 2000).up();
	```

	*/

	var Finger = function () {

		// 🖑factory Finger(eventMode: String, options?: Finger state): Finger
		// Instantiates a new `Finger`. `eventMode` must be `mouse`, `touch` or `pointer`
		// for `MouseEvent`s, `TouchEvent`s or `PointerEvent`s, respectively.

		function Finger(eventMode, options) {
			babelHelpers.classCallCheck(this, Finger);


			this._id = fingerIdSequence++;

			this._mode = eventMode || 'mouse';

			this._hand = options.hand;

			/// TODO: parkinsonFactor or shakesFactor or jitteryness or something

			// 🖑section Finger state
			// The internal state of a `Finger` has options which will be reflected as
			// properties of the events fired afterwards. Some of these state options
			// apply only to a specific event mode.
			this._state = babelHelpers.extends({}, {
				// 🖑option x: Number; The number of pixels from the left boundary the finger is at.
				x: 0,

				// 🖑option y: Number; The number of pixels from the top boundary the finger is at.
				y: 0,

				// 🖑option down: Boolean; Whether the finger is down (clicking/touching/pressing) or not. This is referred to as "active" in some of the events specifications.
				down: false,

				// 🖑option pressure: Number = 0.5; The value for [`Touch.force`](`https://developer.mozilla.org/docs/Web/API/Touch/force`) or [`PointerEvent.pressure`](https://developer.mozilla.org/docs/Web/API/PointerEvent/pressure), between `0.0` and `1.0`
				pressure: 0.5,

				// 🖑option tiltX: Number = 0; The value for [`PointerEvent.tiltX`](https://developer.mozilla.org/docs/Web/API/PointerEvent/tiltX)
				tiltX: 0,

				// 🖑option tiltY: Number = 0; The value for [`PointerEvent.tiltX`](https://developer.mozilla.org/docs/Web/API/PointerEvent/tiltY)
				tiltY: 0,

				// 🖑option width: Number = 25; The value for [`Touch.radiusX`](`https://developer.mozilla.org/docs/Web/API/Touch/radiusX`) or [`PointerEvent.width`](https://developer.mozilla.org/docs/Web/API/PointerEvent/width)
				width: 25,

				// 🖑option radiusY: Number = 25; The value for [`Touch.radiusY`](`https://developer.mozilla.org/docs/Web/API/Touch/radiusY`) or [`PointerEvent.height`](https://developer.mozilla.org/docs/Web/API/PointerEvent/height)
				height: 25,

				// 🖑option pointerType: String = 'pen'; The value for [`PointerEvent.pointerType`](https://developer.mozilla.org/docs/Web/API/PointerEvent/pointerType)
				pointerType: 'pen'
			}, options);

			// A "finger movement" is a plain hashmap that describes either a
			// instantaneous state change (e.g. "touch down", "up/lift touch",
			// "change pressure") or a movement("move", "wiggle", "vary pressure
			// during a few seconds").
			// A finger movement has three things:
			// * A duration (can be zero)
			// * A final finger state
			// * A function that returns the finger state (run when the movement is
			//   still running, i.e. the end timestamp is not reached yet)
			// These are called duration, finalState and getState().
			// getState() gets called with an amount of milliseconds since the last movement
			// getState() might return `false`, meaning 'nothing happened since last time'

			this._movements = [];

			// Timestamp for the end of the last movement.
			this._movesUntil = performance.now();

			// Timestamp for the start of the current movement. This is always in the past.
			this._movesFrom = performance.now();

			// Final state of the last movement (to calculate the next movement if needed).
			this._finalState = babelHelpers.extends({}, this._state);

			// This should be configurable with custom/more graphics
			if (this._mode === 'touch') {
				this._initGraphicIvansFinger();
				if (!touch) {
					console.warn('This browser cannot emulate touch events.');
				}
			} else if (this._mode === 'pointer') {
				if (this._state.pointerType === 'touch') {
					this._initGraphicIvansFinger();
				} else {
					this._initGraphicCircle();
				}
				if (!pointer) {
					console.warn('This browser cannot emulate pointer events.');
				}
			} else {
				this._mode = 'mouse';
				this._initGraphicCircle();
				if (!mouse) {
					console.warn('This browser cannot emulate mouse events.');
				}
			}

			// Only used for `TouchEvent`s (as `Touch.target`): Will hold a
			// reference to the DOM element on which the touch point started when
			// it was first placed on the surface.
			this._touchTargetWhenDowned = undefined;

			// For checking mouseover/mouseout/pointerover/pointerout events, and
			// for checking whether a `Touch` should be in the `targetTouches` `TouchList`
			this._currentTarget = undefined;
		}

		// 🖑method isIdle(): Boolean
		// Returns true when the finger has no more pending movements/waits/wiggles/etc.


		babelHelpers.createClass(Finger, [{
			key: 'isIdle',
			value: function isIdle() {
				return !this._movements.length;
			}

			// 🖑method down(delay?: Number): this
			// Puts the finger down, optionally after a delay.

		}, {
			key: 'down',
			value: function down(delay) {
				return this.update({ down: true, getState: this._falseFn, duration: delay || 0 });
			}

			// 🖑method up(options?: {}): this
			// Lifts the finger up, after an optional delay.

		}, {
			key: 'up',
			value: function up(delay) {
				return this.update({ down: false, getState: this._falseFn, duration: delay || 0 });
			}

			// 🖑method wait(delay): this
			// Don't move this finger for `delay` milliseconds.

		}, {
			key: 'wait',
			value: function wait(delay) {
				this._queueMove({ finalState: this._finalState, getState: this._falseFn, duration: delay });
				return this;
			}

			// 🖑method waitUntil(timestamp): this
			// Don't move this finger until the given timestamp is reached.

		}, {
			key: 'waitUntil',
			value: function waitUntil(timestamp) {
				if (this._movements.length) {
					return this.wait(timestamp - this._movesUntil);
				} else {
					var move = {
						finalState: this._finalState,
						getState: this._falseFn,
						duration: timestamp - performance.now(),
						until: timestamp
					};
					this._movesUntil = this._movesFrom = move.until;
					this._movements.push(move);

					this._hand.fingerIsBusy();
				}
				return this;
			}

			// 🖑method update(options?: {}): this
			// Updates some of the finger options, like pressure or touch angle,
			// without disturbing its movement, after an optional delay.

		}, {
			key: 'update',
			value: function update(options, delay) {
				this._queueMove({ finalState: options, getState: this._falseFn, duration: delay || 0 });
				return this;
			}

			// 🖑method reset(options?: {}): this
			// Clears all the queued movements for this finger and immediately lifts it up

		}, {
			key: 'reset',
			value: function reset(options) {
				return this;
			}

			// 🖑method moveTo(x: Number, y: Number, delay: Number, options?: {}): this
			// Queues moving this finger to an absolute position at `(x, y)`; the
			// movement will last for `delay` milliseconds.

		}, {
			key: 'moveTo',
			value: function moveTo(x, y, delay) {
				return this.moveBy(x - this._finalState.x, y - this._finalState.y, delay);
			}

			// 🖑method moveBy(x: Number, y: Number, delay: Number, options?: {}): this
			// Queues a move of this finger to an position relative to its last position
			// plus`(x, y)`; the movement will last for `delay` milliseconds.

		}, {
			key: 'moveBy',
			value: function moveBy(x, y, delay) {
				var fromX = this._finalState.x;
				var fromY = this._finalState.y;

				var move = {
					finalState: {
						x: fromX + x,
						y: fromY + y
					},

					getState: function (x1, y1, dx, dy) {
						return function (msec) {
							var percent = msec / delay;
							return {
								x: Math.round(x1 + dx * percent),
								y: Math.round(y1 + dy * percent)
							};
						};
					}(fromX, fromY, x, y, delay),

					duration: delay
					// 			until: this._movesUntil + delay
				};

				this._queueMove(move);

				return this;
			}

			// An aux function that always returns false

		}, {
			key: '_falseFn',
			value: function _falseFn() {
				return false;
			}

			// Queues a movement

		}, {
			key: '_queueMove',
			value: function _queueMove(move) {

				if (!this._movements.length) {
					this._movesUntil = this._movesFrom = performance.now();
				}

				move.until = this._movesUntil + move.duration;
				this._movements.push(move);

				this._finalState = move.finalState = babelHelpers.extends({}, this._finalState, move.finalState);

				this._movesUntil = move.until;

				if (this._movements.length === 1) {
					this._hand.fingerIsBusy();
				}
			}

			// Returns the timestamp when the next movement will be finished
			// 🖑method getNextMoveEndTime(): Number|undefined

		}, {
			key: 'getNextMoveEndTime',
			value: function getNextMoveEndTime() {
				if (!this._movements.length) {
					return undefined;
				}
				return this._movements[0].until;
			}

			/*
	   * 🖑method getEvents(timestamp?: Number, justOne: Boolean): []
	   * Updates the private properties of the finger (x, y, timestamp) by
	   * running the next movement(s) as far as indicated by the timestamp (or
	   * as fas as to `performance.now()`), then checks if the state has changed
	   * and means an event should be fired.
	   *
	   * If `justOne` is set to truthy, then this will run just one movements.
	   * Otherwise, it will run as many movements as needed until `timestamp` is reached.
	   *
	   * Returns an array of objects of the form `{type: 'foo', event: MouseEvent(...), finger: Finger}`
	   * or `{type: 'foo', touch: Touch(...), finger: Finger}`, with all the active `Touch`es or
	   * all triggered mouse/pointer events triggered by executing moves until
	   * `timestamp`.
	   *
	   * If the finger doesn't matter when `getEvents()` is called, then an empty
	   * array is return instead. This happens for mice not moving, and fingers
	   * not touching (fingers touching but not moving, and mice not pressing
	   * but moving *do* matter).
	   *
	   * A `Hand` is reponsible for getting events (using loops, timings, or
	   * whatever), requesting the right timestamps if needed, merging `Touch`es
	   * into `TouchEvent`s, and firing the events via `dispatchEvent()`.
	  */

		}, {
			key: 'getEvents',
			value: function getEvents(timestamp, justOne) {
				var now = timestamp || performance.now();
				var changed = false;
				var previousState = babelHelpers.extends({}, this._state);
				// Process all moves that already happened (since last frame)
				while (this._movements.length && this._movements[0].until <= now && !(changed && justOne)) {
					var done = this._movements.shift();
					babelHelpers.extends(this._state, done.finalState);
					this._movesFrom = done.until;
					changed = true;
				}

				// Process ongoing movement
				if (this._movements.length && !(changed && justOne)) {
					var move = this._movements[0];

					var updatedState = move.getState(now - this._movesFrom);

					if (updatedState && !this._statesAreEqual(updatedState, this._state)) {
						changed = true;
						babelHelpers.extends(this._state, updatedState);
					}
				}

				if (!this._movements.length) {
					this._hand.fingerIsIdle();
				}

				// 		// TODO: Add jitter if needed

				var evType = 'idle';

				if (changed) {

					if (previousState.x !== this._state.x || previousState.y !== this._state.y) {
						evType = 'move';
						this._currentTarget = document.elementFromPoint(this._state.x, this._state.y);
					}
					/// TODO: Detect over/out events when the event target changes.

					if (previousState.down && !this._state.down) {
						this._graphic.style.display = 'none';
						evType = 'up';
					} else if (!previousState.down && this._state.down) {
						// TODO: Optionally reset the finger ID and grab a fresh one

						this._graphic.style.display = 'block';
						this._touchTargetWhenDowned = this._currentTarget = document.elementFromPoint(this._state.x, this._state.y);
						evType = 'down';
					}

					this._setGraphicPosition(this._state.x, this._state.y);
				}

				// `MouseEvent`s
				if (this._mode === 'mouse' || this._mode === 'touchpad') {
					if (evType === 'idle') {
						return [];
					}
					/// TODO: Check for mouseover/mouseout events, add them to the
					/// array.
					/// TODO: Create synthetic `click` and `dblclick` events if/when
					/// needed, add them to the array.
					return [{ type: evType, event: this._asMouseEvent(evType), finger: this }];
				}

				// `PointerEvent`s
				if (this._mode === 'pointer') {
					if (evType === 'idle') {
						return [];
					}
					/// TODO: Check for pointerover/pointerout events, add them to the
					/// array.
					/// TODO: Create synthetic `click` and `dblclick` events if/when
					/// needed, add them to the array.
					return [{ type: evType, event: this._asPointerEvent(evType), finger: this }];
				}

				// `Touch`es
				if (this._mode === 'touch') {
					if (this._touchTargetWhenDowned) {

						var ret = [{ type: evType, touch: this._asTouch(evType), finger: this }];
						if (evType === 'up') {
							this._touchTargetWhenDowned = undefined;
						}

						return ret;
					} else {
						return [];
					}
				}

				return [];
			}

			// 🖑method private_asTouch(): Touch
			// Returns an instance of `Touch` representing the current state of the finger
			// Note this is not an event - a `TouchEvent` must be created later, with several
			// `Touch`es.

		}, {
			key: '_asTouch',
			value: function _asTouch() {
				var touch;
				if (touchConstructor && !safari) {
					touch = new Touch({
						identifier: this._id,
						target: this._touchTargetWhenDowned,
						clientX: this._state.x,
						clientY: this._state.y,
						screenX: this._state.x, /// TODO: Handle page scrolling
						screenY: this._state.y,
						pageX: this._state.x,
						pageY: this._state.y,
						radiusX: 25,
						radiusY: 25,
						rotationAngle: 0,
						force: this._state.pressure
					});
				} else {
					touch = document.createTouch(window, // view
					this._touchTargetWhenDowned, // target
					this._id, // identifier
					this._state.x, // pageX
					this._state.y, // pageY
					this._state.x, // screenX
					this._state.y, // screenY

					// Inconsistency: these are needed in Firefox but it MUST be ignored
					// in PhantomJS.
					// 				this._state.x,	// pageX
					// 				this._state.y,	// pageY

					// Non-standard chrome params follow:
					25, // radiusX
					25, // radiusY
					0, // rotationAngle
					this._state.pressure // force
					);
				}

				return touch;
			}

			// 🖑method private_asPointerEvent(): PointerEvent
			// Returns an instance of `PointerEvent` representing the current state of the finger

		}, {
			key: '_asPointerEvent',
			value: function _asPointerEvent(evType) {
				var ev = new PointerEvent('pointer' + evType, {
					bubbles: true,
					button: 0, // Moz doesn't use -1 when no buttons are pressed, WTF?
					// 			buttons: this._state.down ? 1 : 0,
					// 			detail: (evType === 'down' || evType === 'up') ? 1 : 0,	// TODO: count consecutive clicks
					clientX: this._state.x,
					clientY: this._state.y,
					screenX: this._state.x, /// TODO: Handle page scrolling
					screenY: this._state.y,
					pageX: this._state.x,
					pageY: this._state.y,
					pointerType: 'pen',
					pointerId: this._id,
					isPrimary: this._id === 1,
					width: this._state.width,
					height: this._state.height,
					tiltX: this._state.tiltX,
					tiltY: this._state.tiltY,
					pressure: this._state.pressure
					// 			target: document.elementFromPoint(this._state.x, this._state.y),	// works with viewport coords
				});
				return ev;
			}

			// 🖑method private_asMouseEvent(evType: String): PointerEvent
			// Returns an instance of `PointerEvent` representing the current state of the finger

		}, {
			key: '_asMouseEvent',
			value: function _asMouseEvent(evType) {
				var ev;
				if (mouseEventConstructor) {
					ev = new MouseEvent('mouse' + evType, {
						bubbles: true,
						button: 0, // Moz doesn't use -1 when no buttons are pressed, WTF?
						buttons: this._state.down ? 1 : 0,
						detail: evType === 'down' || evType === 'up' ? 1 : 0, // TODO: count consecutive clicks
						clientX: this._state.x,
						clientY: this._state.y,
						screenX: this._state.x, /// TODO: Handle page scrolling
						screenY: this._state.y,
						pageX: this._state.x,
						pageY: this._state.y
					});
				} else // 			target: document.elementFromPoint(this._state.x, this._state.y),	// works with viewport coords
					{
						// For legacy browsers and PhantomJS
						ev = document.createEvent('MouseEvent');
						ev.initMouseEvent('mouse' + evType, // Type
						true, // canBubble
						true, // cancellable
						window, // view
						0, // detail
						this._state.x, // screenX
						this._state.y, // screenY
						this._state.x, // clientX
						this._state.y, // clientY
						false, // ctrlKey
						false, // altKey
						false, // shiftKey
						false, // metaKey
						0, // button
						null // relatedTarget
						);
					}
				return ev;
			}

			// Inits this._graphic to be a SVG circle.

		}, {
			key: '_initGraphicCircle',
			value: function _initGraphicCircle() {

				this._graphic = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
				this._graphic.style.height = '50px';
				this._graphic.style.width = '50px';
				this._graphic.style.zIndex = 1000000; // Some ridiculously high value
				this._graphic.style.position = 'absolute';
				this._graphic.style.top = 0;
				this._graphic.style.left = 0;
				this._graphic.style.marginLeft = '-25px';
				this._graphic.style.marginTop = '-25px';
				this._graphic.style.pointerEvents = 'none';

				var circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
				circle.cx.baseVal.value = 25;
				circle.cy.baseVal.value = 25;
				circle.r.baseVal.value = 20;
				circle.style.stroke = 'rgba(0,0,0,0.3)';
				circle.style.strokeWidth = 2;
				circle.style.fill = 'rgba(0,0,0,0.1)';

				this._graphic.appendChild(circle);

				this._graphic.style.display = 'none';
				document.body.appendChild(this._graphic);
			}

			// Inits this._graphic to be an image of Ivan's index finger

		}, {
			key: '_initGraphicIvansFinger',
			value: function _initGraphicIvansFinger() {

				this._graphic = document.createElement('img');
				this._graphic.src = IvansFinger;
				this._graphic.style.height = '160px';
				this._graphic.style.width = '160px';
				this._graphic.style.zIndex = 1000000; // Some ridiculously high value
				this._graphic.style.position = 'absolute';
				this._graphic.style.top = 0;
				this._graphic.style.left = 0;
				this._graphic.style.marginLeft = '-20px';
				this._graphic.style.marginTop = '-20px';
				this._graphic.style.pointerEvents = 'none';

				this._graphic.style.display = 'none';
				document.body.appendChild(this._graphic);
			}
		}, {
			key: '_setGraphicPosition',
			value: function _setGraphicPosition(x, y) {
				// Borrowed from Leaflet code
				this._graphic.style.transform = 'translate3d(' + x + 'px, ' + y + 'px, 0)';
			}

			// Simple, non-deep comparison of hashmaps, used for comparing internal finger states.
			// Returns `false` when one of the properties of s1 differs from the same property
			// of s2 (or s2 doesn't have it). It ignores if s1 doesn't have all properties from
			// s2.

		}, {
			key: '_statesAreEqual',
			value: function _statesAreEqual(s1, s2) {
				for (var i in s1) {
					if (s1[i] !== s2[i]) {
						return false;
					}
				}
				return true;
			}
		}]);
		return Finger;
	}();

	// Exports a bunch of symbols^H^H^H^H^H ints for use as enums

	var INTERVAL = 1;
	var MINIMAL = 2;
	var INSTANT = 3;
	var FRAME = 4;
	var FASTFRAME = 5;

	// export var INTERVAL = new Symbol();
	// export var MINIMAL = new Symbol();
	// export var INSTANT = new Symbol();
	//

	// 🖑class Hand
	// Represents a set of `Finger`s, capable of performing synthetic touch gestures

	/*
	🖑example

	```js
	var h = new Hand({ timing: '20ms' });
	```

	*/

	var Hand = function () {

		// 🖑factory Hand(options?: Hand options): Hand
		// Instantiates a new `Hand` with the given options.

		function Hand(options) {
			babelHelpers.classCallCheck(this, Hand);


			if (!options) {
				options = {};
			}

			this._fingers = [];

			this._fingersAreIdle = true;

			/// TODO: Timing modes: minimal, interval, frames

			// 🖑option timing: Timing= '20ms'
			// Defines how often new events will be fired, in one of the possible
			// timing modes

			// 🖑miniclass Timing (Hand)
			this._timeInterval = 20;
			this._timingMode = INTERVAL;
			this._framesPending = 0;
			if (options.timing) {
				var timing = options.timing.toString();

				// 🖑option ms
				// Preceded by a number (e.g. `'20ms'`), this mode triggers an event
				// dispatch every that many milliseconds.
				if (timing.match(/^\d+ms$/)) {
					this._timingMode = INTERVAL;
					this._timeInterval = parseInt(timing);
				}

				// 🖑option frame
				// This mode dispatches an event every [animation frame](https://developer.mozilla.org/docs/Web/API/Window/requestAnimationFrame).
				if (timing === 'frame') {
					this._timingMode = FRAME;
					this._timeInterval = parseInt(timing);
				}

				// 🖑option minimal
				// This mode triggers an event dispatch per finger change, and ensures
				// that every move can trigger its own event (no two movements will be
				// rolled into one event if they are very close).
				if (timing === 'minimal') {
					this._timingMode = MINIMAL;
					this._timeInterval = false;
				}

				// 🖑option instant
				// Like the `minimal` mode, but ignores timings completely and dispatches
				// all events instantaneously. This might cause misbehaviour in graphical
				// browsers.
				if (timing === 'instant') {
					this._timingMode = INSTANT;
					this._timeInterval = false;
				}

				// 🖑option fastframe
				// This mode ignores timings completely like the `instant` mode, and
				// dispatches a new event every so many frames.
				if (timing === 'fastframe') {
					this._timingMode = FASTFRAME;
					this._timeInterval = parseInt(timing);
				}
			}
			// 🖑class Hand

			// Cancellable reference to the next call to `_dispatchEvents`. This
			// might be either a `setTimeout` reference or a `requestAnimationFrame`
			// reference.
			this._nextDispatch = null;
		}

		// 🖑method growFinger(eventMode, options): Finger
		// Creates a new `Finger` with the same parameters as the [`Finger` constructor](#finger-finger),
		// and adds it to the hand.


		babelHelpers.createClass(Hand, [{
			key: 'growFinger',
			value: function growFinger(fingerMode, options) {

				if (!options) {
					options = {};
				}
				babelHelpers.extends(options, { hand: this });

				var finger = new Finger(fingerMode, options);

				this._fingers.push(finger);
				return finger;
			}

			// 🖑method fingerIsBusy(): this
			// Used by this hand's fingers to signal that there are movements to be
			// performed by at least one finger.

		}, {
			key: 'fingerIsBusy',
			value: function fingerIsBusy() {

				/// TODO: Start up the event loop

				if (this._fingersAreIdle) {
					// 🖑section
					// Use `document.addEventListener('prostheticHandStop', fn)` to
					// do stuff with it.
					// 🖑event prostheticHandStart: CustomEvent
					// Fired when all movements are complete.
					document.dispatchEvent(new CustomEvent('prostheticHandStart', { target: this }));

					this._fingersAreIdle = false;
					this._scheduleNextDispatch();
				}

				return this;
			}

			// 🖑method fingerIsIdle(): this
			// Used by this hand's fingers to signal that one finger has finished doing
			// all the queued movements.

		}, {
			key: 'fingerIsIdle',
			value: function fingerIsIdle() {

				if (this._fingers.every(function (f) {
					return f.isIdle();
				})) {

					if (!this._fingersAreIdle) {
						// 🖑event prostheticHandStop: CustomEvent
						// Fired when all movements are complete.

						document.dispatchEvent(new CustomEvent('prostheticHandStop', { target: this }));
					}

					this._fingersAreIdle = true;
				}
			}

			// 🖑method sync(delay): this
			// Synchronizes the finger movements by adding a delay of **at least** `delay`
			// milliseconds to each finger. After a sync, the movements of the fingers
			// will happen at exactly the same time.

		}, {
			key: 'sync',
			value: function sync(delay) {

				var endTimestamp = performance.now();

				this._fingers.forEach(function (f) {
					var movesUntil = f._movesUntil;
					if (movesUntil) {
						endTimestamp = Math.max(endTimestamp, movesUntil);
					}
				});

				var waitUntil = endTimestamp + delay;

				this._fingers.forEach(function (f) {
					f.waitUntil(waitUntil);
				});
			}

			// 🖑method private_dispatchEvents(): this
			// Updates all the fingers, fetching their events/touchpoints, and dispatches
			// all `Event`s triggered by the update.
			// This is meant to be called on an internal timer.

		}, {
			key: '_dispatchEvents',
			value: function _dispatchEvents(timestamp) {

				// 🖑event prostheticHandTick: CustomEvent
				// Fired a movement is about to start, just before the mouse/touch/pointer
				// events are fired.
				document.dispatchEvent(new CustomEvent('prostheticHandStart', { target: this }));

				var now = timestamp || performance.now();
				var events = [];
				var touches = [];
				var changedTouches = [];

				var hasTouchStart = false;
				var touchStartTarget = undefined;
				var hasTouchEnd = false;
				var touchEndTarget = undefined;

				var fast = this._timingMode === MINIMAL || this._timingMode === INSTANT || this._timingMode === FASTFRAME;

				this._fingers.forEach(function (f) {

					var evs = f.getEvents(now, fast);

					evs.forEach(function (ev) {
						if ('event' in ev) {
							events.push(ev.event);
						}
						if ('touch' in ev) {
							if (ev.type === 'down') {
								hasTouchStart = true;
								touchStartTarget = ev.touch.target;
								// If several touches start in the same instant at
								// the diffetent targets, this code will instead
								// assume the last target.
							}

							if (ev.type === 'up') {
								hasTouchEnd = true;
								touchEndTarget = ev.touch.target;
							} else {
								// Touches which have just been lost must not be added
								// to 'touches' or 'targetTouches'
								touches.push(ev.touch);
							}

							if (ev.type !== 'idle') {
								changedTouches.push(ev.touch);
							}
						}
					});
				});

				// Fire all `MouseEvent`s and `PointerEvent`s
				events.forEach(function (ev) {
					// 			console.log('Dispatching: ', ev.type);
					document.elementFromPoint(ev.clientX, ev.clientY).dispatchEvent(ev);
				});

				/// Build *ONE* `TouchEvent` with `TouchList`s built with
				/// the fingers' touches.
				if (touches.length || hasTouchEnd) {
					var touchEvent;
					var touchTarget;

					if (hasTouchStart) {
						// In case touches are added and removed on the same instant,
						// `touchstart` takes precedence.

						touchEvent = this._createTouchEvent("touchstart", {
							cancelable: true,
							bubbles: true,
							touches: touches,
							targetTouches: touches.filter(function (t) {
								return t.target === touchStartTarget;
							}),
							changedTouches: changedTouches
						});
						touchTarget = touchStartTarget;
						// console.log('synthesizing touchstart', touchStartTarget, touchEvent);
					} else if (hasTouchEnd) {

							// This goes directly against the specifications!! They say:
							// «touchend: The event's target is the same element that received
							// the touchstart event corresponding to the touch point, even
							// if the touch point has moved outside that element.»
							touchEndTarget = document.elementFromPoint(changedTouches[0].clientX, changedTouches[0].clientY);

							touchEvent = this._createTouchEvent("touchend", {
								cancelable: true,
								bubbles: true,
								touches: touches,
								target: touchEndTarget,
								targetTouches: touches.filter(function (t) {
									return t.target === touchEndTarget;
								}),
								changedTouches: changedTouches
							});
							touchTarget = touchEndTarget;
							// console.log('synthesizing touchend', touchEndTarget, touchEvent);
						} else {

								// I have no idea what I'm doing!!!!1
								// Apparently dispatching a touch event to the target of a touch
								// will not work.
								// 				touchTarget = touches[0].target;
								touchTarget = document.elementFromPoint(touches[0].clientX, touches[0].clientY);

								touchEvent = this._createTouchEvent("touchmove", {
									cancelable: true,
									bubbles: true,
									touches: touches,
									targetTouches: touches.filter(function (t) {
										return t.target === touchTarget;
									}),
									changedTouches: changedTouches
								});
							}

					if (changedTouches.length) {
						// console.log('Dispatching touch event:', touchEvent.type, touchEvent, touchTarget);

						// Safari misbehaves when searching for the elementFromPoint(0, 0)
						// and returns `undefined` instead of `document`
						if (!touchTarget) {
							touchTarget = document;
						}

						touchTarget.dispatchEvent(touchEvent);
					}
				}

				this._scheduleNextDispatch();

				return this;
			}

			// Wrapper over `new Event()` or `createEvent(); initTouchEvent()` depending
			// on what the browser supports.

		}, {
			key: '_createTouchEvent',
			value: function _createTouchEvent(type, data) {
				if (touchEventConstructor) {
					return new TouchEvent(type, data);
				} else {
					// It's ugly, it's legacy, but it should work.
					// See https://miketaylr.com/posts/2015/09/init-touch-event-is-a-rats-nest.html

					var touchEvent;
					try {
						touchEvent = document.createEvent('TouchEvent');
					} catch (e) {
						touchEvent = document.createEvent('UIEvent');
					}

					if (touchEvent && touchEvent.initTouchEvent) {
						if (touchEvent.initTouchEvent.length == 0 && !safari) {
							// Chrome
							touchEvent.initTouchEvent(this._createTouchListFromArray(data.touches), this._createTouchListFromArray(data.targetTouches), this._createTouchListFromArray(data.changedTouches), type, window, 0, // screenX
							0, // screenY
							0, // clientX
							0 // clientY
							);
						} else if (touchEvent.initTouchEvent.length == 12) {
								//firefox
								touchEvent.initTouchEvent(type, data.bubbles, data.cancelable, window, data.detail, data.ctrlKey, data.altKey, data.shiftKey, data.metaKey, this._createTouchListFromArray(data.touches), this._createTouchListFromArray(data.targetTouches), this._createTouchListFromArray(data.changedTouches));
							} else {
								// Safari (length = 18, shows 0)
								// https://developer.apple.com/library/safari/documentation/UserExperience/Reference/TouchEventClassReference/index.html#//apple_ref/javascript/instm/TouchEvent/initTouchEvent
								touchEvent.initTouchEvent(type, data.bubbles, data.cancelable, window, data.detail, 0, // screenX,
								0, // screenY,
								0, // pageX,
								0, // pageY,
								data.ctrlKey, data.altKey, data.shiftKey, data.metaKey, this._createTouchListFromArray(data.touches), this._createTouchListFromArray(data.targetTouches), this._createTouchListFromArray(data.changedTouches), 1, // data.scale,	- as factor of initial distance between the two first touches
								0 // data.rotation	- as delta of angle of two first touches
								);
							}
					}

					return touchEvent;
				}
			}

			// Stupid wrapper over document.createTouchList, because PhantomJS
			// doesn't like document.createTouchList.apply. I know this is a hack.
			// **Hopefully** nobody needs more than 8 fingers at the same time in PhantomJS.

		}, {
			key: '_createTouchListFromArray',
			value: function _createTouchListFromArray(touches) {
				switch (touches.length) {
					case 0:
						return document.createTouchList();
					case 1:
						return document.createTouchList(touches[0]);
					case 2:
						return document.createTouchList(touches[0], touches[1]);
					case 3:
						return document.createTouchList(touches[0], touches[1], touches[2]);
					case 4:
						return document.createTouchList(touches[0], touches[1], touches[2], touches[3]);
					case 5:
						return document.createTouchList(touches[0], touches[1], touches[2], touches[3], touches[4]);
					case 6:
						return document.createTouchList(touches[0], touches[1], touches[2], touches[3], touches[4], touches[5]);
					case 7:
						return document.createTouchList(touches[0], touches[1], touches[2], touches[3], touches[4], touches[5], touches[6]);
					default:
						return document.createTouchList(touches[0], touches[1], touches[2], touches[3], touches[4], touches[5], touches[6], touches[7]);
				}
			}
		}, {
			key: '_scheduleNextDispatch',
			value: function _scheduleNextDispatch() {
				if (!this._fingersAreIdle) {

					// Calculate time for next movement end. Could be refactored out for
					// some timing modes.
					var min = Infinity;
					this._fingers.forEach(function (f) {
						if (!f.isIdle()) {
							var next = f.getNextMoveEndTime();
							// 						console.log('next:', next);
							if (next < min) {
								min = next;
							}
						}
					});

					if (this._timingMode === INTERVAL) {
						this._nextDispatch = setTimeout(this._dispatchEvents.bind(this), this._timeInterval);
					} else if (this._timingMode === MINIMAL) {
						this._nextDispatch = setTimeout(this._dispatchEvents.bind(this), min - performance.now());
					} else if (this._timingMode === INSTANT) {
						return this._dispatchEvents(min);
					} else if (this._timingMode === FRAME) {
						this._nextDispatch = requestAnimationFrame(this._dispatchEvents.bind(this));
					} else if (this._timingMode === FASTFRAME) {
						this._nextDispatch = requestAnimationFrame(function () {
							this._dispatchEvents(min);
						}.bind(this));
					}
				}
			}
		}]);
		return Hand;
	}();

	return Hand;

}));
//# sourceMappingURL=prosthetic-hand.js.map
