PluginProbe ʕ •ᴥ•ʔ
Responsive Lightbox & Gallery / 2.5.4
Responsive Lightbox & Gallery v2.5.4
2.7.8 trunk 1.0.0 1.0.1 1.0.1.1 1.0.2 1.0.3 1.0.4 1.1.0 1.1.1 1.1.2 1.2.0 1.2.1 1.2.2 1.2.3 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.4.0 1.4.0.1 1.4.1 1.4.11 1.4.12 1.4.13 1.4.14 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.6.0 1.6.1 1.6.10 1.6.11 1.6.12 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 2.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.1 2.2.0 2.2.1 2.2.2 2.2.3 2.2.3.1 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.3.5 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.4.8 2.4.9 2.5.0 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 2.6.0 2.6.1 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7
responsive-lightbox / assets / tosrus / hammer.js
responsive-lightbox / assets / tosrus Last commit date
LICENSE 4 years ago README.md 4 years ago hammer.js 6 years ago hammer.min.js 6 years ago jquery.tosrus.css 6 years ago jquery.tosrus.js 5 years ago jquery.tosrus.min.css 6 years ago jquery.tosrus.min.js 5 years ago
hammer.js
2644 lines
1 /*! Hammer.JS - v2.0.8 - 2016-04-23
2 * http://hammerjs.github.io/
3 *
4 * Copyright (c) 2016 Jorik Tangelder;
5 * Licensed under the MIT 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} [merge=false]
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.pressed = false; // mousedown state
795
796 Input.apply(this, arguments);
797 }
798
799 inherit(MouseInput, Input, {
800 /**
801 * handle mouse events
802 * @param {Object} ev
803 */
804 handler: function MEhandler(ev) {
805 var eventType = MOUSE_INPUT_MAP[ev.type];
806
807 // on start we want to have the left mouse button down
808 if (eventType & INPUT_START && ev.button === 0) {
809 this.pressed = true;
810 }
811
812 if (eventType & INPUT_MOVE && ev.which !== 1) {
813 eventType = INPUT_END;
814 }
815
816 // mouse must be down
817 if (!this.pressed) {
818 return;
819 }
820
821 if (eventType & INPUT_END) {
822 this.pressed = false;
823 }
824
825 this.callback(this.manager, eventType, {
826 pointers: [ev],
827 changedPointers: [ev],
828 pointerType: INPUT_TYPE_MOUSE,
829 srcEvent: ev
830 });
831 }
832 });
833
834 var POINTER_INPUT_MAP = {
835 pointerdown: INPUT_START,
836 pointermove: INPUT_MOVE,
837 pointerup: INPUT_END,
838 pointercancel: INPUT_CANCEL,
839 pointerout: INPUT_CANCEL
840 };
841
842 // in IE10 the pointer types is defined as an enum
843 var IE10_POINTER_TYPE_ENUM = {
844 2: INPUT_TYPE_TOUCH,
845 3: INPUT_TYPE_PEN,
846 4: INPUT_TYPE_MOUSE,
847 5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816
848 };
849
850 var POINTER_ELEMENT_EVENTS = 'pointerdown';
851 var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel';
852
853 // IE10 has prefixed support, and case-sensitive
854 if (window.MSPointerEvent && !window.PointerEvent) {
855 POINTER_ELEMENT_EVENTS = 'MSPointerDown';
856 POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel';
857 }
858
859 /**
860 * Pointer events input
861 * @constructor
862 * @extends Input
863 */
864 function PointerEventInput() {
865 this.evEl = POINTER_ELEMENT_EVENTS;
866 this.evWin = POINTER_WINDOW_EVENTS;
867
868 Input.apply(this, arguments);
869
870 this.store = (this.manager.session.pointerEvents = []);
871 }
872
873 inherit(PointerEventInput, Input, {
874 /**
875 * handle mouse events
876 * @param {Object} ev
877 */
878 handler: function PEhandler(ev) {
879 var store = this.store;
880 var removePointer = false;
881
882 var eventTypeNormalized = ev.type.toLowerCase().replace('ms', '');
883 var eventType = POINTER_INPUT_MAP[eventTypeNormalized];
884 var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType;
885
886 var isTouch = (pointerType == INPUT_TYPE_TOUCH);
887
888 // get index of the event in the store
889 var storeIndex = inArray(store, ev.pointerId, 'pointerId');
890
891 // start and mouse must be down
892 if (eventType & INPUT_START && (ev.button === 0 || isTouch)) {
893 if (storeIndex < 0) {
894 store.push(ev);
895 storeIndex = store.length - 1;
896 }
897 } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
898 removePointer = true;
899 }
900
901 // it not found, so the pointer hasn't been down (so it's probably a hover)
902 if (storeIndex < 0) {
903 return;
904 }
905
906 // update the event in the store
907 store[storeIndex] = ev;
908
909 this.callback(this.manager, eventType, {
910 pointers: store,
911 changedPointers: [ev],
912 pointerType: pointerType,
913 srcEvent: ev
914 });
915
916 if (removePointer) {
917 // remove from the store
918 store.splice(storeIndex, 1);
919 }
920 }
921 });
922
923 var SINGLE_TOUCH_INPUT_MAP = {
924 touchstart: INPUT_START,
925 touchmove: INPUT_MOVE,
926 touchend: INPUT_END,
927 touchcancel: INPUT_CANCEL
928 };
929
930 var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart';
931 var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel';
932
933 /**
934 * Touch events input
935 * @constructor
936 * @extends Input
937 */
938 function SingleTouchInput() {
939 this.evTarget = SINGLE_TOUCH_TARGET_EVENTS;
940 this.evWin = SINGLE_TOUCH_WINDOW_EVENTS;
941 this.started = false;
942
943 Input.apply(this, arguments);
944 }
945
946 inherit(SingleTouchInput, Input, {
947 handler: function TEhandler(ev) {
948 var type = SINGLE_TOUCH_INPUT_MAP[ev.type];
949
950 // should we handle the touch events?
951 if (type === INPUT_START) {
952 this.started = true;
953 }
954
955 if (!this.started) {
956 return;
957 }
958
959 var touches = normalizeSingleTouches.call(this, ev, type);
960
961 // when done, reset the started state
962 if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) {
963 this.started = false;
964 }
965
966 this.callback(this.manager, type, {
967 pointers: touches[0],
968 changedPointers: touches[1],
969 pointerType: INPUT_TYPE_TOUCH,
970 srcEvent: ev
971 });
972 }
973 });
974
975 /**
976 * @this {TouchInput}
977 * @param {Object} ev
978 * @param {Number} type flag
979 * @returns {undefined|Array} [all, changed]
980 */
981 function normalizeSingleTouches(ev, type) {
982 var all = toArray(ev.touches);
983 var changed = toArray(ev.changedTouches);
984
985 if (type & (INPUT_END | INPUT_CANCEL)) {
986 all = uniqueArray(all.concat(changed), 'identifier', true);
987 }
988
989 return [all, changed];
990 }
991
992 var TOUCH_INPUT_MAP = {
993 touchstart: INPUT_START,
994 touchmove: INPUT_MOVE,
995 touchend: INPUT_END,
996 touchcancel: INPUT_CANCEL
997 };
998
999 var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel';
1000
1001 /**
1002 * Multi-user touch events input
1003 * @constructor
1004 * @extends Input
1005 */
1006 function TouchInput() {
1007 this.evTarget = TOUCH_TARGET_EVENTS;
1008 this.targetIds = {};
1009
1010 Input.apply(this, arguments);
1011 }
1012
1013 inherit(TouchInput, Input, {
1014 handler: function MTEhandler(ev) {
1015 var type = TOUCH_INPUT_MAP[ev.type];
1016 var touches = getTouches.call(this, ev, type);
1017 if (!touches) {
1018 return;
1019 }
1020
1021 this.callback(this.manager, type, {
1022 pointers: touches[0],
1023 changedPointers: touches[1],
1024 pointerType: INPUT_TYPE_TOUCH,
1025 srcEvent: ev
1026 });
1027 }
1028 });
1029
1030 /**
1031 * @this {TouchInput}
1032 * @param {Object} ev
1033 * @param {Number} type flag
1034 * @returns {undefined|Array} [all, changed]
1035 */
1036 function getTouches(ev, type) {
1037 var allTouches = toArray(ev.touches);
1038 var targetIds = this.targetIds;
1039
1040 // when there is only one touch, the process can be simplified
1041 if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) {
1042 targetIds[allTouches[0].identifier] = true;
1043 return [allTouches, allTouches];
1044 }
1045
1046 var i,
1047 targetTouches,
1048 changedTouches = toArray(ev.changedTouches),
1049 changedTargetTouches = [],
1050 target = this.target;
1051
1052 // get target touches from touches
1053 targetTouches = allTouches.filter(function(touch) {
1054 return hasParent(touch.target, target);
1055 });
1056
1057 // collect touches
1058 if (type === INPUT_START) {
1059 i = 0;
1060 while (i < targetTouches.length) {
1061 targetIds[targetTouches[i].identifier] = true;
1062 i++;
1063 }
1064 }
1065
1066 // filter changed touches to only contain touches that exist in the collected target ids
1067 i = 0;
1068 while (i < changedTouches.length) {
1069 if (targetIds[changedTouches[i].identifier]) {
1070 changedTargetTouches.push(changedTouches[i]);
1071 }
1072
1073 // cleanup removed touches
1074 if (type & (INPUT_END | INPUT_CANCEL)) {
1075 delete targetIds[changedTouches[i].identifier];
1076 }
1077 i++;
1078 }
1079
1080 if (!changedTargetTouches.length) {
1081 return;
1082 }
1083
1084 return [
1085 // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel'
1086 uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true),
1087 changedTargetTouches
1088 ];
1089 }
1090
1091 /**
1092 * Combined touch and mouse input
1093 *
1094 * Touch has a higher priority then mouse, and while touching no mouse events are allowed.
1095 * This because touch devices also emit mouse events while doing a touch.
1096 *
1097 * @constructor
1098 * @extends Input
1099 */
1100
1101 var DEDUP_TIMEOUT = 2500;
1102 var DEDUP_DISTANCE = 25;
1103
1104 function TouchMouseInput() {
1105 Input.apply(this, arguments);
1106
1107 var handler = bindFn(this.handler, this);
1108 this.touch = new TouchInput(this.manager, handler);
1109 this.mouse = new MouseInput(this.manager, handler);
1110
1111 this.primaryTouch = null;
1112 this.lastTouches = [];
1113 }
1114
1115 inherit(TouchMouseInput, Input, {
1116 /**
1117 * handle mouse and touch events
1118 * @param {Hammer} manager
1119 * @param {String} inputEvent
1120 * @param {Object} inputData
1121 */
1122 handler: function TMEhandler(manager, inputEvent, inputData) {
1123 var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH),
1124 isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE);
1125
1126 if (isMouse && inputData.sourceCapabilities && inputData.sourceCapabilities.firesTouchEvents) {
1127 return;
1128 }
1129
1130 // when we're in a touch event, record touches to de-dupe synthetic mouse event
1131 if (isTouch) {
1132 recordTouches.call(this, inputEvent, inputData);
1133 } else if (isMouse && isSyntheticEvent.call(this, inputData)) {
1134 return;
1135 }
1136
1137 this.callback(manager, inputEvent, inputData);
1138 },
1139
1140 /**
1141 * remove the event listeners
1142 */
1143 destroy: function destroy() {
1144 this.touch.destroy();
1145 this.mouse.destroy();
1146 }
1147 });
1148
1149 function recordTouches(eventType, eventData) {
1150 if (eventType & INPUT_START) {
1151 this.primaryTouch = eventData.changedPointers[0].identifier;
1152 setLastTouch.call(this, eventData);
1153 } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
1154 setLastTouch.call(this, eventData);
1155 }
1156 }
1157
1158 function setLastTouch(eventData) {
1159 var touch = eventData.changedPointers[0];
1160
1161 if (touch.identifier === this.primaryTouch) {
1162 var lastTouch = {x: touch.clientX, y: touch.clientY};
1163 this.lastTouches.push(lastTouch);
1164 var lts = this.lastTouches;
1165 var removeLastTouch = function() {
1166 var i = lts.indexOf(lastTouch);
1167 if (i > -1) {
1168 lts.splice(i, 1);
1169 }
1170 };
1171 setTimeout(removeLastTouch, DEDUP_TIMEOUT);
1172 }
1173 }
1174
1175 function isSyntheticEvent(eventData) {
1176 var x = eventData.srcEvent.clientX, y = eventData.srcEvent.clientY;
1177 for (var i = 0; i < this.lastTouches.length; i++) {
1178 var t = this.lastTouches[i];
1179 var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y);
1180 if (dx <= DEDUP_DISTANCE && dy <= DEDUP_DISTANCE) {
1181 return true;
1182 }
1183 }
1184 return false;
1185 }
1186
1187 var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction');
1188 var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined;
1189
1190 // magical touchAction value
1191 var TOUCH_ACTION_COMPUTE = 'compute';
1192 var TOUCH_ACTION_AUTO = 'auto';
1193 var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented
1194 var TOUCH_ACTION_NONE = 'none';
1195 var TOUCH_ACTION_PAN_X = 'pan-x';
1196 var TOUCH_ACTION_PAN_Y = 'pan-y';
1197 var TOUCH_ACTION_MAP = getTouchActionProps();
1198
1199 /**
1200 * Touch Action
1201 * sets the touchAction property or uses the js alternative
1202 * @param {Manager} manager
1203 * @param {String} value
1204 * @constructor
1205 */
1206 function TouchAction(manager, value) {
1207 this.manager = manager;
1208 this.set(value);
1209 }
1210
1211 TouchAction.prototype = {
1212 /**
1213 * set the touchAction value on the element or enable the polyfill
1214 * @param {String} value
1215 */
1216 set: function(value) {
1217 // find out the touch-action by the event handlers
1218 if (value == TOUCH_ACTION_COMPUTE) {
1219 value = this.compute();
1220 }
1221
1222 if (NATIVE_TOUCH_ACTION && this.manager.element.style && TOUCH_ACTION_MAP[value]) {
1223 this.manager.element.style[PREFIXED_TOUCH_ACTION] = value;
1224 }
1225 this.actions = value.toLowerCase().trim();
1226 },
1227
1228 /**
1229 * just re-set the touchAction value
1230 */
1231 update: function() {
1232 this.set(this.manager.options.touchAction);
1233 },
1234
1235 /**
1236 * compute the value for the touchAction property based on the recognizer's settings
1237 * @returns {String} value
1238 */
1239 compute: function() {
1240 var actions = [];
1241 each(this.manager.recognizers, function(recognizer) {
1242 if (boolOrFn(recognizer.options.enable, [recognizer])) {
1243 actions = actions.concat(recognizer.getTouchAction());
1244 }
1245 });
1246 return cleanTouchActions(actions.join(' '));
1247 },
1248
1249 /**
1250 * this method is called on each input cycle and provides the preventing of the browser behavior
1251 * @param {Object} input
1252 */
1253 preventDefaults: function(input) {
1254 var srcEvent = input.srcEvent;
1255 var direction = input.offsetDirection;
1256
1257 // if the touch action did prevented once this session
1258 if (this.manager.session.prevented) {
1259 srcEvent.preventDefault();
1260 return;
1261 }
1262
1263 var actions = this.actions;
1264 var hasNone = inStr(actions, TOUCH_ACTION_NONE) && !TOUCH_ACTION_MAP[TOUCH_ACTION_NONE];
1265 var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_Y];
1266 var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_X];
1267
1268 if (hasNone) {
1269 //do not prevent defaults if this is a tap gesture
1270
1271 var isTapPointer = input.pointers.length === 1;
1272 var isTapMovement = input.distance < 2;
1273 var isTapTouchTime = input.deltaTime < 250;
1274
1275 if (isTapPointer && isTapMovement && isTapTouchTime) {
1276 return;
1277 }
1278 }
1279
1280 if (hasPanX && hasPanY) {
1281 // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent
1282 return;
1283 }
1284
1285 if (hasNone ||
1286 (hasPanY && direction & DIRECTION_HORIZONTAL) ||
1287 (hasPanX && direction & DIRECTION_VERTICAL)) {
1288 return this.preventSrc(srcEvent);
1289 }
1290 },
1291
1292 /**
1293 * call preventDefault to prevent the browser's default behavior (scrolling in most cases)
1294 * @param {Object} srcEvent
1295 */
1296 preventSrc: function(srcEvent) {
1297 this.manager.session.prevented = true;
1298 srcEvent.preventDefault();
1299 }
1300 };
1301
1302 /**
1303 * when the touchActions are collected they are not a valid value, so we need to clean things up. *
1304 * @param {String} actions
1305 * @returns {*}
1306 */
1307 function cleanTouchActions(actions) {
1308 // none
1309 if (inStr(actions, TOUCH_ACTION_NONE)) {
1310 return TOUCH_ACTION_NONE;
1311 }
1312
1313 var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X);
1314 var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y);
1315
1316 // if both pan-x and pan-y are set (different recognizers
1317 // for different directions, e.g. horizontal pan but vertical swipe?)
1318 // we need none (as otherwise with pan-x pan-y combined none of these
1319 // recognizers will work, since the browser would handle all panning
1320 if (hasPanX && hasPanY) {
1321 return TOUCH_ACTION_NONE;
1322 }
1323
1324 // pan-x OR pan-y
1325 if (hasPanX || hasPanY) {
1326 return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y;
1327 }
1328
1329 // manipulation
1330 if (inStr(actions, TOUCH_ACTION_MANIPULATION)) {
1331 return TOUCH_ACTION_MANIPULATION;
1332 }
1333
1334 return TOUCH_ACTION_AUTO;
1335 }
1336
1337 function getTouchActionProps() {
1338 if (!NATIVE_TOUCH_ACTION) {
1339 return false;
1340 }
1341 var touchMap = {};
1342 var cssSupports = window.CSS && window.CSS.supports;
1343 ['auto', 'manipulation', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'].forEach(function(val) {
1344
1345 // If css.supports is not supported but there is native touch-action assume it supports
1346 // all values. This is the case for IE 10 and 11.
1347 touchMap[val] = cssSupports ? window.CSS.supports('touch-action', val) : true;
1348 });
1349 return touchMap;
1350 }
1351
1352 /**
1353 * Recognizer flow explained; *
1354 * All recognizers have the initial state of POSSIBLE when a input session starts.
1355 * The definition of a input session is from the first input until the last input, with all it's movement in it. *
1356 * Example session for mouse-input: mousedown -> mousemove -> mouseup
1357 *
1358 * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed
1359 * which determines with state it should be.
1360 *
1361 * If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to
1362 * POSSIBLE to give it another change on the next cycle.
1363 *
1364 * Possible
1365 * |
1366 * +-----+---------------+
1367 * | |
1368 * +-----+-----+ |
1369 * | | |
1370 * Failed Cancelled |
1371 * +-------+------+
1372 * | |
1373 * Recognized Began
1374 * |
1375 * Changed
1376 * |
1377 * Ended/Recognized
1378 */
1379 var STATE_POSSIBLE = 1;
1380 var STATE_BEGAN = 2;
1381 var STATE_CHANGED = 4;
1382 var STATE_ENDED = 8;
1383 var STATE_RECOGNIZED = STATE_ENDED;
1384 var STATE_CANCELLED = 16;
1385 var STATE_FAILED = 32;
1386
1387 /**
1388 * Recognizer
1389 * Every recognizer needs to extend from this class.
1390 * @constructor
1391 * @param {Object} options
1392 */
1393 function Recognizer(options) {
1394 this.options = assign({}, this.defaults, options || {});
1395
1396 this.id = uniqueId();
1397
1398 this.manager = null;
1399
1400 // default is enable true
1401 this.options.enable = ifUndefined(this.options.enable, true);
1402
1403 this.state = STATE_POSSIBLE;
1404
1405 this.simultaneous = {};
1406 this.requireFail = [];
1407 }
1408
1409 Recognizer.prototype = {
1410 /**
1411 * @virtual
1412 * @type {Object}
1413 */
1414 defaults: {},
1415
1416 /**
1417 * set options
1418 * @param {Object} options
1419 * @return {Recognizer}
1420 */
1421 set: function(options) {
1422 assign(this.options, options);
1423
1424 // also update the touchAction, in case something changed about the directions/enabled state
1425 this.manager && this.manager.touchAction.update();
1426 return this;
1427 },
1428
1429 /**
1430 * recognize simultaneous with an other recognizer.
1431 * @param {Recognizer} otherRecognizer
1432 * @returns {Recognizer} this
1433 */
1434 recognizeWith: function(otherRecognizer) {
1435 if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) {
1436 return this;
1437 }
1438
1439 var simultaneous = this.simultaneous;
1440 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
1441 if (!simultaneous[otherRecognizer.id]) {
1442 simultaneous[otherRecognizer.id] = otherRecognizer;
1443 otherRecognizer.recognizeWith(this);
1444 }
1445 return this;
1446 },
1447
1448 /**
1449 * drop the simultaneous link. it doesnt remove the link on the other recognizer.
1450 * @param {Recognizer} otherRecognizer
1451 * @returns {Recognizer} this
1452 */
1453 dropRecognizeWith: function(otherRecognizer) {
1454 if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) {
1455 return this;
1456 }
1457
1458 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
1459 delete this.simultaneous[otherRecognizer.id];
1460 return this;
1461 },
1462
1463 /**
1464 * recognizer can only run when an other is failing
1465 * @param {Recognizer} otherRecognizer
1466 * @returns {Recognizer} this
1467 */
1468 requireFailure: function(otherRecognizer) {
1469 if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) {
1470 return this;
1471 }
1472
1473 var requireFail = this.requireFail;
1474 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
1475 if (inArray(requireFail, otherRecognizer) === -1) {
1476 requireFail.push(otherRecognizer);
1477 otherRecognizer.requireFailure(this);
1478 }
1479 return this;
1480 },
1481
1482 /**
1483 * drop the requireFailure link. it does not remove the link on the other recognizer.
1484 * @param {Recognizer} otherRecognizer
1485 * @returns {Recognizer} this
1486 */
1487 dropRequireFailure: function(otherRecognizer) {
1488 if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) {
1489 return this;
1490 }
1491
1492 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
1493 var index = inArray(this.requireFail, otherRecognizer);
1494 if (index > -1) {
1495 this.requireFail.splice(index, 1);
1496 }
1497 return this;
1498 },
1499
1500 /**
1501 * has require failures boolean
1502 * @returns {boolean}
1503 */
1504 hasRequireFailures: function() {
1505 return this.requireFail.length > 0;
1506 },
1507
1508 /**
1509 * if the recognizer can recognize simultaneous with an other recognizer
1510 * @param {Recognizer} otherRecognizer
1511 * @returns {Boolean}
1512 */
1513 canRecognizeWith: function(otherRecognizer) {
1514 return !!this.simultaneous[otherRecognizer.id];
1515 },
1516
1517 /**
1518 * You should use `tryEmit` instead of `emit` directly to check
1519 * that all the needed recognizers has failed before emitting.
1520 * @param {Object} input
1521 */
1522 emit: function(input) {
1523 var self = this;
1524 var state = this.state;
1525
1526 function emit(event) {
1527 self.manager.emit(event, input);
1528 }
1529
1530 // 'panstart' and 'panmove'
1531 if (state < STATE_ENDED) {
1532 emit(self.options.event + stateStr(state));
1533 }
1534
1535 emit(self.options.event); // simple 'eventName' events
1536
1537 if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...)
1538 emit(input.additionalEvent);
1539 }
1540
1541 // panend and pancancel
1542 if (state >= STATE_ENDED) {
1543 emit(self.options.event + stateStr(state));
1544 }
1545 },
1546
1547 /**
1548 * Check that all the require failure recognizers has failed,
1549 * if true, it emits a gesture event,
1550 * otherwise, setup the state to FAILED.
1551 * @param {Object} input
1552 */
1553 tryEmit: function(input) {
1554 if (this.canEmit()) {
1555 return this.emit(input);
1556 }
1557 // it's failing anyway
1558 this.state = STATE_FAILED;
1559 },
1560
1561 /**
1562 * can we emit?
1563 * @returns {boolean}
1564 */
1565 canEmit: function() {
1566 var i = 0;
1567 while (i < this.requireFail.length) {
1568 if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) {
1569 return false;
1570 }
1571 i++;
1572 }
1573 return true;
1574 },
1575
1576 /**
1577 * update the recognizer
1578 * @param {Object} inputData
1579 */
1580 recognize: function(inputData) {
1581 // make a new copy of the inputData
1582 // so we can change the inputData without messing up the other recognizers
1583 var inputDataClone = assign({}, inputData);
1584
1585 // is is enabled and allow recognizing?
1586 if (!boolOrFn(this.options.enable, [this, inputDataClone])) {
1587 this.reset();
1588 this.state = STATE_FAILED;
1589 return;
1590 }
1591
1592 // reset when we've reached the end
1593 if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) {
1594 this.state = STATE_POSSIBLE;
1595 }
1596
1597 this.state = this.process(inputDataClone);
1598
1599 // the recognizer has recognized a gesture
1600 // so trigger an event
1601 if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) {
1602 this.tryEmit(inputDataClone);
1603 }
1604 },
1605
1606 /**
1607 * return the state of the recognizer
1608 * the actual recognizing happens in this method
1609 * @virtual
1610 * @param {Object} inputData
1611 * @returns {Const} STATE
1612 */
1613 process: function(inputData) { }, // jshint ignore:line
1614
1615 /**
1616 * return the preferred touch-action
1617 * @virtual
1618 * @returns {Array}
1619 */
1620 getTouchAction: function() { },
1621
1622 /**
1623 * called when the gesture isn't allowed to recognize
1624 * like when another is being recognized or it is disabled
1625 * @virtual
1626 */
1627 reset: function() { }
1628 };
1629
1630 /**
1631 * get a usable string, used as event postfix
1632 * @param {Const} state
1633 * @returns {String} state
1634 */
1635 function stateStr(state) {
1636 if (state & STATE_CANCELLED) {
1637 return 'cancel';
1638 } else if (state & STATE_ENDED) {
1639 return 'end';
1640 } else if (state & STATE_CHANGED) {
1641 return 'move';
1642 } else if (state & STATE_BEGAN) {
1643 return 'start';
1644 }
1645 return '';
1646 }
1647
1648 /**
1649 * direction cons to string
1650 * @param {Const} direction
1651 * @returns {String}
1652 */
1653 function directionStr(direction) {
1654 if (direction == DIRECTION_DOWN) {
1655 return 'down';
1656 } else if (direction == DIRECTION_UP) {
1657 return 'up';
1658 } else if (direction == DIRECTION_LEFT) {
1659 return 'left';
1660 } else if (direction == DIRECTION_RIGHT) {
1661 return 'right';
1662 }
1663 return '';
1664 }
1665
1666 /**
1667 * get a recognizer by name if it is bound to a manager
1668 * @param {Recognizer|String} otherRecognizer
1669 * @param {Recognizer} recognizer
1670 * @returns {Recognizer}
1671 */
1672 function getRecognizerByNameIfManager(otherRecognizer, recognizer) {
1673 var manager = recognizer.manager;
1674 if (manager) {
1675 return manager.get(otherRecognizer);
1676 }
1677 return otherRecognizer;
1678 }
1679
1680 /**
1681 * This recognizer is just used as a base for the simple attribute recognizers.
1682 * @constructor
1683 * @extends Recognizer
1684 */
1685 function AttrRecognizer() {
1686 Recognizer.apply(this, arguments);
1687 }
1688
1689 inherit(AttrRecognizer, Recognizer, {
1690 /**
1691 * @namespace
1692 * @memberof AttrRecognizer
1693 */
1694 defaults: {
1695 /**
1696 * @type {Number}
1697 * @default 1
1698 */
1699 pointers: 1
1700 },
1701
1702 /**
1703 * Used to check if it the recognizer receives valid input, like input.distance > 10.
1704 * @memberof AttrRecognizer
1705 * @param {Object} input
1706 * @returns {Boolean} recognized
1707 */
1708 attrTest: function(input) {
1709 var optionPointers = this.options.pointers;
1710 return optionPointers === 0 || input.pointers.length === optionPointers;
1711 },
1712
1713 /**
1714 * Process the input and return the state for the recognizer
1715 * @memberof AttrRecognizer
1716 * @param {Object} input
1717 * @returns {*} State
1718 */
1719 process: function(input) {
1720 var state = this.state;
1721 var eventType = input.eventType;
1722
1723 var isRecognized = state & (STATE_BEGAN | STATE_CHANGED);
1724 var isValid = this.attrTest(input);
1725
1726 // on cancel input and we've recognized before, return STATE_CANCELLED
1727 if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) {
1728 return state | STATE_CANCELLED;
1729 } else if (isRecognized || isValid) {
1730 if (eventType & INPUT_END) {
1731 return state | STATE_ENDED;
1732 } else if (!(state & STATE_BEGAN)) {
1733 return STATE_BEGAN;
1734 }
1735 return state | STATE_CHANGED;
1736 }
1737 return STATE_FAILED;
1738 }
1739 });
1740
1741 /**
1742 * Pan
1743 * Recognized when the pointer is down and moved in the allowed direction.
1744 * @constructor
1745 * @extends AttrRecognizer
1746 */
1747 function PanRecognizer() {
1748 AttrRecognizer.apply(this, arguments);
1749
1750 this.pX = null;
1751 this.pY = null;
1752 }
1753
1754 inherit(PanRecognizer, AttrRecognizer, {
1755 /**
1756 * @namespace
1757 * @memberof PanRecognizer
1758 */
1759 defaults: {
1760 event: 'pan',
1761 threshold: 10,
1762 pointers: 1,
1763 direction: DIRECTION_ALL
1764 },
1765
1766 getTouchAction: function() {
1767 var direction = this.options.direction;
1768 var actions = [];
1769 if (direction & DIRECTION_HORIZONTAL) {
1770 actions.push(TOUCH_ACTION_PAN_Y);
1771 }
1772 if (direction & DIRECTION_VERTICAL) {
1773 actions.push(TOUCH_ACTION_PAN_X);
1774 }
1775 return actions;
1776 },
1777
1778 directionTest: function(input) {
1779 var options = this.options;
1780 var hasMoved = true;
1781 var distance = input.distance;
1782 var direction = input.direction;
1783 var x = input.deltaX;
1784 var y = input.deltaY;
1785
1786 // lock to axis?
1787 if (!(direction & options.direction)) {
1788 if (options.direction & DIRECTION_HORIZONTAL) {
1789 direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
1790 hasMoved = x != this.pX;
1791 distance = Math.abs(input.deltaX);
1792 } else {
1793 direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN;
1794 hasMoved = y != this.pY;
1795 distance = Math.abs(input.deltaY);
1796 }
1797 }
1798 input.direction = direction;
1799 return hasMoved && distance > options.threshold && direction & options.direction;
1800 },
1801
1802 attrTest: function(input) {
1803 return AttrRecognizer.prototype.attrTest.call(this, input) &&
1804 (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input)));
1805 },
1806
1807 emit: function(input) {
1808
1809 this.pX = input.deltaX;
1810 this.pY = input.deltaY;
1811
1812 var direction = directionStr(input.direction);
1813
1814 if (direction) {
1815 input.additionalEvent = this.options.event + direction;
1816 }
1817 this._super.emit.call(this, input);
1818 }
1819 });
1820
1821 /**
1822 * Pinch
1823 * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out).
1824 * @constructor
1825 * @extends AttrRecognizer
1826 */
1827 function PinchRecognizer() {
1828 AttrRecognizer.apply(this, arguments);
1829 }
1830
1831 inherit(PinchRecognizer, AttrRecognizer, {
1832 /**
1833 * @namespace
1834 * @memberof PinchRecognizer
1835 */
1836 defaults: {
1837 event: 'pinch',
1838 threshold: 0,
1839 pointers: 2
1840 },
1841
1842 getTouchAction: function() {
1843 return [TOUCH_ACTION_NONE];
1844 },
1845
1846 attrTest: function(input) {
1847 return this._super.attrTest.call(this, input) &&
1848 (Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN);
1849 },
1850
1851 emit: function(input) {
1852 if (input.scale !== 1) {
1853 var inOut = input.scale < 1 ? 'in' : 'out';
1854 input.additionalEvent = this.options.event + inOut;
1855 }
1856 this._super.emit.call(this, input);
1857 }
1858 });
1859
1860 /**
1861 * Press
1862 * Recognized when the pointer is down for x ms without any movement.
1863 * @constructor
1864 * @extends Recognizer
1865 */
1866 function PressRecognizer() {
1867 Recognizer.apply(this, arguments);
1868
1869 this._timer = null;
1870 this._input = null;
1871 }
1872
1873 inherit(PressRecognizer, Recognizer, {
1874 /**
1875 * @namespace
1876 * @memberof PressRecognizer
1877 */
1878 defaults: {
1879 event: 'press',
1880 pointers: 1,
1881 time: 251, // minimal time of the pointer to be pressed
1882 threshold: 9 // a minimal movement is ok, but keep it low
1883 },
1884
1885 getTouchAction: function() {
1886 return [TOUCH_ACTION_AUTO];
1887 },
1888
1889 process: function(input) {
1890 var options = this.options;
1891 var validPointers = input.pointers.length === options.pointers;
1892 var validMovement = input.distance < options.threshold;
1893 var validTime = input.deltaTime > options.time;
1894
1895 this._input = input;
1896
1897 // we only allow little movement
1898 // and we've reached an end event, so a tap is possible
1899 if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) {
1900 this.reset();
1901 } else if (input.eventType & INPUT_START) {
1902 this.reset();
1903 this._timer = setTimeoutContext(function() {
1904 this.state = STATE_RECOGNIZED;
1905 this.tryEmit();
1906 }, options.time, this);
1907 } else if (input.eventType & INPUT_END) {
1908 return STATE_RECOGNIZED;
1909 }
1910 return STATE_FAILED;
1911 },
1912
1913 reset: function() {
1914 clearTimeout(this._timer);
1915 },
1916
1917 emit: function(input) {
1918 if (this.state !== STATE_RECOGNIZED) {
1919 return;
1920 }
1921
1922 if (input && (input.eventType & INPUT_END)) {
1923 this.manager.emit(this.options.event + 'up', input);
1924 } else {
1925 this._input.timeStamp = now();
1926 this.manager.emit(this.options.event, this._input);
1927 }
1928 }
1929 });
1930
1931 /**
1932 * Rotate
1933 * Recognized when two or more pointer are moving in a circular motion.
1934 * @constructor
1935 * @extends AttrRecognizer
1936 */
1937 function RotateRecognizer() {
1938 AttrRecognizer.apply(this, arguments);
1939 }
1940
1941 inherit(RotateRecognizer, AttrRecognizer, {
1942 /**
1943 * @namespace
1944 * @memberof RotateRecognizer
1945 */
1946 defaults: {
1947 event: 'rotate',
1948 threshold: 0,
1949 pointers: 2
1950 },
1951
1952 getTouchAction: function() {
1953 return [TOUCH_ACTION_NONE];
1954 },
1955
1956 attrTest: function(input) {
1957 return this._super.attrTest.call(this, input) &&
1958 (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN);
1959 }
1960 });
1961
1962 /**
1963 * Swipe
1964 * Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction.
1965 * @constructor
1966 * @extends AttrRecognizer
1967 */
1968 function SwipeRecognizer() {
1969 AttrRecognizer.apply(this, arguments);
1970 }
1971
1972 inherit(SwipeRecognizer, AttrRecognizer, {
1973 /**
1974 * @namespace
1975 * @memberof SwipeRecognizer
1976 */
1977 defaults: {
1978 event: 'swipe',
1979 threshold: 10,
1980 velocity: 0.3,
1981 direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL,
1982 pointers: 1
1983 },
1984
1985 getTouchAction: function() {
1986 return PanRecognizer.prototype.getTouchAction.call(this);
1987 },
1988
1989 attrTest: function(input) {
1990 var direction = this.options.direction;
1991 var velocity;
1992
1993 if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) {
1994 velocity = input.overallVelocity;
1995 } else if (direction & DIRECTION_HORIZONTAL) {
1996 velocity = input.overallVelocityX;
1997 } else if (direction & DIRECTION_VERTICAL) {
1998 velocity = input.overallVelocityY;
1999 }
2000
2001 return this._super.attrTest.call(this, input) &&
2002 direction & input.offsetDirection &&
2003 input.distance > this.options.threshold &&
2004 input.maxPointers == this.options.pointers &&
2005 abs(velocity) > this.options.velocity && input.eventType & INPUT_END;
2006 },
2007
2008 emit: function(input) {
2009 var direction = directionStr(input.offsetDirection);
2010 if (direction) {
2011 this.manager.emit(this.options.event + direction, input);
2012 }
2013
2014 this.manager.emit(this.options.event, input);
2015 }
2016 });
2017
2018 /**
2019 * A tap is ecognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur
2020 * between the given interval and position. The delay option can be used to recognize multi-taps without firing
2021 * a single tap.
2022 *
2023 * The eventData from the emitted event contains the property `tapCount`, which contains the amount of
2024 * multi-taps being recognized.
2025 * @constructor
2026 * @extends Recognizer
2027 */
2028 function TapRecognizer() {
2029 Recognizer.apply(this, arguments);
2030
2031 // previous time and center,
2032 // used for tap counting
2033 this.pTime = false;
2034 this.pCenter = false;
2035
2036 this._timer = null;
2037 this._input = null;
2038 this.count = 0;
2039 }
2040
2041 inherit(TapRecognizer, Recognizer, {
2042 /**
2043 * @namespace
2044 * @memberof PinchRecognizer
2045 */
2046 defaults: {
2047 event: 'tap',
2048 pointers: 1,
2049 taps: 1,
2050 interval: 300, // max time between the multi-tap taps
2051 time: 250, // max time of the pointer to be down (like finger on the screen)
2052 threshold: 9, // a minimal movement is ok, but keep it low
2053 posThreshold: 10 // a multi-tap can be a bit off the initial position
2054 },
2055
2056 getTouchAction: function() {
2057 return [TOUCH_ACTION_MANIPULATION];
2058 },
2059
2060 process: function(input) {
2061 var options = this.options;
2062
2063 var validPointers = input.pointers.length === options.pointers;
2064 var validMovement = input.distance < options.threshold;
2065 var validTouchTime = input.deltaTime < options.time;
2066
2067 this.reset();
2068
2069 if ((input.eventType & INPUT_START) && (this.count === 0)) {
2070 return this.failTimeout();
2071 }
2072
2073 // we only allow little movement
2074 // and we've reached an end event, so a tap is possible
2075 if (validMovement && validTouchTime && validPointers) {
2076 if (input.eventType != INPUT_END) {
2077 return this.failTimeout();
2078 }
2079
2080 var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true;
2081 var validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold;
2082
2083 this.pTime = input.timeStamp;
2084 this.pCenter = input.center;
2085
2086 if (!validMultiTap || !validInterval) {
2087 this.count = 1;
2088 } else {
2089 this.count += 1;
2090 }
2091
2092 this._input = input;
2093
2094 // if tap count matches we have recognized it,
2095 // else it has began recognizing...
2096 var tapCount = this.count % options.taps;
2097 if (tapCount === 0) {
2098 // no failing requirements, immediately trigger the tap event
2099 // or wait as long as the multitap interval to trigger
2100 if (!this.hasRequireFailures()) {
2101 return STATE_RECOGNIZED;
2102 } else {
2103 this._timer = setTimeoutContext(function() {
2104 this.state = STATE_RECOGNIZED;
2105 this.tryEmit();
2106 }, options.interval, this);
2107 return STATE_BEGAN;
2108 }
2109 }
2110 }
2111 return STATE_FAILED;
2112 },
2113
2114 failTimeout: function() {
2115 this._timer = setTimeoutContext(function() {
2116 this.state = STATE_FAILED;
2117 }, this.options.interval, this);
2118 return STATE_FAILED;
2119 },
2120
2121 reset: function() {
2122 clearTimeout(this._timer);
2123 },
2124
2125 emit: function() {
2126 if (this.state == STATE_RECOGNIZED) {
2127 this._input.tapCount = this.count;
2128 this.manager.emit(this.options.event, this._input);
2129 }
2130 }
2131 });
2132
2133 /**
2134 * Simple way to create a manager with a default set of recognizers.
2135 * @param {HTMLElement} element
2136 * @param {Object} [options]
2137 * @constructor
2138 */
2139 function Hammer(element, options) {
2140 options = options || {};
2141 options.recognizers = ifUndefined(options.recognizers, Hammer.defaults.preset);
2142 return new Manager(element, options);
2143 }
2144
2145 /**
2146 * @const {string}
2147 */
2148 Hammer.VERSION = '2.0.8';
2149
2150 /**
2151 * default settings
2152 * @namespace
2153 */
2154 Hammer.defaults = {
2155 /**
2156 * set if DOM events are being triggered.
2157 * But this is slower and unused by simple implementations, so disabled by default.
2158 * @type {Boolean}
2159 * @default false
2160 */
2161 domEvents: false,
2162
2163 /**
2164 * The value for the touchAction property/fallback.
2165 * When set to `compute` it will magically set the correct value based on the added recognizers.
2166 * @type {String}
2167 * @default compute
2168 */
2169 touchAction: TOUCH_ACTION_COMPUTE,
2170
2171 /**
2172 * @type {Boolean}
2173 * @default true
2174 */
2175 enable: true,
2176
2177 /**
2178 * EXPERIMENTAL FEATURE -- can be removed/changed
2179 * Change the parent input target element.
2180 * If Null, then it is being set the to main element.
2181 * @type {Null|EventTarget}
2182 * @default null
2183 */
2184 inputTarget: null,
2185
2186 /**
2187 * force an input class
2188 * @type {Null|Function}
2189 * @default null
2190 */
2191 inputClass: null,
2192
2193 /**
2194 * Default recognizer setup when calling `Hammer()`
2195 * When creating a new Manager these will be skipped.
2196 * @type {Array}
2197 */
2198 preset: [
2199 // RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...]
2200 [RotateRecognizer, {enable: false}],
2201 [PinchRecognizer, {enable: false}, ['rotate']],
2202 [SwipeRecognizer, {direction: DIRECTION_HORIZONTAL}],
2203 [PanRecognizer, {direction: DIRECTION_HORIZONTAL}, ['swipe']],
2204 [TapRecognizer],
2205 [TapRecognizer, {event: 'doubletap', taps: 2}, ['tap']],
2206 [PressRecognizer]
2207 ],
2208
2209 /**
2210 * Some CSS properties can be used to improve the working of Hammer.
2211 * Add them to this method and they will be set when creating a new Manager.
2212 * @namespace
2213 */
2214 cssProps: {
2215 /**
2216 * Disables text selection to improve the dragging gesture. Mainly for desktop browsers.
2217 * @type {String}
2218 * @default 'none'
2219 */
2220 userSelect: 'none',
2221
2222 /**
2223 * Disable the Windows Phone grippers when pressing an element.
2224 * @type {String}
2225 * @default 'none'
2226 */
2227 touchSelect: 'none',
2228
2229 /**
2230 * Disables the default callout shown when you touch and hold a touch target.
2231 * On iOS, when you touch and hold a touch target such as a link, Safari displays
2232 * a callout containing information about the link. This property allows you to disable that callout.
2233 * @type {String}
2234 * @default 'none'
2235 */
2236 touchCallout: 'none',
2237
2238 /**
2239 * Specifies whether zooming is enabled. Used by IE10>
2240 * @type {String}
2241 * @default 'none'
2242 */
2243 contentZooming: 'none',
2244
2245 /**
2246 * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers.
2247 * @type {String}
2248 * @default 'none'
2249 */
2250 userDrag: 'none',
2251
2252 /**
2253 * Overrides the highlight color shown when the user taps a link or a JavaScript
2254 * clickable element in iOS. This property obeys the alpha value, if specified.
2255 * @type {String}
2256 * @default 'rgba(0,0,0,0)'
2257 */
2258 tapHighlightColor: 'rgba(0,0,0,0)'
2259 }
2260 };
2261
2262 var STOP = 1;
2263 var FORCED_STOP = 2;
2264
2265 /**
2266 * Manager
2267 * @param {HTMLElement} element
2268 * @param {Object} [options]
2269 * @constructor
2270 */
2271 function Manager(element, options) {
2272 this.options = assign({}, Hammer.defaults, options || {});
2273
2274 this.options.inputTarget = this.options.inputTarget || element;
2275
2276 this.handlers = {};
2277 this.session = {};
2278 this.recognizers = [];
2279 this.oldCssProps = {};
2280
2281 this.element = element;
2282 this.input = createInputInstance(this);
2283 this.touchAction = new TouchAction(this, this.options.touchAction);
2284
2285 toggleCssProps(this, true);
2286
2287 each(this.options.recognizers, function(item) {
2288 var recognizer = this.add(new (item[0])(item[1]));
2289 item[2] && recognizer.recognizeWith(item[2]);
2290 item[3] && recognizer.requireFailure(item[3]);
2291 }, this);
2292 }
2293
2294 Manager.prototype = {
2295 /**
2296 * set options
2297 * @param {Object} options
2298 * @returns {Manager}
2299 */
2300 set: function(options) {
2301 assign(this.options, options);
2302
2303 // Options that need a little more setup
2304 if (options.touchAction) {
2305 this.touchAction.update();
2306 }
2307 if (options.inputTarget) {
2308 // Clean up existing event listeners and reinitialize
2309 this.input.destroy();
2310 this.input.target = options.inputTarget;
2311 this.input.init();
2312 }
2313 return this;
2314 },
2315
2316 /**
2317 * stop recognizing for this session.
2318 * This session will be discarded, when a new [input]start event is fired.
2319 * When forced, the recognizer cycle is stopped immediately.
2320 * @param {Boolean} [force]
2321 */
2322 stop: function(force) {
2323 this.session.stopped = force ? FORCED_STOP : STOP;
2324 },
2325
2326 /**
2327 * run the recognizers!
2328 * called by the inputHandler function on every movement of the pointers (touches)
2329 * it walks through all the recognizers and tries to detect the gesture that is being made
2330 * @param {Object} inputData
2331 */
2332 recognize: function(inputData) {
2333 var session = this.session;
2334 if (session.stopped) {
2335 return;
2336 }
2337
2338 // run the touch-action polyfill
2339 this.touchAction.preventDefaults(inputData);
2340
2341 var recognizer;
2342 var recognizers = this.recognizers;
2343
2344 // this holds the recognizer that is being recognized.
2345 // so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED
2346 // if no recognizer is detecting a thing, it is set to `null`
2347 var curRecognizer = session.curRecognizer;
2348
2349 // reset when the last recognizer is recognized
2350 // or when we're in a new session
2351 if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) {
2352 curRecognizer = session.curRecognizer = null;
2353 }
2354
2355 var i = 0;
2356 while (i < recognizers.length) {
2357 recognizer = recognizers[i];
2358
2359 // find out if we are allowed try to recognize the input for this one.
2360 // 1. allow if the session is NOT forced stopped (see the .stop() method)
2361 // 2. allow if we still haven't recognized a gesture in this session, or the this recognizer is the one
2362 // that is being recognized.
2363 // 3. allow if the recognizer is allowed to run simultaneous with the current recognized recognizer.
2364 // this can be setup with the `recognizeWith()` method on the recognizer.
2365 if (session.stopped !== FORCED_STOP && ( // 1
2366 !curRecognizer || recognizer == curRecognizer || // 2
2367 recognizer.canRecognizeWith(curRecognizer))) { // 3
2368 recognizer.recognize(inputData);
2369 } else {
2370 recognizer.reset();
2371 }
2372
2373 // if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the
2374 // current active recognizer. but only if we don't already have an active recognizer
2375 if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) {
2376 curRecognizer = session.curRecognizer = recognizer;
2377 }
2378 i++;
2379 }
2380 },
2381
2382 /**
2383 * get a recognizer by its event name.
2384 * @param {Recognizer|String} recognizer
2385 * @returns {Recognizer|Null}
2386 */
2387 get: function(recognizer) {
2388 if (recognizer instanceof Recognizer) {
2389 return recognizer;
2390 }
2391
2392 var recognizers = this.recognizers;
2393 for (var i = 0; i < recognizers.length; i++) {
2394 if (recognizers[i].options.event == recognizer) {
2395 return recognizers[i];
2396 }
2397 }
2398 return null;
2399 },
2400
2401 /**
2402 * add a recognizer to the manager
2403 * existing recognizers with the same event name will be removed
2404 * @param {Recognizer} recognizer
2405 * @returns {Recognizer|Manager}
2406 */
2407 add: function(recognizer) {
2408 if (invokeArrayArg(recognizer, 'add', this)) {
2409 return this;
2410 }
2411
2412 // remove existing
2413 var existing = this.get(recognizer.options.event);
2414 if (existing) {
2415 this.remove(existing);
2416 }
2417
2418 this.recognizers.push(recognizer);
2419 recognizer.manager = this;
2420
2421 this.touchAction.update();
2422 return recognizer;
2423 },
2424
2425 /**
2426 * remove a recognizer by name or instance
2427 * @param {Recognizer|String} recognizer
2428 * @returns {Manager}
2429 */
2430 remove: function(recognizer) {
2431 if (invokeArrayArg(recognizer, 'remove', this)) {
2432 return this;
2433 }
2434
2435 recognizer = this.get(recognizer);
2436
2437 // let's make sure this recognizer exists
2438 if (recognizer) {
2439 var recognizers = this.recognizers;
2440 var index = inArray(recognizers, recognizer);
2441
2442 if (index !== -1) {
2443 recognizers.splice(index, 1);
2444 this.touchAction.update();
2445 }
2446 }
2447
2448 return this;
2449 },
2450
2451 /**
2452 * bind event
2453 * @param {String} events
2454 * @param {Function} handler
2455 * @returns {EventEmitter} this
2456 */
2457 on: function(events, handler) {
2458 if (events === undefined) {
2459 return;
2460 }
2461 if (handler === undefined) {
2462 return;
2463 }
2464
2465 var handlers = this.handlers;
2466 each(splitStr(events), function(event) {
2467 handlers[event] = handlers[event] || [];
2468 handlers[event].push(handler);
2469 });
2470 return this;
2471 },
2472
2473 /**
2474 * unbind event, leave emit blank to remove all handlers
2475 * @param {String} events
2476 * @param {Function} [handler]
2477 * @returns {EventEmitter} this
2478 */
2479 off: function(events, handler) {
2480 if (events === undefined) {
2481 return;
2482 }
2483
2484 var handlers = this.handlers;
2485 each(splitStr(events), function(event) {
2486 if (!handler) {
2487 delete handlers[event];
2488 } else {
2489 handlers[event] && handlers[event].splice(inArray(handlers[event], handler), 1);
2490 }
2491 });
2492 return this;
2493 },
2494
2495 /**
2496 * emit event to the listeners
2497 * @param {String} event
2498 * @param {Object} data
2499 */
2500 emit: function(event, data) {
2501 // we also want to trigger dom events
2502 if (this.options.domEvents) {
2503 triggerDomEvent(event, data);
2504 }
2505
2506 // no handlers, so skip it all
2507 var handlers = this.handlers[event] && this.handlers[event].slice();
2508 if (!handlers || !handlers.length) {
2509 return;
2510 }
2511
2512 data.type = event;
2513 data.preventDefault = function() {
2514 data.srcEvent.preventDefault();
2515 };
2516
2517 var i = 0;
2518 while (i < handlers.length) {
2519 handlers[i](data);
2520 i++;
2521 }
2522 },
2523
2524 /**
2525 * destroy the manager and unbinds all events
2526 * it doesn't unbind dom events, that is the user own responsibility
2527 */
2528 destroy: function() {
2529 this.element && toggleCssProps(this, false);
2530
2531 this.handlers = {};
2532 this.session = {};
2533 this.input.destroy();
2534 this.element = null;
2535 }
2536 };
2537
2538 /**
2539 * add/remove the css properties as defined in manager.options.cssProps
2540 * @param {Manager} manager
2541 * @param {Boolean} add
2542 */
2543 function toggleCssProps(manager, add) {
2544 var element = manager.element;
2545 if (!element.style) {
2546 return;
2547 }
2548 var prop;
2549 each(manager.options.cssProps, function(value, name) {
2550 prop = prefixed(element.style, name);
2551 if (add) {
2552 manager.oldCssProps[prop] = element.style[prop];
2553 element.style[prop] = value;
2554 } else {
2555 element.style[prop] = manager.oldCssProps[prop] || '';
2556 }
2557 });
2558 if (!add) {
2559 manager.oldCssProps = {};
2560 }
2561 }
2562
2563 /**
2564 * trigger dom event
2565 * @param {String} event
2566 * @param {Object} data
2567 */
2568 function triggerDomEvent(event, data) {
2569 var gestureEvent = document.createEvent('Event');
2570 gestureEvent.initEvent(event, true, true);
2571 gestureEvent.gesture = data;
2572 data.target.dispatchEvent(gestureEvent);
2573 }
2574
2575 assign(Hammer, {
2576 INPUT_START: INPUT_START,
2577 INPUT_MOVE: INPUT_MOVE,
2578 INPUT_END: INPUT_END,
2579 INPUT_CANCEL: INPUT_CANCEL,
2580
2581 STATE_POSSIBLE: STATE_POSSIBLE,
2582 STATE_BEGAN: STATE_BEGAN,
2583 STATE_CHANGED: STATE_CHANGED,
2584 STATE_ENDED: STATE_ENDED,
2585 STATE_RECOGNIZED: STATE_RECOGNIZED,
2586 STATE_CANCELLED: STATE_CANCELLED,
2587 STATE_FAILED: STATE_FAILED,
2588
2589 DIRECTION_NONE: DIRECTION_NONE,
2590 DIRECTION_LEFT: DIRECTION_LEFT,
2591 DIRECTION_RIGHT: DIRECTION_RIGHT,
2592 DIRECTION_UP: DIRECTION_UP,
2593 DIRECTION_DOWN: DIRECTION_DOWN,
2594 DIRECTION_HORIZONTAL: DIRECTION_HORIZONTAL,
2595 DIRECTION_VERTICAL: DIRECTION_VERTICAL,
2596 DIRECTION_ALL: DIRECTION_ALL,
2597
2598 Manager: Manager,
2599 Input: Input,
2600 TouchAction: TouchAction,
2601
2602 TouchInput: TouchInput,
2603 MouseInput: MouseInput,
2604 PointerEventInput: PointerEventInput,
2605 TouchMouseInput: TouchMouseInput,
2606 SingleTouchInput: SingleTouchInput,
2607
2608 Recognizer: Recognizer,
2609 AttrRecognizer: AttrRecognizer,
2610 Tap: TapRecognizer,
2611 Pan: PanRecognizer,
2612 Swipe: SwipeRecognizer,
2613 Pinch: PinchRecognizer,
2614 Rotate: RotateRecognizer,
2615 Press: PressRecognizer,
2616
2617 on: addEventListeners,
2618 off: removeEventListeners,
2619 each: each,
2620 merge: merge,
2621 extend: extend,
2622 assign: assign,
2623 inherit: inherit,
2624 bindFn: bindFn,
2625 prefixed: prefixed
2626 });
2627
2628 // this prevents errors when Hammer is loaded in the presence of an AMD
2629 // style loader but by script tag, not by the loader.
2630 var freeGlobal = (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {})); // jshint ignore:line
2631 freeGlobal.Hammer = Hammer;
2632
2633 if (typeof define === 'function' && define.amd) {
2634 define(function() {
2635 return Hammer;
2636 });
2637 } else if (typeof module != 'undefined' && module.exports) {
2638 module.exports = Hammer;
2639 } else {
2640 window[exportName] = Hammer;
2641 }
2642
2643 })(window, document, 'Hammer');
2644