rubynk 0.0.2 → 0.0.3

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