UNPKG

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