touch_action 0.0.2alpha → 0.1.0

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