PageSourceSearch

https://parentnotices.com/js/hammerjs/hammer.js

js parentnotices.com collected 2026-09-24 12:53:21 UTC 67,578 bytes, 2,463 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.