Implemented URL query parsing for initial token /opa/?token=abcde
[src/app-framework-demo.git] / afb-client / bower_components / hammerjs / hammer.js
1 /*! Hammer.JS - v2.0.6 - 2015-12-23
2  * http://hammerjs.github.io/
3  *
4  * Copyright (c) 2015 Jorik Tangelder;
5  * Licensed under the  license */
6 (function(window, document, exportName, undefined) {
7   'use strict';
8
9 var VENDOR_PREFIXES = ['', 'webkit', 'Moz', 'MS', 'ms', 'o'];
10 var TEST_ELEMENT = document.createElement('div');
11
12 var TYPE_FUNCTION = 'function';
13
14 var round = Math.round;
15 var abs = Math.abs;
16 var now = Date.now;
17
18 /**
19  * set a timeout with a given scope
20  * @param {Function} fn
21  * @param {Number} timeout
22  * @param {Object} context
23  * @returns {number}
24  */
25 function setTimeoutContext(fn, timeout, context) {
26     return setTimeout(bindFn(fn, context), timeout);
27 }
28
29 /**
30  * if the argument is an array, we want to execute the fn on each entry
31  * if it aint an array we don't want to do a thing.
32  * this is used by all the methods that accept a single and array argument.
33  * @param {*|Array} arg
34  * @param {String} fn
35  * @param {Object} [context]
36  * @returns {Boolean}
37  */
38 function invokeArrayArg(arg, fn, context) {
39     if (Array.isArray(arg)) {
40         each(arg, context[fn], context);
41         return true;
42     }
43     return false;
44 }
45
46 /**
47  * walk objects and arrays
48  * @param {Object} obj
49  * @param {Function} iterator
50  * @param {Object} context
51  */
52 function each(obj, iterator, context) {
53     var i;
54
55     if (!obj) {
56         return;
57     }
58
59     if (obj.forEach) {
60         obj.forEach(iterator, context);
61     } else if (obj.length !== undefined) {
62         i = 0;
63         while (i < obj.length) {
64             iterator.call(context, obj[i], i, obj);
65             i++;
66         }
67     } else {
68         for (i in obj) {
69             obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj);
70         }
71     }
72 }
73
74 /**
75  * wrap a method with a deprecation warning and stack trace
76  * @param {Function} method
77  * @param {String} name
78  * @param {String} message
79  * @returns {Function} A new function wrapping the supplied method.
80  */
81 function deprecate(method, name, message) {
82     var deprecationMessage = 'DEPRECATED METHOD: ' + name + '\n' + message + ' AT \n';
83     return function() {
84         var e = new Error('get-stack-trace');
85         var stack = e && e.stack ? e.stack.replace(/^[^\(]+?[\n$]/gm, '')
86             .replace(/^\s+at\s+/gm, '')
87             .replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@') : 'Unknown Stack Trace';
88
89         var log = window.console && (window.console.warn || window.console.log);
90         if (log) {
91             log.call(window.console, deprecationMessage, stack);
92         }
93         return method.apply(this, arguments);
94     };
95 }
96
97 /**
98  * extend object.
99  * means that properties in dest will be overwritten by the ones in src.
100  * @param {Object} target
101  * @param {...Object} objects_to_assign
102  * @returns {Object} target
103  */
104 var assign;
105 if (typeof Object.assign !== 'function') {
106     assign = function assign(target) {
107         if (target === undefined || target === null) {
108             throw new TypeError('Cannot convert undefined or null to object');
109         }
110
111         var output = Object(target);
112         for (var index = 1; index < arguments.length; index++) {
113             var source = arguments[index];
114             if (source !== undefined && source !== null) {
115                 for (var nextKey in source) {
116                     if (source.hasOwnProperty(nextKey)) {
117                         output[nextKey] = source[nextKey];
118                     }
119                 }
120             }
121         }
122         return output;
123     };
124 } else {
125     assign = Object.assign;
126 }
127
128 /**
129  * extend object.
130  * means that properties in dest will be overwritten by the ones in src.
131  * @param {Object} dest
132  * @param {Object} src
133  * @param {Boolean=false} [merge]
134  * @returns {Object} dest
135  */
136 var extend = deprecate(function extend(dest, src, merge) {
137     var keys = Object.keys(src);
138     var i = 0;
139     while (i < keys.length) {
140         if (!merge || (merge && dest[keys[i]] === undefined)) {
141             dest[keys[i]] = src[keys[i]];
142         }
143         i++;
144     }
145     return dest;
146 }, 'extend', 'Use `assign`.');
147
148 /**
149  * merge the values from src in the dest.
150  * means that properties that exist in dest will not be overwritten by src
151  * @param {Object} dest
152  * @param {Object} src
153  * @returns {Object} dest
154  */
155 var merge = deprecate(function merge(dest, src) {
156     return extend(dest, src, true);
157 }, 'merge', 'Use `assign`.');
158
159 /**
160  * simple class inheritance
161  * @param {Function} child
162  * @param {Function} base
163  * @param {Object} [properties]
164  */
165 function inherit(child, base, properties) {
166     var baseP = base.prototype,
167         childP;
168
169     childP = child.prototype = Object.create(baseP);
170     childP.constructor = child;
171     childP._super = baseP;
172
173     if (properties) {
174         assign(childP, properties);
175     }
176 }
177
178 /**
179  * simple function bind
180  * @param {Function} fn
181  * @param {Object} context
182  * @returns {Function}
183  */
184 function bindFn(fn, context) {
185     return function boundFn() {
186         return fn.apply(context, arguments);
187     };
188 }
189
190 /**
191  * let a boolean value also be a function that must return a boolean
192  * this first item in args will be used as the context
193  * @param {Boolean|Function} val
194  * @param {Array} [args]
195  * @returns {Boolean}
196  */
197 function boolOrFn(val, args) {
198     if (typeof val == TYPE_FUNCTION) {
199         return val.apply(args ? args[0] || undefined : undefined, args);
200     }
201     return val;
202 }
203
204 /**
205  * use the val2 when val1 is undefined
206  * @param {*} val1
207  * @param {*} val2
208  * @returns {*}
209  */
210 function ifUndefined(val1, val2) {
211     return (val1 === undefined) ? val2 : val1;
212 }
213
214 /**
215  * addEventListener with multiple events at once
216  * @param {EventTarget} target
217  * @param {String} types
218  * @param {Function} handler
219  */
220 function addEventListeners(target, types, handler) {
221     each(splitStr(types), function(type) {
222         target.addEventListener(type, handler, false);
223     });
224 }
225
226 /**
227  * removeEventListener with multiple events at once
228  * @param {EventTarget} target
229  * @param {String} types
230  * @param {Function} handler
231  */
232 function removeEventListeners(target, types, handler) {
233     each(splitStr(types), function(type) {
234         target.removeEventListener(type, handler, false);
235     });
236 }
237
238 /**
239  * find if a node is in the given parent
240  * @method hasParent
241  * @param {HTMLElement} node
242  * @param {HTMLElement} parent
243  * @return {Boolean} found
244  */
245 function hasParent(node, parent) {
246     while (node) {
247         if (node == parent) {
248             return true;
249         }
250         node = node.parentNode;
251     }
252     return false;
253 }
254
255 /**
256  * small indexOf wrapper
257  * @param {String} str
258  * @param {String} find
259  * @returns {Boolean} found
260  */
261 function inStr(str, find) {
262     return str.indexOf(find) > -1;
263 }
264
265 /**
266  * split string on whitespace
267  * @param {String} str
268  * @returns {Array} words
269  */
270 function splitStr(str) {
271     return str.trim().split(/\s+/g);
272 }
273
274 /**
275  * find if a array contains the object using indexOf or a simple polyFill
276  * @param {Array} src
277  * @param {String} find
278  * @param {String} [findByKey]
279  * @return {Boolean|Number} false when not found, or the index
280  */
281 function inArray(src, find, findByKey) {
282     if (src.indexOf && !findByKey) {
283         return src.indexOf(find);
284     } else {
285         var i = 0;
286         while (i < src.length) {
287             if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) {
288                 return i;
289             }
290             i++;
291         }
292         return -1;
293     }
294 }
295
296 /**
297  * convert array-like objects to real arrays
298  * @param {Object} obj
299  * @returns {Array}
300  */
301 function toArray(obj) {
302     return Array.prototype.slice.call(obj, 0);
303 }
304
305 /**
306  * unique array with objects based on a key (like 'id') or just by the array's value
307  * @param {Array} src [{id:1},{id:2},{id:1}]
308  * @param {String} [key]
309  * @param {Boolean} [sort=False]
310  * @returns {Array} [{id:1},{id:2}]
311  */
312 function uniqueArray(src, key, sort) {
313     var results = [];
314     var values = [];
315     var i = 0;
316
317     while (i < src.length) {
318         var val = key ? src[i][key] : src[i];
319         if (inArray(values, val) < 0) {
320             results.push(src[i]);
321         }
322         values[i] = val;
323         i++;
324     }
325
326     if (sort) {
327         if (!key) {
328             results = results.sort();
329         } else {
330             results = results.sort(function sortUniqueArray(a, b) {
331                 return a[key] > b[key];
332             });
333         }
334     }
335
336     return results;
337 }
338
339 /**
340  * get the prefixed property
341  * @param {Object} obj
342  * @param {String} property
343  * @returns {String|Undefined} prefixed
344  */
345 function prefixed(obj, property) {
346     var prefix, prop;
347     var camelProp = property[0].toUpperCase() + property.slice(1);
348
349     var i = 0;
350     while (i < VENDOR_PREFIXES.length) {
351         prefix = VENDOR_PREFIXES[i];
352         prop = (prefix) ? prefix + camelProp : property;
353
354         if (prop in obj) {
355             return prop;
356         }
357         i++;
358     }
359     return undefined;
360 }
361
362 /**
363  * get a unique id
364  * @returns {number} uniqueId
365  */
366 var _uniqueId = 1;
367 function uniqueId() {
368     return _uniqueId++;
369 }
370
371 /**
372  * get the window object of an element
373  * @param {HTMLElement} element
374  * @returns {DocumentView|Window}
375  */
376 function getWindowForElement(element) {
377     var doc = element.ownerDocument || element;
378     return (doc.defaultView || doc.parentWindow || window);
379 }
380
381 var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;
382
383 var SUPPORT_TOUCH = ('ontouchstart' in window);
384 var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined;
385 var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent);
386
387 var INPUT_TYPE_TOUCH = 'touch';
388 var INPUT_TYPE_PEN = 'pen';
389 var INPUT_TYPE_MOUSE = 'mouse';
390 var INPUT_TYPE_KINECT = 'kinect';
391
392 var COMPUTE_INTERVAL = 25;
393
394 var INPUT_START = 1;
395 var INPUT_MOVE = 2;
396 var INPUT_END = 4;
397 var INPUT_CANCEL = 8;
398
399 var DIRECTION_NONE = 1;
400 var DIRECTION_LEFT = 2;
401 var DIRECTION_RIGHT = 4;
402 var DIRECTION_UP = 8;
403 var DIRECTION_DOWN = 16;
404
405 var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT;
406 var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN;
407 var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
408
409 var PROPS_XY = ['x', 'y'];
410 var PROPS_CLIENT_XY = ['clientX', 'clientY'];
411
412 /**
413  * create new input type manager
414  * @param {Manager} manager
415  * @param {Function} callback
416  * @returns {Input}
417  * @constructor
418  */
419 function Input(manager, callback) {
420     var self = this;
421     this.manager = manager;
422     this.callback = callback;
423     this.element = manager.element;
424     this.target = manager.options.inputTarget;
425
426     // smaller wrapper around the handler, for the scope and the enabled state of the manager,
427     // so when disabled the input events are completely bypassed.
428     this.domHandler = function(ev) {
429         if (boolOrFn(manager.options.enable, [manager])) {
430             self.handler(ev);
431         }
432     };
433
434     this.init();
435
436 }
437
438 Input.prototype = {
439     /**
440      * should handle the inputEvent data and trigger the callback
441      * @virtual
442      */
443     handler: function() { },
444
445     /**
446      * bind the events
447      */
448     init: function() {
449         this.evEl && addEventListeners(this.element, this.evEl, this.domHandler);
450         this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler);
451         this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
452     },
453
454     /**
455      * unbind the events
456      */
457     destroy: function() {
458         this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler);
459         this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler);
460         this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
461     }
462 };
463
464 /**
465  * create new input type manager
466  * called by the Manager constructor
467  * @param {Hammer} manager
468  * @returns {Input}
469  */
470 function createInputInstance(manager) {
471     var Type;
472     var inputClass = manager.options.inputClass;
473
474     if (inputClass) {
475         Type = inputClass;
476     } else if (SUPPORT_POINTER_EVENTS) {
477         Type = PointerEventInput;
478     } else if (SUPPORT_ONLY_TOUCH) {
479         Type = TouchInput;
480     } else if (!SUPPORT_TOUCH) {
481         Type = MouseInput;
482     } else {
483         Type = TouchMouseInput;
484     }
485     return new (Type)(manager, inputHandler);
486 }
487
488 /**
489  * handle input events
490  * @param {Manager} manager
491  * @param {String} eventType
492  * @param {Object} input
493  */
494 function inputHandler(manager, eventType, input) {
495     var pointersLen = input.pointers.length;
496     var changedPointersLen = input.changedPointers.length;
497     var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0));
498     var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0));
499
500     input.isFirst = !!isFirst;
501     input.isFinal = !!isFinal;
502
503     if (isFirst) {
504         manager.session = {};
505     }
506
507     // source event is the normalized value of the domEvents
508     // like 'touchstart, mouseup, pointerdown'
509     input.eventType = eventType;
510
511     // compute scale, rotation etc
512     computeInputData(manager, input);
513
514     // emit secret event
515     manager.emit('hammer.input', input);
516
517     manager.recognize(input);
518     manager.session.prevInput = input;
519 }
520
521 /**
522  * extend the data with some usable properties like scale, rotate, velocity etc
523  * @param {Object} manager
524  * @param {Object} input
525  */
526 function computeInputData(manager, input) {
527     var session = manager.session;
528     var pointers = input.pointers;
529     var pointersLength = pointers.length;
530
531     // store the first input to calculate the distance and direction
532     if (!session.firstInput) {
533         session.firstInput = simpleCloneInputData(input);
534     }
535
536     // to compute scale and rotation we need to store the multiple touches
537     if (pointersLength > 1 && !session.firstMultiple) {
538         session.firstMultiple = simpleCloneInputData(input);
539     } else if (pointersLength === 1) {
540         session.firstMultiple = false;
541     }
542
543     var firstInput = session.firstInput;
544     var firstMultiple = session.firstMultiple;
545     var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center;
546
547     var center = input.center = getCenter(pointers);
548     input.timeStamp = now();
549     input.deltaTime = input.timeStamp - firstInput.timeStamp;
550
551     input.angle = getAngle(offsetCenter, center);
552     input.distance = getDistance(offsetCenter, center);
553
554     computeDeltaXY(session, input);
555     input.offsetDirection = getDirection(input.deltaX, input.deltaY);
556
557     var overallVelocity = getVelocity(input.deltaTime, input.deltaX, input.deltaY);
558     input.overallVelocityX = overallVelocity.x;
559     input.overallVelocityY = overallVelocity.y;
560     input.overallVelocity = (abs(overallVelocity.x) > abs(overallVelocity.y)) ? overallVelocity.x : overallVelocity.y;
561
562     input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1;
563     input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0;
564
565     input.maxPointers = !session.prevInput ? input.pointers.length : ((input.pointers.length >
566         session.prevInput.maxPointers) ? input.pointers.length : session.prevInput.maxPointers);
567
568     computeIntervalInputData(session, input);
569
570     // find the correct target
571     var target = manager.element;
572     if (hasParent(input.srcEvent.target, target)) {
573         target = input.srcEvent.target;
574     }
575     input.target = target;
576 }
577
578 function computeDeltaXY(session, input) {
579     var center = input.center;
580     var offset = session.offsetDelta || {};
581     var prevDelta = session.prevDelta || {};
582     var prevInput = session.prevInput || {};
583
584     if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) {
585         prevDelta = session.prevDelta = {
586             x: prevInput.deltaX || 0,
587             y: prevInput.deltaY || 0
588         };
589
590         offset = session.offsetDelta = {
591             x: center.x,
592             y: center.y
593         };
594     }
595
596     input.deltaX = prevDelta.x + (center.x - offset.x);
597     input.deltaY = prevDelta.y + (center.y - offset.y);
598 }
599
600 /**
601  * velocity is calculated every x ms
602  * @param {Object} session
603  * @param {Object} input
604  */
605 function computeIntervalInputData(session, input) {
606     var last = session.lastInterval || input,
607         deltaTime = input.timeStamp - last.timeStamp,
608         velocity, velocityX, velocityY, direction;
609
610     if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined)) {
611         var deltaX = input.deltaX - last.deltaX;
612         var deltaY = input.deltaY - last.deltaY;
613
614         var v = getVelocity(deltaTime, deltaX, deltaY);
615         velocityX = v.x;
616         velocityY = v.y;
617         velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y;
618         direction = getDirection(deltaX, deltaY);
619
620         session.lastInterval = input;
621     } else {
622         // use latest velocity info if it doesn't overtake a minimum period
623         velocity = last.velocity;
624         velocityX = last.velocityX;
625         velocityY = last.velocityY;
626         direction = last.direction;
627     }
628
629     input.velocity = velocity;
630     input.velocityX = velocityX;
631     input.velocityY = velocityY;
632     input.direction = direction;
633 }
634
635 /**
636  * create a simple clone from the input used for storage of firstInput and firstMultiple
637  * @param {Object} input
638  * @returns {Object} clonedInputData
639  */
640 function simpleCloneInputData(input) {
641     // make a simple copy of the pointers because we will get a reference if we don't
642     // we only need clientXY for the calculations
643     var pointers = [];
644     var i = 0;
645     while (i < input.pointers.length) {
646         pointers[i] = {
647             clientX: round(input.pointers[i].clientX),
648             clientY: round(input.pointers[i].clientY)
649         };
650         i++;
651     }
652
653     return {
654         timeStamp: now(),
655         pointers: pointers,
656         center: getCenter(pointers),
657         deltaX: input.deltaX,
658         deltaY: input.deltaY
659     };
660 }
661
662 /**
663  * get the center of all the pointers
664  * @param {Array} pointers
665  * @return {Object} center contains `x` and `y` properties
666  */
667 function getCenter(pointers) {
668     var pointersLength = pointers.length;
669
670     // no need to loop when only one touch
671     if (pointersLength === 1) {
672         return {
673             x: round(pointers[0].clientX),
674             y: round(pointers[0].clientY)
675         };
676     }
677
678     var x = 0, y = 0, i = 0;
679     while (i < pointersLength) {
680         x += pointers[i].clientX;
681         y += pointers[i].clientY;
682         i++;
683     }
684
685     return {
686         x: round(x / pointersLength),
687         y: round(y / pointersLength)
688     };
689 }
690
691 /**
692  * calculate the velocity between two points. unit is in px per ms.
693  * @param {Number} deltaTime
694  * @param {Number} x
695  * @param {Number} y
696  * @return {Object} velocity `x` and `y`
697  */
698 function getVelocity(deltaTime, x, y) {
699     return {
700         x: x / deltaTime || 0,
701         y: y / deltaTime || 0
702     };
703 }
704
705 /**
706  * get the direction between two points
707  * @param {Number} x
708  * @param {Number} y
709  * @return {Number} direction
710  */
711 function getDirection(x, y) {
712     if (x === y) {
713         return DIRECTION_NONE;
714     }
715
716     if (abs(x) >= abs(y)) {
717         return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
718     }
719     return y < 0 ? DIRECTION_UP : DIRECTION_DOWN;
720 }
721
722 /**
723  * calculate the absolute distance between two points
724  * @param {Object} p1 {x, y}
725  * @param {Object} p2 {x, y}
726  * @param {Array} [props] containing x and y keys
727  * @return {Number} distance
728  */
729 function getDistance(p1, p2, props) {
730     if (!props) {
731         props = PROPS_XY;
732     }
733     var x = p2[props[0]] - p1[props[0]],
734         y = p2[props[1]] - p1[props[1]];
735
736     return Math.sqrt((x * x) + (y * y));
737 }
738
739 /**
740  * calculate the angle between two coordinates
741  * @param {Object} p1
742  * @param {Object} p2
743  * @param {Array} [props] containing x and y keys
744  * @return {Number} angle
745  */
746 function getAngle(p1, p2, props) {
747     if (!props) {
748         props = PROPS_XY;
749     }
750     var x = p2[props[0]] - p1[props[0]],
751         y = p2[props[1]] - p1[props[1]];
752     return Math.atan2(y, x) * 180 / Math.PI;
753 }
754
755 /**
756  * calculate the rotation degrees between two pointersets
757  * @param {Array} start array of pointers
758  * @param {Array} end array of pointers
759  * @return {Number} rotation
760  */
761 function getRotation(start, end) {
762     return getAngle(end[1], end[0], PROPS_CLIENT_XY) + getAngle(start[1], start[0], PROPS_CLIENT_XY);
763 }
764
765 /**
766  * calculate the scale factor between two pointersets
767  * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
768  * @param {Array} start array of pointers
769  * @param {Array} end array of pointers
770  * @return {Number} scale
771  */
772 function getScale(start, end) {
773     return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY);
774 }
775
776 var MOUSE_INPUT_MAP = {
777     mousedown: INPUT_START,
778     mousemove: INPUT_MOVE,
779     mouseup: INPUT_END
780 };
781
782 var MOUSE_ELEMENT_EVENTS = 'mousedown';
783 var MOUSE_WINDOW_EVENTS = 'mousemove mouseup';
784
785 /**
786  * Mouse events input
787  * @constructor
788  * @extends Input
789  */
790 function MouseInput() {
791     this.evEl = MOUSE_ELEMENT_EVENTS;
792     this.evWin = MOUSE_WINDOW_EVENTS;
793
794     this.allow = true; // used by Input.TouchMouse to disable mouse events
795     this.pressed = false; // mousedown state
796
797     Input.apply(this, arguments);
798 }
799
800 inherit(MouseInput, Input, {
801     /**
802      * handle mouse events
803      * @param {Object} ev
804      */
805     handler: function MEhandler(ev) {
806         var eventType = MOUSE_INPUT_MAP[ev.type];
807
808         // on start we want to have the left mouse button down
809         if (eventType & INPUT_START && ev.button === 0) {
810             this.pressed = true;
811         }
812
813         if (eventType & INPUT_MOVE && ev.which !== 1) {
814             eventType = INPUT_END;
815         }
816
817         // mouse must be down, and mouse events are allowed (see the TouchMouse input)
818         if (!this.pressed || !this.allow) {
819             return;
820         }
821
822         if (eventType & INPUT_END) {
823             this.pressed = false;
824         }
825
826         this.callback(this.manager, eventType, {
827             pointers: [ev],
828             changedPointers: [ev],
829             pointerType: INPUT_TYPE_MOUSE,
830             srcEvent: ev
831         });
832     }
833 });
834
835 var POINTER_INPUT_MAP = {
836     pointerdown: INPUT_START,
837     pointermove: INPUT_MOVE,
838     pointerup: INPUT_END,
839     pointercancel: INPUT_CANCEL,
840     pointerout: INPUT_CANCEL
841 };
842
843 // in IE10 the pointer types is defined as an enum
844 var IE10_POINTER_TYPE_ENUM = {
845     2: INPUT_TYPE_TOUCH,
846     3: INPUT_TYPE_PEN,
847     4: INPUT_TYPE_MOUSE,
848     5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816
849 };
850
851 var POINTER_ELEMENT_EVENTS = 'pointerdown';
852 var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel';
853
854 // IE10 has prefixed support, and case-sensitive
855 if (window.MSPointerEvent && !window.PointerEvent) {
856     POINTER_ELEMENT_EVENTS = 'MSPointerDown';
857     POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel';
858 }
859
860 /**
861  * Pointer events input
862  * @constructor
863  * @extends Input
864  */
865 function PointerEventInput() {
866     this.evEl = POINTER_ELEMENT_EVENTS;
867     this.evWin = POINTER_WINDOW_EVENTS;
868
869     Input.apply(this, arguments);
870
871     this.store = (this.manager.session.pointerEvents = []);
872 }
873
874 inherit(PointerEventInput, Input, {
875     /**
876      * handle mouse events
877      * @param {Object} ev
878      */
879     handler: function PEhandler(ev) {
880         var store = this.store;
881         var removePointer = false;
882
883         var eventTypeNormalized = ev.type.toLowerCase().replace('ms', '');
884         var eventType = POINTER_INPUT_MAP[eventTypeNormalized];
885         var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType;
886
887         var isTouch = (pointerType == INPUT_TYPE_TOUCH);
888
889         // get index of the event in the store
890         var storeIndex = inArray(store, ev.pointerId, 'pointerId');
891
892         // start and mouse must be down
893         if (eventType & INPUT_START && (ev.button === 0 || isTouch)) {
894             if (storeIndex < 0) {
895                 store.push(ev);
896                 storeIndex = store.length - 1;
897             }
898         } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
899             removePointer = true;
900         }
901
902         // it not found, so the pointer hasn't been down (so it's probably a hover)
903         if (storeIndex < 0) {
904             return;
905         }
906
907         // update the event in the store
908         store[storeIndex] = ev;
909
910         this.callback(this.manager, eventType, {
911             pointers: store,
912             changedPointers: [ev],
913             pointerType: pointerType,
914             srcEvent: ev
915         });
916
917         if (removePointer) {
918             // remove from the store
919             store.splice(storeIndex, 1);
920         }
921     }
922 });
923
924 var SINGLE_TOUCH_INPUT_MAP = {
925     touchstart: INPUT_START,
926     touchmove: INPUT_MOVE,
927     touchend: INPUT_END,
928     touchcancel: INPUT_CANCEL
929 };
930
931 var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart';
932 var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel';
933
934 /**
935  * Touch events input
936  * @constructor
937  * @extends Input
938  */
939 function SingleTouchInput() {
940     this.evTarget = SINGLE_TOUCH_TARGET_EVENTS;
941     this.evWin = SINGLE_TOUCH_WINDOW_EVENTS;
942     this.started = false;
943
944     Input.apply(this, arguments);
945 }
946
947 inherit(SingleTouchInput, Input, {
948     handler: function TEhandler(ev) {
949         var type = SINGLE_TOUCH_INPUT_MAP[ev.type];
950
951         // should we handle the touch events?
952         if (type === INPUT_START) {
953             this.started = true;
954         }
955
956         if (!this.started) {
957             return;
958         }
959
960         var touches = normalizeSingleTouches.call(this, ev, type);
961
962         // when done, reset the started state
963         if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) {
964             this.started = false;
965         }
966
967         this.callback(this.manager, type, {
968             pointers: touches[0],
969             changedPointers: touches[1],
970             pointerType: INPUT_TYPE_TOUCH,
971             srcEvent: ev
972         });
973     }
974 });
975
976 /**
977  * @this {TouchInput}
978  * @param {Object} ev
979  * @param {Number} type flag
980  * @returns {undefined|Array} [all, changed]
981  */
982 function normalizeSingleTouches(ev, type) {
983     var all = toArray(ev.touches);
984     var changed = toArray(ev.changedTouches);
985
986     if (type & (INPUT_END | INPUT_CANCEL)) {
987         all = uniqueArray(all.concat(changed), 'identifier', true);
988     }
989
990     return [all, changed];
991 }
992
993 var TOUCH_INPUT_MAP = {
994     touchstart: INPUT_START,
995     touchmove: INPUT_MOVE,
996     touchend: INPUT_END,
997     touchcancel: INPUT_CANCEL
998 };
999
1000 var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel';
1001
1002 /**
1003  * Multi-user touch events input
1004  * @constructor
1005  * @extends Input
1006  */
1007 function TouchInput() {
1008     this.evTarget = TOUCH_TARGET_EVENTS;
1009     this.targetIds = {};
1010
1011     Input.apply(this, arguments);
1012 }
1013
1014 inherit(TouchInput, Input, {
1015     handler: function MTEhandler(ev) {
1016         var type = TOUCH_INPUT_MAP[ev.type];
1017         var touches = getTouches.call(this, ev, type);
1018         if (!touches) {
1019             return;
1020         }
1021
1022         this.callback(this.manager, type, {
1023             pointers: touches[0],
1024             changedPointers: touches[1],
1025             pointerType: INPUT_TYPE_TOUCH,
1026             srcEvent: ev
1027         });
1028     }
1029 });
1030
1031 /**
1032  * @this {TouchInput}
1033  * @param {Object} ev
1034  * @param {Number} type flag
1035  * @returns {undefined|Array} [all, changed]
1036  */
1037 function getTouches(ev, type) {
1038     var allTouches = toArray(ev.touches);
1039     var targetIds = this.targetIds;
1040
1041     // when there is only one touch, the process can be simplified
1042     if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) {
1043         targetIds[allTouches[0].identifier] = true;
1044         return [allTouches, allTouches];
1045     }
1046
1047     var i,
1048         targetTouches,
1049         changedTouches = toArray(ev.changedTouches),
1050         changedTargetTouches = [],
1051         target = this.target;
1052
1053     // get target touches from touches
1054     targetTouches = allTouches.filter(function(touch) {
1055         return hasParent(touch.target, target);
1056     });
1057
1058     // collect touches
1059     if (type === INPUT_START) {
1060         i = 0;
1061         while (i < targetTouches.length) {
1062             targetIds[targetTouches[i].identifier] = true;
1063             i++;
1064         }
1065     }
1066
1067     // filter changed touches to only contain touches that exist in the collected target ids
1068     i = 0;
1069     while (i < changedTouches.length) {
1070         if (targetIds[changedTouches[i].identifier]) {
1071             changedTargetTouches.push(changedTouches[i]);
1072         }
1073
1074         // cleanup removed touches
1075         if (type & (INPUT_END | INPUT_CANCEL)) {
1076             delete targetIds[changedTouches[i].identifier];
1077         }
1078         i++;
1079     }
1080
1081     if (!changedTargetTouches.length) {
1082         return;
1083     }
1084
1085     return [
1086         // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel'
1087         uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true),
1088         changedTargetTouches
1089     ];
1090 }
1091
1092 /**
1093  * Combined touch and mouse input
1094  *
1095  * Touch has a higher priority then mouse, and while touching no mouse events are allowed.
1096  * This because touch devices also emit mouse events while doing a touch.
1097  *
1098  * @constructor
1099  * @extends Input
1100  */
1101 function TouchMouseInput() {
1102     Input.apply(this, arguments);
1103
1104     var handler = bindFn(this.handler, this);
1105     this.touch = new TouchInput(this.manager, handler);
1106     this.mouse = new MouseInput(this.manager, handler);
1107 }
1108
1109 inherit(TouchMouseInput, Input, {
1110     /**
1111      * handle mouse and touch events
1112      * @param {Hammer} manager
1113      * @param {String} inputEvent
1114      * @param {Object} inputData
1115      */
1116     handler: function TMEhandler(manager, inputEvent, inputData) {
1117         var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH),
1118             isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE);
1119
1120         // when we're in a touch event, so  block all upcoming mouse events
1121         // most mobile browser also emit mouseevents, right after touchstart
1122         if (isTouch) {
1123             this.mouse.allow = false;
1124         } else if (isMouse && !this.mouse.allow) {
1125             return;
1126         }
1127
1128         // reset the allowMouse when we're done
1129         if (inputEvent & (INPUT_END | INPUT_CANCEL)) {
1130             this.mouse.allow = true;
1131         }
1132
1133         this.callback(manager, inputEvent, inputData);
1134     },
1135
1136     /**
1137      * remove the event listeners
1138      */
1139     destroy: function destroy() {
1140         this.touch.destroy();
1141         this.mouse.destroy();
1142     }
1143 });
1144
1145 var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction');
1146 var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined;
1147
1148 // magical touchAction value
1149 var TOUCH_ACTION_COMPUTE = 'compute';
1150 var TOUCH_ACTION_AUTO = 'auto';
1151 var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented
1152 var TOUCH_ACTION_NONE = 'none';
1153 var TOUCH_ACTION_PAN_X = 'pan-x';
1154 var TOUCH_ACTION_PAN_Y = 'pan-y';
1155
1156 /**
1157  * Touch Action
1158  * sets the touchAction property or uses the js alternative
1159  * @param {Manager} manager
1160  * @param {String} value
1161  * @constructor
1162  */
1163 function TouchAction(manager, value) {
1164     this.manager = manager;
1165     this.set(value);
1166 }
1167
1168 TouchAction.prototype = {
1169     /**
1170      * set the touchAction value on the element or enable the polyfill
1171      * @param {String} value
1172      */
1173     set: function(value) {
1174         // find out the touch-action by the event handlers
1175         if (value == TOUCH_ACTION_COMPUTE) {
1176             value = this.compute();
1177         }
1178
1179         if (NATIVE_TOUCH_ACTION && this.manager.element.style) {
1180             this.manager.element.style[PREFIXED_TOUCH_ACTION] = value;
1181         }
1182         this.actions = value.toLowerCase().trim();
1183     },
1184
1185     /**
1186      * just re-set the touchAction value
1187      */
1188     update: function() {
1189         this.set(this.manager.options.touchAction);
1190     },
1191
1192     /**
1193      * compute the value for the touchAction property based on the recognizer's settings
1194      * @returns {String} value
1195      */
1196     compute: function() {
1197         var actions = [];
1198         each(this.manager.recognizers, function(recognizer) {
1199             if (boolOrFn(recognizer.options.enable, [recognizer])) {
1200                 actions = actions.concat(recognizer.getTouchAction());
1201             }
1202         });
1203         return cleanTouchActions(actions.join(' '));
1204     },
1205
1206     /**
1207      * this method is called on each input cycle and provides the preventing of the browser behavior
1208      * @param {Object} input
1209      */
1210     preventDefaults: function(input) {
1211         // not needed with native support for the touchAction property
1212         if (NATIVE_TOUCH_ACTION) {
1213             return;
1214         }
1215
1216         var srcEvent = input.srcEvent;
1217         var direction = input.offsetDirection;
1218
1219         // if the touch action did prevented once this session
1220         if (this.manager.session.prevented) {
1221             srcEvent.preventDefault();
1222             return;
1223         }
1224
1225         var actions = this.actions;
1226         var hasNone = inStr(actions, TOUCH_ACTION_NONE);
1227         var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y);
1228         var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X);
1229
1230         if (hasNone) {
1231             //do not prevent defaults if this is a tap gesture
1232
1233             var isTapPointer = input.pointers.length === 1;
1234             var isTapMovement = input.distance < 2;
1235             var isTapTouchTime = input.deltaTime < 250;
1236
1237             if (isTapPointer && isTapMovement && isTapTouchTime) {
1238                 return;
1239             }
1240         }
1241
1242         if (hasPanX && hasPanY) {
1243             // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent
1244             return;
1245         }
1246
1247         if (hasNone ||
1248             (hasPanY && direction & DIRECTION_HORIZONTAL) ||
1249             (hasPanX && direction & DIRECTION_VERTICAL)) {
1250             return this.preventSrc(srcEvent);
1251         }
1252     },
1253
1254     /**
1255      * call preventDefault to prevent the browser's default behavior (scrolling in most cases)
1256      * @param {Object} srcEvent
1257      */
1258     preventSrc: function(srcEvent) {
1259         this.manager.session.prevented = true;
1260         srcEvent.preventDefault();
1261     }
1262 };
1263
1264 /**
1265  * when the touchActions are collected they are not a valid value, so we need to clean things up. *
1266  * @param {String} actions
1267  * @returns {*}
1268  */
1269 function cleanTouchActions(actions) {
1270     // none
1271     if (inStr(actions, TOUCH_ACTION_NONE)) {
1272         return TOUCH_ACTION_NONE;
1273     }
1274
1275     var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X);
1276     var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y);
1277
1278     // if both pan-x and pan-y are set (different recognizers
1279     // for different directions, e.g. horizontal pan but vertical swipe?)
1280     // we need none (as otherwise with pan-x pan-y combined none of these
1281     // recognizers will work, since the browser would handle all panning
1282     if (hasPanX && hasPanY) {
1283         return TOUCH_ACTION_NONE;
1284     }
1285
1286     // pan-x OR pan-y
1287     if (hasPanX || hasPanY) {
1288         return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y;
1289     }
1290
1291     // manipulation
1292     if (inStr(actions, TOUCH_ACTION_MANIPULATION)) {
1293         return TOUCH_ACTION_MANIPULATION;
1294     }
1295
1296     return TOUCH_ACTION_AUTO;
1297 }
1298
1299 /**
1300  * Recognizer flow explained; *
1301  * All recognizers have the initial state of POSSIBLE when a input session starts.
1302  * The definition of a input session is from the first input until the last input, with all it's movement in it. *
1303  * Example session for mouse-input: mousedown -> mousemove -> mouseup
1304  *
1305  * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed
1306  * which determines with state it should be.
1307  *
1308  * If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to
1309  * POSSIBLE to give it another change on the next cycle.
1310  *
1311  *               Possible
1312  *                  |
1313  *            +-----+---------------+
1314  *            |                     |
1315  *      +-----+-----+               |
1316  *      |           |               |
1317  *   Failed      Cancelled          |
1318  *                          +-------+------+
1319  *                          |              |
1320  *                      Recognized       Began
1321  *                                         |
1322  *                                      Changed
1323  *                                         |
1324  *                                  Ended/Recognized
1325  */
1326 var STATE_POSSIBLE = 1;
1327 var STATE_BEGAN = 2;
1328 var STATE_CHANGED = 4;
1329 var STATE_ENDED = 8;
1330 var STATE_RECOGNIZED = STATE_ENDED;
1331 var STATE_CANCELLED = 16;
1332 var STATE_FAILED = 32;
1333
1334 /**
1335  * Recognizer
1336  * Every recognizer needs to extend from this class.
1337  * @constructor
1338  * @param {Object} options
1339  */
1340 function Recognizer(options) {
1341     this.options = assign({}, this.defaults, options || {});
1342
1343     this.id = uniqueId();
1344
1345     this.manager = null;
1346
1347     // default is enable true
1348     this.options.enable = ifUndefined(this.options.enable, true);
1349
1350     this.state = STATE_POSSIBLE;
1351
1352     this.simultaneous = {};
1353     this.requireFail = [];
1354 }
1355
1356 Recognizer.prototype = {
1357     /**
1358      * @virtual
1359      * @type {Object}
1360      */
1361     defaults: {},
1362
1363     /**
1364      * set options
1365      * @param {Object} options
1366      * @return {Recognizer}
1367      */
1368     set: function(options) {
1369         assign(this.options, options);
1370
1371         // also update the touchAction, in case something changed about the directions/enabled state
1372         this.manager && this.manager.touchAction.update();
1373         return this;
1374     },
1375
1376     /**
1377      * recognize simultaneous with an other recognizer.
1378      * @param {Recognizer} otherRecognizer
1379      * @returns {Recognizer} this
1380      */
1381     recognizeWith: function(otherRecognizer) {
1382         if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) {
1383             return this;
1384         }
1385
1386         var simultaneous = this.simultaneous;
1387         otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
1388         if (!simultaneous[otherRecognizer.id]) {
1389             simultaneous[otherRecognizer.id] = otherRecognizer;
1390             otherRecognizer.recognizeWith(this);
1391         }
1392         return this;
1393     },
1394
1395     /**
1396      * drop the simultaneous link. it doesnt remove the link on the other recognizer.
1397      * @param {Recognizer} otherRecognizer
1398      * @returns {Recognizer} this
1399      */
1400     dropRecognizeWith: function(otherRecognizer) {
1401         if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) {
1402             return this;
1403         }
1404
1405         otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
1406         delete this.simultaneous[otherRecognizer.id];
1407         return this;
1408     },
1409
1410     /**
1411      * recognizer can only run when an other is failing
1412      * @param {Recognizer} otherRecognizer
1413      * @returns {Recognizer} this
1414      */
1415     requireFailure: function(otherRecognizer) {
1416         if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) {
1417             return this;
1418         }
1419
1420         var requireFail = this.requireFail;
1421         otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
1422         if (inArray(requireFail, otherRecognizer) === -1) {
1423             requireFail.push(otherRecognizer);
1424             otherRecognizer.requireFailure(this);
1425         }
1426         return this;
1427     },
1428
1429     /**
1430      * drop the requireFailure link. it does not remove the link on the other recognizer.
1431      * @param {Recognizer} otherRecognizer
1432      * @returns {Recognizer} this
1433      */
1434     dropRequireFailure: function(otherRecognizer) {
1435         if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) {
1436             return this;
1437         }
1438
1439         otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
1440         var index = inArray(this.requireFail, otherRecognizer);
1441         if (index > -1) {
1442             this.requireFail.splice(index, 1);
1443         }
1444         return this;
1445     },
1446
1447     /**
1448      * has require failures boolean
1449      * @returns {boolean}
1450      */
1451     hasRequireFailures: function() {
1452         return this.requireFail.length > 0;
1453     },
1454
1455     /**
1456      * if the recognizer can recognize simultaneous with an other recognizer
1457      * @param {Recognizer} otherRecognizer
1458      * @returns {Boolean}
1459      */
1460     canRecognizeWith: function(otherRecognizer) {
1461         return !!this.simultaneous[otherRecognizer.id];
1462     },
1463
1464     /**
1465      * You should use `tryEmit` instead of `emit` directly to check
1466      * that all the needed recognizers has failed before emitting.
1467      * @param {Object} input
1468      */
1469     emit: function(input) {
1470         var self = this;
1471         var state = this.state;
1472
1473         function emit(event) {
1474             self.manager.emit(event, input);
1475         }
1476
1477         // 'panstart' and 'panmove'
1478         if (state < STATE_ENDED) {
1479             emit(self.options.event + stateStr(state));
1480         }
1481
1482         emit(self.options.event); // simple 'eventName' events
1483
1484         if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...)
1485             emit(input.additionalEvent);
1486         }
1487
1488         // panend and pancancel
1489         if (state >= STATE_ENDED) {
1490             emit(self.options.event + stateStr(state));
1491         }
1492     },
1493
1494     /**
1495      * Check that all the require failure recognizers has failed,
1496      * if true, it emits a gesture event,
1497      * otherwise, setup the state to FAILED.
1498      * @param {Object} input
1499      */
1500     tryEmit: function(input) {
1501         if (this.canEmit()) {
1502             return this.emit(input);
1503         }
1504         // it's failing anyway
1505         this.state = STATE_FAILED;
1506     },
1507
1508     /**
1509      * can we emit?
1510      * @returns {boolean}
1511      */
1512     canEmit: function() {
1513         var i = 0;
1514         while (i < this.requireFail.length) {
1515             if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) {
1516                 return false;
1517             }
1518             i++;
1519         }
1520         return true;
1521     },
1522
1523     /**
1524      * update the recognizer
1525      * @param {Object} inputData
1526      */
1527     recognize: function(inputData) {
1528         // make a new copy of the inputData
1529         // so we can change the inputData without messing up the other recognizers
1530         var inputDataClone = assign({}, inputData);
1531
1532         // is is enabled and allow recognizing?
1533         if (!boolOrFn(this.options.enable, [this, inputDataClone])) {
1534             this.reset();
1535             this.state = STATE_FAILED;
1536             return;
1537         }
1538
1539         // reset when we've reached the end
1540         if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) {
1541             this.state = STATE_POSSIBLE;
1542         }
1543
1544         this.state = this.process(inputDataClone);
1545
1546         // the recognizer has recognized a gesture
1547         // so trigger an event
1548         if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) {
1549             this.tryEmit(inputDataClone);
1550         }
1551     },
1552
1553     /**
1554      * return the state of the recognizer
1555      * the actual recognizing happens in this method
1556      * @virtual
1557      * @param {Object} inputData
1558      * @returns {Const} STATE
1559      */
1560     process: function(inputData) { }, // jshint ignore:line
1561
1562     /**
1563      * return the preferred touch-action
1564      * @virtual
1565      * @returns {Array}
1566      */
1567     getTouchAction: function() { },
1568
1569     /**
1570      * called when the gesture isn't allowed to recognize
1571      * like when another is being recognized or it is disabled
1572      * @virtual
1573      */
1574     reset: function() { }
1575 };
1576
1577 /**
1578  * get a usable string, used as event postfix
1579  * @param {Const} state
1580  * @returns {String} state
1581  */
1582 function stateStr(state) {
1583     if (state & STATE_CANCELLED) {
1584         return 'cancel';
1585     } else if (state & STATE_ENDED) {
1586         return 'end';
1587     } else if (state & STATE_CHANGED) {
1588         return 'move';
1589     } else if (state & STATE_BEGAN) {
1590         return 'start';
1591     }
1592     return '';
1593 }
1594
1595 /**
1596  * direction cons to string
1597  * @param {Const} direction
1598  * @returns {String}
1599  */
1600 function directionStr(direction) {
1601     if (direction == DIRECTION_DOWN) {
1602         return 'down';
1603     } else if (direction == DIRECTION_UP) {
1604         return 'up';
1605     } else if (direction == DIRECTION_LEFT) {
1606         return 'left';
1607     } else if (direction == DIRECTION_RIGHT) {
1608         return 'right';
1609     }
1610     return '';
1611 }
1612
1613 /**
1614  * get a recognizer by name if it is bound to a manager
1615  * @param {Recognizer|String} otherRecognizer
1616  * @param {Recognizer} recognizer
1617  * @returns {Recognizer}
1618  */
1619 function getRecognizerByNameIfManager(otherRecognizer, recognizer) {
1620     var manager = recognizer.manager;
1621     if (manager) {
1622         return manager.get(otherRecognizer);
1623     }
1624     return otherRecognizer;
1625 }
1626
1627 /**
1628  * This recognizer is just used as a base for the simple attribute recognizers.
1629  * @constructor
1630  * @extends Recognizer
1631  */
1632 function AttrRecognizer() {
1633     Recognizer.apply(this, arguments);
1634 }
1635
1636 inherit(AttrRecognizer, Recognizer, {
1637     /**
1638      * @namespace
1639      * @memberof AttrRecognizer
1640      */
1641     defaults: {
1642         /**
1643          * @type {Number}
1644          * @default 1
1645          */
1646         pointers: 1
1647     },
1648
1649     /**
1650      * Used to check if it the recognizer receives valid input, like input.distance > 10.
1651      * @memberof AttrRecognizer
1652      * @param {Object} input
1653      * @returns {Boolean} recognized
1654      */
1655     attrTest: function(input) {
1656         var optionPointers = this.options.pointers;
1657         return optionPointers === 0 || input.pointers.length === optionPointers;
1658     },
1659
1660     /**
1661      * Process the input and return the state for the recognizer
1662      * @memberof AttrRecognizer
1663      * @param {Object} input
1664      * @returns {*} State
1665      */
1666     process: function(input) {
1667         var state = this.state;
1668         var eventType = input.eventType;
1669
1670         var isRecognized = state & (STATE_BEGAN | STATE_CHANGED);
1671         var isValid = this.attrTest(input);
1672
1673         // on cancel input and we've recognized before, return STATE_CANCELLED
1674         if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) {
1675             return state | STATE_CANCELLED;
1676         } else if (isRecognized || isValid) {
1677             if (eventType & INPUT_END) {
1678                 return state | STATE_ENDED;
1679             } else if (!(state & STATE_BEGAN)) {
1680                 return STATE_BEGAN;
1681             }
1682             return state | STATE_CHANGED;
1683         }
1684         return STATE_FAILED;
1685     }
1686 });
1687
1688 /**
1689  * Pan
1690  * Recognized when the pointer is down and moved in the allowed direction.
1691  * @constructor
1692  * @extends AttrRecognizer
1693  */
1694 function PanRecognizer() {
1695     AttrRecognizer.apply(this, arguments);
1696
1697     this.pX = null;
1698     this.pY = null;
1699 }
1700
1701 inherit(PanRecognizer, AttrRecognizer, {
1702     /**
1703      * @namespace
1704      * @memberof PanRecognizer
1705      */
1706     defaults: {
1707         event: 'pan',
1708         threshold: 10,
1709         pointers: 1,
1710         direction: DIRECTION_ALL
1711     },
1712
1713     getTouchAction: function() {
1714         var direction = this.options.direction;
1715         var actions = [];
1716         if (direction & DIRECTION_HORIZONTAL) {
1717             actions.push(TOUCH_ACTION_PAN_Y);
1718         }
1719         if (direction & DIRECTION_VERTICAL) {
1720             actions.push(TOUCH_ACTION_PAN_X);
1721         }
1722         return actions;
1723     },
1724
1725     directionTest: function(input) {
1726         var options = this.options;
1727         var hasMoved = true;
1728         var distance = input.distance;
1729         var direction = input.direction;
1730         var x = input.deltaX;
1731         var y = input.deltaY;
1732
1733         // lock to axis?
1734         if (!(direction & options.direction)) {
1735             if (options.direction & DIRECTION_HORIZONTAL) {
1736                 direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
1737                 hasMoved = x != this.pX;
1738                 distance = Math.abs(input.deltaX);
1739             } else {
1740                 direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN;
1741                 hasMoved = y != this.pY;
1742                 distance = Math.abs(input.deltaY);
1743             }
1744         }
1745         input.direction = direction;
1746         return hasMoved && distance > options.threshold && direction & options.direction;
1747     },
1748
1749     attrTest: function(input) {
1750         return AttrRecognizer.prototype.attrTest.call(this, input) &&
1751             (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input)));
1752     },
1753
1754     emit: function(input) {
1755
1756         this.pX = input.deltaX;
1757         this.pY = input.deltaY;
1758
1759         var direction = directionStr(input.direction);
1760
1761         if (direction) {
1762             input.additionalEvent = this.options.event + direction;
1763         }
1764         this._super.emit.call(this, input);
1765     }
1766 });
1767
1768 /**
1769  * Pinch
1770  * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out).
1771  * @constructor
1772  * @extends AttrRecognizer
1773  */
1774 function PinchRecognizer() {
1775     AttrRecognizer.apply(this, arguments);
1776 }
1777
1778 inherit(PinchRecognizer, AttrRecognizer, {
1779     /**
1780      * @namespace
1781      * @memberof PinchRecognizer
1782      */
1783     defaults: {
1784         event: 'pinch',
1785         threshold: 0,
1786         pointers: 2
1787     },
1788
1789     getTouchAction: function() {
1790         return [TOUCH_ACTION_NONE];
1791     },
1792
1793     attrTest: function(input) {
1794         return this._super.attrTest.call(this, input) &&
1795             (Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN);
1796     },
1797
1798     emit: function(input) {
1799         if (input.scale !== 1) {
1800             var inOut = input.scale < 1 ? 'in' : 'out';
1801             input.additionalEvent = this.options.event + inOut;
1802         }
1803         this._super.emit.call(this, input);
1804     }
1805 });
1806
1807 /**
1808  * Press
1809  * Recognized when the pointer is down for x ms without any movement.
1810  * @constructor
1811  * @extends Recognizer
1812  */
1813 function PressRecognizer() {
1814     Recognizer.apply(this, arguments);
1815
1816     this._timer = null;
1817     this._input = null;
1818 }
1819
1820 inherit(PressRecognizer, Recognizer, {
1821     /**
1822      * @namespace
1823      * @memberof PressRecognizer
1824      */
1825     defaults: {
1826         event: 'press',
1827         pointers: 1,
1828         time: 251, // minimal time of the pointer to be pressed
1829         threshold: 9 // a minimal movement is ok, but keep it low
1830     },
1831
1832     getTouchAction: function() {
1833         return [TOUCH_ACTION_AUTO];
1834     },
1835
1836     process: function(input) {
1837         var options = this.options;
1838         var validPointers = input.pointers.length === options.pointers;
1839         var validMovement = input.distance < options.threshold;
1840         var validTime = input.deltaTime > options.time;
1841
1842         this._input = input;
1843
1844         // we only allow little movement
1845         // and we've reached an end event, so a tap is possible
1846         if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) {
1847             this.reset();
1848         } else if (input.eventType & INPUT_START) {
1849             this.reset();
1850             this._timer = setTimeoutContext(function() {
1851                 this.state = STATE_RECOGNIZED;
1852                 this.tryEmit();
1853             }, options.time, this);
1854         } else if (input.eventType & INPUT_END) {
1855             return STATE_RECOGNIZED;
1856         }
1857         return STATE_FAILED;
1858     },
1859
1860     reset: function() {
1861         clearTimeout(this._timer);
1862     },
1863
1864     emit: function(input) {
1865         if (this.state !== STATE_RECOGNIZED) {
1866             return;
1867         }
1868
1869         if (input && (input.eventType & INPUT_END)) {
1870             this.manager.emit(this.options.event + 'up', input);
1871         } else {
1872             this._input.timeStamp = now();
1873             this.manager.emit(this.options.event, this._input);
1874         }
1875     }
1876 });
1877
1878 /**
1879  * Rotate
1880  * Recognized when two or more pointer are moving in a circular motion.
1881  * @constructor
1882  * @extends AttrRecognizer
1883  */
1884 function RotateRecognizer() {
1885     AttrRecognizer.apply(this, arguments);
1886 }
1887
1888 inherit(RotateRecognizer, AttrRecognizer, {
1889     /**
1890      * @namespace
1891      * @memberof RotateRecognizer
1892      */
1893     defaults: {
1894         event: 'rotate',
1895         threshold: 0,
1896         pointers: 2
1897     },
1898
1899     getTouchAction: function() {
1900         return [TOUCH_ACTION_NONE];
1901     },
1902
1903     attrTest: function(input) {
1904         return this._super.attrTest.call(this, input) &&
1905             (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN);
1906     }
1907 });
1908
1909 /**
1910  * Swipe
1911  * Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction.
1912  * @constructor
1913  * @extends AttrRecognizer
1914  */
1915 function SwipeRecognizer() {
1916     AttrRecognizer.apply(this, arguments);
1917 }
1918
1919 inherit(SwipeRecognizer, AttrRecognizer, {
1920     /**
1921      * @namespace
1922      * @memberof SwipeRecognizer
1923      */
1924     defaults: {
1925         event: 'swipe',
1926         threshold: 10,
1927         velocity: 0.3,
1928         direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL,
1929         pointers: 1
1930     },
1931
1932     getTouchAction: function() {
1933         return PanRecognizer.prototype.getTouchAction.call(this);
1934     },
1935
1936     attrTest: function(input) {
1937         var direction = this.options.direction;
1938         var velocity;
1939
1940         if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) {
1941             velocity = input.overallVelocity;
1942         } else if (direction & DIRECTION_HORIZONTAL) {
1943             velocity = input.overallVelocityX;
1944         } else if (direction & DIRECTION_VERTICAL) {
1945             velocity = input.overallVelocityY;
1946         }
1947
1948         return this._super.attrTest.call(this, input) &&
1949             direction & input.offsetDirection &&
1950             input.distance > this.options.threshold &&
1951             input.maxPointers == this.options.pointers &&
1952             abs(velocity) > this.options.velocity && input.eventType & INPUT_END;
1953     },
1954
1955     emit: function(input) {
1956         var direction = directionStr(input.offsetDirection);
1957         if (direction) {
1958             this.manager.emit(this.options.event + direction, input);
1959         }
1960
1961         this.manager.emit(this.options.event, input);
1962     }
1963 });
1964
1965 /**
1966  * A tap is ecognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur
1967  * between the given interval and position. The delay option can be used to recognize multi-taps without firing
1968  * a single tap.
1969  *
1970  * The eventData from the emitted event contains the property `tapCount`, which contains the amount of
1971  * multi-taps being recognized.
1972  * @constructor
1973  * @extends Recognizer
1974  */
1975 function TapRecognizer() {
1976     Recognizer.apply(this, arguments);
1977
1978     // previous time and center,
1979     // used for tap counting
1980     this.pTime = false;
1981     this.pCenter = false;
1982
1983     this._timer = null;
1984     this._input = null;
1985     this.count = 0;
1986 }
1987
1988 inherit(TapRecognizer, Recognizer, {
1989     /**
1990      * @namespace
1991      * @memberof PinchRecognizer
1992      */
1993     defaults: {
1994         event: 'tap',
1995         pointers: 1,
1996         taps: 1,
1997         interval: 300, // max time between the multi-tap taps
1998         time: 250, // max time of the pointer to be down (like finger on the screen)
1999         threshold: 9, // a minimal movement is ok, but keep it low
2000         posThreshold: 10 // a multi-tap can be a bit off the initial position
2001     },
2002
2003     getTouchAction: function() {
2004         return [TOUCH_ACTION_MANIPULATION];
2005     },
2006
2007     process: function(input) {
2008         var options = this.options;
2009
2010         var validPointers = input.pointers.length === options.pointers;
2011         var validMovement = input.distance < options.threshold;
2012         var validTouchTime = input.deltaTime < options.time;
2013
2014         this.reset();
2015
2016         if ((input.eventType & INPUT_START) && (this.count === 0)) {
2017             return this.failTimeout();
2018         }
2019
2020         // we only allow little movement
2021         // and we've reached an end event, so a tap is possible
2022         if (validMovement && validTouchTime && validPointers) {
2023             if (input.eventType != INPUT_END) {
2024                 return this.failTimeout();
2025             }
2026
2027             var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true;
2028             var validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold;
2029
2030             this.pTime = input.timeStamp;
2031             this.pCenter = input.center;
2032
2033             if (!validMultiTap || !validInterval) {
2034                 this.count = 1;
2035             } else {
2036                 this.count += 1;
2037             }
2038
2039             this._input = input;
2040
2041             // if tap count matches we have recognized it,
2042             // else it has began recognizing...
2043             var tapCount = this.count % options.taps;
2044             if (tapCount === 0) {
2045                 // no failing requirements, immediately trigger the tap event
2046                 // or wait as long as the multitap interval to trigger
2047                 if (!this.hasRequireFailures()) {
2048                     return STATE_RECOGNIZED;
2049                 } else {
2050                     this._timer = setTimeoutContext(function() {
2051                         this.state = STATE_RECOGNIZED;
2052                         this.tryEmit();
2053                     }, options.interval, this);
2054                     return STATE_BEGAN;
2055                 }
2056             }
2057         }
2058         return STATE_FAILED;
2059     },
2060
2061     failTimeout: function() {
2062         this._timer = setTimeoutContext(function() {
2063             this.state = STATE_FAILED;
2064         }, this.options.interval, this);
2065         return STATE_FAILED;
2066     },
2067
2068     reset: function() {
2069         clearTimeout(this._timer);
2070     },
2071
2072     emit: function() {
2073         if (this.state == STATE_RECOGNIZED) {
2074             this._input.tapCount = this.count;
2075             this.manager.emit(this.options.event, this._input);
2076         }
2077     }
2078 });
2079
2080 /**
2081  * Simple way to create a manager with a default set of recognizers.
2082  * @param {HTMLElement} element
2083  * @param {Object} [options]
2084  * @constructor
2085  */
2086 function Hammer(element, options) {
2087     options = options || {};
2088     options.recognizers = ifUndefined(options.recognizers, Hammer.defaults.preset);
2089     return new Manager(element, options);
2090 }
2091
2092 /**
2093  * @const {string}
2094  */
2095 Hammer.VERSION = '2.0.6';
2096
2097 /**
2098  * default settings
2099  * @namespace
2100  */
2101 Hammer.defaults = {
2102     /**
2103      * set if DOM events are being triggered.
2104      * But this is slower and unused by simple implementations, so disabled by default.
2105      * @type {Boolean}
2106      * @default false
2107      */
2108     domEvents: false,
2109
2110     /**
2111      * The value for the touchAction property/fallback.
2112      * When set to `compute` it will magically set the correct value based on the added recognizers.
2113      * @type {String}
2114      * @default compute
2115      */
2116     touchAction: TOUCH_ACTION_COMPUTE,
2117
2118     /**
2119      * @type {Boolean}
2120      * @default true
2121      */
2122     enable: true,
2123
2124     /**
2125      * EXPERIMENTAL FEATURE -- can be removed/changed
2126      * Change the parent input target element.
2127      * If Null, then it is being set the to main element.
2128      * @type {Null|EventTarget}
2129      * @default null
2130      */
2131     inputTarget: null,
2132
2133     /**
2134      * force an input class
2135      * @type {Null|Function}
2136      * @default null
2137      */
2138     inputClass: null,
2139
2140     /**
2141      * Default recognizer setup when calling `Hammer()`
2142      * When creating a new Manager these will be skipped.
2143      * @type {Array}
2144      */
2145     preset: [
2146         // RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...]
2147         [RotateRecognizer, {enable: false}],
2148         [PinchRecognizer, {enable: false}, ['rotate']],
2149         [SwipeRecognizer, {direction: DIRECTION_HORIZONTAL}],
2150         [PanRecognizer, {direction: DIRECTION_HORIZONTAL}, ['swipe']],
2151         [TapRecognizer],
2152         [TapRecognizer, {event: 'doubletap', taps: 2}, ['tap']],
2153         [PressRecognizer]
2154     ],
2155
2156     /**
2157      * Some CSS properties can be used to improve the working of Hammer.
2158      * Add them to this method and they will be set when creating a new Manager.
2159      * @namespace
2160      */
2161     cssProps: {
2162         /**
2163          * Disables text selection to improve the dragging gesture. Mainly for desktop browsers.
2164          * @type {String}
2165          * @default 'none'
2166          */
2167         userSelect: 'none',
2168
2169         /**
2170          * Disable the Windows Phone grippers when pressing an element.
2171          * @type {String}
2172          * @default 'none'
2173          */
2174         touchSelect: 'none',
2175
2176         /**
2177          * Disables the default callout shown when you touch and hold a touch target.
2178          * On iOS, when you touch and hold a touch target such as a link, Safari displays
2179          * a callout containing information about the link. This property allows you to disable that callout.
2180          * @type {String}
2181          * @default 'none'
2182          */
2183         touchCallout: 'none',
2184
2185         /**
2186          * Specifies whether zooming is enabled. Used by IE10>
2187          * @type {String}
2188          * @default 'none'
2189          */
2190         contentZooming: 'none',
2191
2192         /**
2193          * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers.
2194          * @type {String}
2195          * @default 'none'
2196          */
2197         userDrag: 'none',
2198
2199         /**
2200          * Overrides the highlight color shown when the user taps a link or a JavaScript
2201          * clickable element in iOS. This property obeys the alpha value, if specified.
2202          * @type {String}
2203          * @default 'rgba(0,0,0,0)'
2204          */
2205         tapHighlightColor: 'rgba(0,0,0,0)'
2206     }
2207 };
2208
2209 var STOP = 1;
2210 var FORCED_STOP = 2;
2211
2212 /**
2213  * Manager
2214  * @param {HTMLElement} element
2215  * @param {Object} [options]
2216  * @constructor
2217  */
2218 function Manager(element, options) {
2219     this.options = assign({}, Hammer.defaults, options || {});
2220
2221     this.options.inputTarget = this.options.inputTarget || element;
2222
2223     this.handlers = {};
2224     this.session = {};
2225     this.recognizers = [];
2226
2227     this.element = element;
2228     this.input = createInputInstance(this);
2229     this.touchAction = new TouchAction(this, this.options.touchAction);
2230
2231     toggleCssProps(this, true);
2232
2233     each(this.options.recognizers, function(item) {
2234         var recognizer = this.add(new (item[0])(item[1]));
2235         item[2] && recognizer.recognizeWith(item[2]);
2236         item[3] && recognizer.requireFailure(item[3]);
2237     }, this);
2238 }
2239
2240 Manager.prototype = {
2241     /**
2242      * set options
2243      * @param {Object} options
2244      * @returns {Manager}
2245      */
2246     set: function(options) {
2247         assign(this.options, options);
2248
2249         // Options that need a little more setup
2250         if (options.touchAction) {
2251             this.touchAction.update();
2252         }
2253         if (options.inputTarget) {
2254             // Clean up existing event listeners and reinitialize
2255             this.input.destroy();
2256             this.input.target = options.inputTarget;
2257             this.input.init();
2258         }
2259         return this;
2260     },
2261
2262     /**
2263      * stop recognizing for this session.
2264      * This session will be discarded, when a new [input]start event is fired.
2265      * When forced, the recognizer cycle is stopped immediately.
2266      * @param {Boolean} [force]
2267      */
2268     stop: function(force) {
2269         this.session.stopped = force ? FORCED_STOP : STOP;
2270     },
2271
2272     /**
2273      * run the recognizers!
2274      * called by the inputHandler function on every movement of the pointers (touches)
2275      * it walks through all the recognizers and tries to detect the gesture that is being made
2276      * @param {Object} inputData
2277      */
2278     recognize: function(inputData) {
2279         var session = this.session;
2280         if (session.stopped) {
2281             return;
2282         }
2283
2284         // run the touch-action polyfill
2285         this.touchAction.preventDefaults(inputData);
2286
2287         var recognizer;
2288         var recognizers = this.recognizers;
2289
2290         // this holds the recognizer that is being recognized.
2291         // so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED
2292         // if no recognizer is detecting a thing, it is set to `null`
2293         var curRecognizer = session.curRecognizer;
2294
2295         // reset when the last recognizer is recognized
2296         // or when we're in a new session
2297         if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) {
2298             curRecognizer = session.curRecognizer = null;
2299         }
2300
2301         var i = 0;
2302         while (i < recognizers.length) {
2303             recognizer = recognizers[i];
2304
2305             // find out if we are allowed try to recognize the input for this one.
2306             // 1.   allow if the session is NOT forced stopped (see the .stop() method)
2307             // 2.   allow if we still haven't recognized a gesture in this session, or the this recognizer is the one
2308             //      that is being recognized.
2309             // 3.   allow if the recognizer is allowed to run simultaneous with the current recognized recognizer.
2310             //      this can be setup with the `recognizeWith()` method on the recognizer.
2311             if (session.stopped !== FORCED_STOP && ( // 1
2312                     !curRecognizer || recognizer == curRecognizer || // 2
2313                     recognizer.canRecognizeWith(curRecognizer))) { // 3
2314                 recognizer.recognize(inputData);
2315             } else {
2316                 recognizer.reset();
2317             }
2318
2319             // if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the
2320             // current active recognizer. but only if we don't already have an active recognizer
2321             if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) {
2322                 curRecognizer = session.curRecognizer = recognizer;
2323             }
2324             i++;
2325         }
2326     },
2327
2328     /**
2329      * get a recognizer by its event name.
2330      * @param {Recognizer|String} recognizer
2331      * @returns {Recognizer|Null}
2332      */
2333     get: function(recognizer) {
2334         if (recognizer instanceof Recognizer) {
2335             return recognizer;
2336         }
2337
2338         var recognizers = this.recognizers;
2339         for (var i = 0; i < recognizers.length; i++) {
2340             if (recognizers[i].options.event == recognizer) {
2341                 return recognizers[i];
2342             }
2343         }
2344         return null;
2345     },
2346
2347     /**
2348      * add a recognizer to the manager
2349      * existing recognizers with the same event name will be removed
2350      * @param {Recognizer} recognizer
2351      * @returns {Recognizer|Manager}
2352      */
2353     add: function(recognizer) {
2354         if (invokeArrayArg(recognizer, 'add', this)) {
2355             return this;
2356         }
2357
2358         // remove existing
2359         var existing = this.get(recognizer.options.event);
2360         if (existing) {
2361             this.remove(existing);
2362         }
2363
2364         this.recognizers.push(recognizer);
2365         recognizer.manager = this;
2366
2367         this.touchAction.update();
2368         return recognizer;
2369     },
2370
2371     /**
2372      * remove a recognizer by name or instance
2373      * @param {Recognizer|String} recognizer
2374      * @returns {Manager}
2375      */
2376     remove: function(recognizer) {
2377         if (invokeArrayArg(recognizer, 'remove', this)) {
2378             return this;
2379         }
2380
2381         recognizer = this.get(recognizer);
2382
2383         // let's make sure this recognizer exists
2384         if (recognizer) {
2385             var recognizers = this.recognizers;
2386             var index = inArray(recognizers, recognizer);
2387
2388             if (index !== -1) {
2389                 recognizers.splice(index, 1);
2390                 this.touchAction.update();
2391             }
2392         }
2393
2394         return this;
2395     },
2396
2397     /**
2398      * bind event
2399      * @param {String} events
2400      * @param {Function} handler
2401      * @returns {EventEmitter} this
2402      */
2403     on: function(events, handler) {
2404         var handlers = this.handlers;
2405         each(splitStr(events), function(event) {
2406             handlers[event] = handlers[event] || [];
2407             handlers[event].push(handler);
2408         });
2409         return this;
2410     },
2411
2412     /**
2413      * unbind event, leave emit blank to remove all handlers
2414      * @param {String} events
2415      * @param {Function} [handler]
2416      * @returns {EventEmitter} this
2417      */
2418     off: function(events, handler) {
2419         var handlers = this.handlers;
2420         each(splitStr(events), function(event) {
2421             if (!handler) {
2422                 delete handlers[event];
2423             } else {
2424                 handlers[event] && handlers[event].splice(inArray(handlers[event], handler), 1);
2425             }
2426         });
2427         return this;
2428     },
2429
2430     /**
2431      * emit event to the listeners
2432      * @param {String} event
2433      * @param {Object} data
2434      */
2435     emit: function(event, data) {
2436         // we also want to trigger dom events
2437         if (this.options.domEvents) {
2438             triggerDomEvent(event, data);
2439         }
2440
2441         // no handlers, so skip it all
2442         var handlers = this.handlers[event] && this.handlers[event].slice();
2443         if (!handlers || !handlers.length) {
2444             return;
2445         }
2446
2447         data.type = event;
2448         data.preventDefault = function() {
2449             data.srcEvent.preventDefault();
2450         };
2451
2452         var i = 0;
2453         while (i < handlers.length) {
2454             handlers[i](data);
2455             i++;
2456         }
2457     },
2458
2459     /**
2460      * destroy the manager and unbinds all events
2461      * it doesn't unbind dom events, that is the user own responsibility
2462      */
2463     destroy: function() {
2464         this.element && toggleCssProps(this, false);
2465
2466         this.handlers = {};
2467         this.session = {};
2468         this.input.destroy();
2469         this.element = null;
2470     }
2471 };
2472
2473 /**
2474  * add/remove the css properties as defined in manager.options.cssProps
2475  * @param {Manager} manager
2476  * @param {Boolean} add
2477  */
2478 function toggleCssProps(manager, add) {
2479     var element = manager.element;
2480     if (!element.style) {
2481         return;
2482     }
2483     each(manager.options.cssProps, function(value, name) {
2484         element.style[prefixed(element.style, name)] = add ? value : '';
2485     });
2486 }
2487
2488 /**
2489  * trigger dom event
2490  * @param {String} event
2491  * @param {Object} data
2492  */
2493 function triggerDomEvent(event, data) {
2494     var gestureEvent = document.createEvent('Event');
2495     gestureEvent.initEvent(event, true, true);
2496     gestureEvent.gesture = data;
2497     data.target.dispatchEvent(gestureEvent);
2498 }
2499
2500 assign(Hammer, {
2501     INPUT_START: INPUT_START,
2502     INPUT_MOVE: INPUT_MOVE,
2503     INPUT_END: INPUT_END,
2504     INPUT_CANCEL: INPUT_CANCEL,
2505
2506     STATE_POSSIBLE: STATE_POSSIBLE,
2507     STATE_BEGAN: STATE_BEGAN,
2508     STATE_CHANGED: STATE_CHANGED,
2509     STATE_ENDED: STATE_ENDED,
2510     STATE_RECOGNIZED: STATE_RECOGNIZED,
2511     STATE_CANCELLED: STATE_CANCELLED,
2512     STATE_FAILED: STATE_FAILED,
2513
2514     DIRECTION_NONE: DIRECTION_NONE,
2515     DIRECTION_LEFT: DIRECTION_LEFT,
2516     DIRECTION_RIGHT: DIRECTION_RIGHT,
2517     DIRECTION_UP: DIRECTION_UP,
2518     DIRECTION_DOWN: DIRECTION_DOWN,
2519     DIRECTION_HORIZONTAL: DIRECTION_HORIZONTAL,
2520     DIRECTION_VERTICAL: DIRECTION_VERTICAL,
2521     DIRECTION_ALL: DIRECTION_ALL,
2522
2523     Manager: Manager,
2524     Input: Input,
2525     TouchAction: TouchAction,
2526
2527     TouchInput: TouchInput,
2528     MouseInput: MouseInput,
2529     PointerEventInput: PointerEventInput,
2530     TouchMouseInput: TouchMouseInput,
2531     SingleTouchInput: SingleTouchInput,
2532
2533     Recognizer: Recognizer,
2534     AttrRecognizer: AttrRecognizer,
2535     Tap: TapRecognizer,
2536     Pan: PanRecognizer,
2537     Swipe: SwipeRecognizer,
2538     Pinch: PinchRecognizer,
2539     Rotate: RotateRecognizer,
2540     Press: PressRecognizer,
2541
2542     on: addEventListeners,
2543     off: removeEventListeners,
2544     each: each,
2545     merge: merge,
2546     extend: extend,
2547     assign: assign,
2548     inherit: inherit,
2549     bindFn: bindFn,
2550     prefixed: prefixed
2551 });
2552
2553 // this prevents errors when Hammer is loaded in the presence of an AMD
2554 //  style loader but by script tag, not by the loader.
2555 var freeGlobal = (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {})); // jshint ignore:line
2556 freeGlobal.Hammer = Hammer;
2557
2558 if (typeof define === 'function' && define.amd) {
2559     define(function() {
2560         return Hammer;
2561     });
2562 } else if (typeof module != 'undefined' && module.exports) {
2563     module.exports = Hammer;
2564 } else {
2565     window[exportName] = Hammer;
2566 }
2567
2568 })(window, document, 'Hammer');