hammerjs_rails 1.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 07e04e93367436529241e395b86d27136fd739ff
4
+ data.tar.gz: f8c7b6a4e8d8a0dec2a0178fd22e6669cdbe69ee
5
+ SHA512:
6
+ metadata.gz: 615c08b00653f78fb62551662205b784932efd2b1c5321f818a48619dc66162cd0ba5ce15594c930404d9924ea70bcadc1bb8f0bdc22fd2998e3c85cec9b6e49
7
+ data.tar.gz: 644b387e8ef92e1d6bc085be1452d7c1320b6f71b85e78604cd64b5f69a913a17f52ec7db4ea2caff70a84e43dc9b1d4b0ceb748c9652315054601e067799661
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in hammerjs_rails.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Guy Israeli
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,33 @@
1
+ # HammerjsRails
2
+
3
+ Hammer.js for Ruby on Rails
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'hammerjs_rails'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install hammerjs_rails
18
+
19
+ ## Usage
20
+
21
+ add to manifest (application.js)
22
+
23
+ //= require hammer
24
+
25
+ and then lay down the hammer on https://github.com/EightMedia/hammer.js
26
+
27
+ ## Contributing
28
+
29
+ 1. Fork it ( https://github.com/[my-github-username]/hammerjs_rails/fork )
30
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
31
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
32
+ 4. Push to the branch (`git push origin my-new-feature`)
33
+ 5. Create a new Pull Request
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,2163 @@
1
+ /*! Hammer.JS - v1.1.3 - 2014-05-22
2
+ * http://eightmedia.github.io/hammer.js
3
+ *
4
+ * Copyright (c) 2014 Jorik Tangelder <j.tangelder@gmail.com>;
5
+ * Licensed under the MIT license */
6
+
7
+ (function(window, undefined) {
8
+ 'use strict';
9
+
10
+ /**
11
+ * @main
12
+ * @module hammer
13
+ *
14
+ * @class Hammer
15
+ * @static
16
+ */
17
+
18
+ /**
19
+ * Hammer, use this to create instances
20
+ * ````
21
+ * var hammertime = new Hammer(myElement);
22
+ * ````
23
+ *
24
+ * @method Hammer
25
+ * @param {HTMLElement} element
26
+ * @param {Object} [options={}]
27
+ * @return {Hammer.Instance}
28
+ */
29
+ var Hammer = function Hammer(element, options) {
30
+ return new Hammer.Instance(element, options || {});
31
+ };
32
+
33
+ /**
34
+ * version, as defined in package.json
35
+ * the value will be set at each build
36
+ * @property VERSION
37
+ * @final
38
+ * @type {String}
39
+ */
40
+ Hammer.VERSION = '1.1.3';
41
+
42
+ /**
43
+ * default settings.
44
+ * more settings are defined per gesture at `/gestures`. Each gesture can be disabled/enabled
45
+ * by setting it's name (like `swipe`) to false.
46
+ * You can set the defaults for all instances by changing this object before creating an instance.
47
+ * @example
48
+ * ````
49
+ * Hammer.defaults.drag = false;
50
+ * Hammer.defaults.behavior.touchAction = 'pan-y';
51
+ * delete Hammer.defaults.behavior.userSelect;
52
+ * ````
53
+ * @property defaults
54
+ * @type {Object}
55
+ */
56
+ Hammer.defaults = {
57
+ /**
58
+ * this setting object adds styles and attributes to the element to prevent the browser from doing
59
+ * its native behavior. The css properties are auto prefixed for the browsers when needed.
60
+ * @property defaults.behavior
61
+ * @type {Object}
62
+ */
63
+ behavior: {
64
+ /**
65
+ * Disables text selection to improve the dragging gesture. When the value is `none` it also sets
66
+ * `onselectstart=false` for IE on the element. Mainly for desktop browsers.
67
+ * @property defaults.behavior.userSelect
68
+ * @type {String}
69
+ * @default 'none'
70
+ */
71
+ userSelect: 'none',
72
+
73
+ /**
74
+ * Specifies whether and how a given region can be manipulated by the user (for instance, by panning or zooming).
75
+ * Used by Chrome 35> and IE10>. By default this makes the element blocking any touch event.
76
+ * @property defaults.behavior.touchAction
77
+ * @type {String}
78
+ * @default: 'pan-y'
79
+ */
80
+ touchAction: 'pan-y',
81
+
82
+ /**
83
+ * Disables the default callout shown when you touch and hold a touch target.
84
+ * On iOS, when you touch and hold a touch target such as a link, Safari displays
85
+ * a callout containing information about the link. This property allows you to disable that callout.
86
+ * @property defaults.behavior.touchCallout
87
+ * @type {String}
88
+ * @default 'none'
89
+ */
90
+ touchCallout: 'none',
91
+
92
+ /**
93
+ * Specifies whether zooming is enabled. Used by IE10>
94
+ * @property defaults.behavior.contentZooming
95
+ * @type {String}
96
+ * @default 'none'
97
+ */
98
+ contentZooming: 'none',
99
+
100
+ /**
101
+ * Specifies that an entire element should be draggable instead of its contents.
102
+ * Mainly for desktop browsers.
103
+ * @property defaults.behavior.userDrag
104
+ * @type {String}
105
+ * @default 'none'
106
+ */
107
+ userDrag: 'none',
108
+
109
+ /**
110
+ * Overrides the highlight color shown when the user taps a link or a JavaScript
111
+ * clickable element in Safari on iPhone. This property obeys the alpha value, if specified.
112
+ *
113
+ * If you don't specify an alpha value, Safari on iPhone applies a default alpha value
114
+ * to the color. To disable tap highlighting, set the alpha value to 0 (invisible).
115
+ * If you set the alpha value to 1.0 (opaque), the element is not visible when tapped.
116
+ * @property defaults.behavior.tapHighlightColor
117
+ * @type {String}
118
+ * @default 'rgba(0,0,0,0)'
119
+ */
120
+ tapHighlightColor: 'rgba(0,0,0,0)'
121
+ }
122
+ };
123
+
124
+ /**
125
+ * hammer document where the base events are added at
126
+ * @property DOCUMENT
127
+ * @type {HTMLElement}
128
+ * @default window.document
129
+ */
130
+ Hammer.DOCUMENT = document;
131
+
132
+ /**
133
+ * detect support for pointer events
134
+ * @property HAS_POINTEREVENTS
135
+ * @type {Boolean}
136
+ */
137
+ Hammer.HAS_POINTEREVENTS = navigator.pointerEnabled || navigator.msPointerEnabled;
138
+
139
+ /**
140
+ * detect support for touch events
141
+ * @property HAS_TOUCHEVENTS
142
+ * @type {Boolean}
143
+ */
144
+ Hammer.HAS_TOUCHEVENTS = ('ontouchstart' in window);
145
+
146
+ /**
147
+ * detect mobile browsers
148
+ * @property IS_MOBILE
149
+ * @type {Boolean}
150
+ */
151
+ Hammer.IS_MOBILE = /mobile|tablet|ip(ad|hone|od)|android|silk/i.test(navigator.userAgent);
152
+
153
+ /**
154
+ * detect if we want to support mouseevents at all
155
+ * @property NO_MOUSEEVENTS
156
+ * @type {Boolean}
157
+ */
158
+ Hammer.NO_MOUSEEVENTS = (Hammer.HAS_TOUCHEVENTS && Hammer.IS_MOBILE) || Hammer.HAS_POINTEREVENTS;
159
+
160
+ /**
161
+ * interval in which Hammer recalculates current velocity/direction/angle in ms
162
+ * @property CALCULATE_INTERVAL
163
+ * @type {Number}
164
+ * @default 25
165
+ */
166
+ Hammer.CALCULATE_INTERVAL = 25;
167
+
168
+ /**
169
+ * eventtypes per touchevent (start, move, end) are filled by `Event.determineEventTypes` on `setup`
170
+ * the object contains the DOM event names per type (`EVENT_START`, `EVENT_MOVE`, `EVENT_END`)
171
+ * @property EVENT_TYPES
172
+ * @private
173
+ * @writeOnce
174
+ * @type {Object}
175
+ */
176
+ var EVENT_TYPES = {};
177
+
178
+ /**
179
+ * direction strings, for safe comparisons
180
+ * @property DIRECTION_DOWN|LEFT|UP|RIGHT
181
+ * @final
182
+ * @type {String}
183
+ * @default 'down' 'left' 'up' 'right'
184
+ */
185
+ var DIRECTION_DOWN = Hammer.DIRECTION_DOWN = 'down';
186
+ var DIRECTION_LEFT = Hammer.DIRECTION_LEFT = 'left';
187
+ var DIRECTION_UP = Hammer.DIRECTION_UP = 'up';
188
+ var DIRECTION_RIGHT = Hammer.DIRECTION_RIGHT = 'right';
189
+
190
+ /**
191
+ * pointertype strings, for safe comparisons
192
+ * @property POINTER_MOUSE|TOUCH|PEN
193
+ * @final
194
+ * @type {String}
195
+ * @default 'mouse' 'touch' 'pen'
196
+ */
197
+ var POINTER_MOUSE = Hammer.POINTER_MOUSE = 'mouse';
198
+ var POINTER_TOUCH = Hammer.POINTER_TOUCH = 'touch';
199
+ var POINTER_PEN = Hammer.POINTER_PEN = 'pen';
200
+
201
+ /**
202
+ * eventtypes
203
+ * @property EVENT_START|MOVE|END|RELEASE|TOUCH
204
+ * @final
205
+ * @type {String}
206
+ * @default 'start' 'change' 'move' 'end' 'release' 'touch'
207
+ */
208
+ var EVENT_START = Hammer.EVENT_START = 'start';
209
+ var EVENT_MOVE = Hammer.EVENT_MOVE = 'move';
210
+ var EVENT_END = Hammer.EVENT_END = 'end';
211
+ var EVENT_RELEASE = Hammer.EVENT_RELEASE = 'release';
212
+ var EVENT_TOUCH = Hammer.EVENT_TOUCH = 'touch';
213
+
214
+ /**
215
+ * if the window events are set...
216
+ * @property READY
217
+ * @writeOnce
218
+ * @type {Boolean}
219
+ * @default false
220
+ */
221
+ Hammer.READY = false;
222
+
223
+ /**
224
+ * plugins namespace
225
+ * @property plugins
226
+ * @type {Object}
227
+ */
228
+ Hammer.plugins = Hammer.plugins || {};
229
+
230
+ /**
231
+ * gestures namespace
232
+ * see `/gestures` for the definitions
233
+ * @property gestures
234
+ * @type {Object}
235
+ */
236
+ Hammer.gestures = Hammer.gestures || {};
237
+
238
+ /**
239
+ * setup events to detect gestures on the document
240
+ * this function is called when creating an new instance
241
+ * @private
242
+ */
243
+ function setup() {
244
+ if(Hammer.READY) {
245
+ return;
246
+ }
247
+
248
+ // find what eventtypes we add listeners to
249
+ Event.determineEventTypes();
250
+
251
+ // Register all gestures inside Hammer.gestures
252
+ Utils.each(Hammer.gestures, function(gesture) {
253
+ Detection.register(gesture);
254
+ });
255
+
256
+ // Add touch events on the document
257
+ Event.onTouch(Hammer.DOCUMENT, EVENT_MOVE, Detection.detect);
258
+ Event.onTouch(Hammer.DOCUMENT, EVENT_END, Detection.detect);
259
+
260
+ // Hammer is ready...!
261
+ Hammer.READY = true;
262
+ }
263
+
264
+ /**
265
+ * @module hammer
266
+ *
267
+ * @class Utils
268
+ * @static
269
+ */
270
+ var Utils = Hammer.utils = {
271
+ /**
272
+ * extend method, could also be used for cloning when `dest` is an empty object.
273
+ * changes the dest object
274
+ * @method extend
275
+ * @param {Object} dest
276
+ * @param {Object} src
277
+ * @param {Boolean} [merge=false] do a merge
278
+ * @return {Object} dest
279
+ */
280
+ extend: function extend(dest, src, merge) {
281
+ for(var key in src) {
282
+ if(!src.hasOwnProperty(key) || (dest[key] !== undefined && merge)) {
283
+ continue;
284
+ }
285
+ dest[key] = src[key];
286
+ }
287
+ return dest;
288
+ },
289
+
290
+ /**
291
+ * simple addEventListener wrapper
292
+ * @method on
293
+ * @param {HTMLElement} element
294
+ * @param {String} type
295
+ * @param {Function} handler
296
+ */
297
+ on: function on(element, type, handler) {
298
+ element.addEventListener(type, handler, false);
299
+ },
300
+
301
+ /**
302
+ * simple removeEventListener wrapper
303
+ * @method off
304
+ * @param {HTMLElement} element
305
+ * @param {String} type
306
+ * @param {Function} handler
307
+ */
308
+ off: function off(element, type, handler) {
309
+ element.removeEventListener(type, handler, false);
310
+ },
311
+
312
+ /**
313
+ * forEach over arrays and objects
314
+ * @method each
315
+ * @param {Object|Array} obj
316
+ * @param {Function} iterator
317
+ * @param {any} iterator.item
318
+ * @param {Number} iterator.index
319
+ * @param {Object|Array} iterator.obj the source object
320
+ * @param {Object} context value to use as `this` in the iterator
321
+ */
322
+ each: function each(obj, iterator, context) {
323
+ var i, len;
324
+
325
+ // native forEach on arrays
326
+ if('forEach' in obj) {
327
+ obj.forEach(iterator, context);
328
+ // arrays
329
+ } else if(obj.length !== undefined) {
330
+ for(i = 0, len = obj.length; i < len; i++) {
331
+ if(iterator.call(context, obj[i], i, obj) === false) {
332
+ return;
333
+ }
334
+ }
335
+ // objects
336
+ } else {
337
+ for(i in obj) {
338
+ if(obj.hasOwnProperty(i) &&
339
+ iterator.call(context, obj[i], i, obj) === false) {
340
+ return;
341
+ }
342
+ }
343
+ }
344
+ },
345
+
346
+ /**
347
+ * find if a string contains the string using indexOf
348
+ * @method inStr
349
+ * @param {String} src
350
+ * @param {String} find
351
+ * @return {Boolean} found
352
+ */
353
+ inStr: function inStr(src, find) {
354
+ return src.indexOf(find) > -1;
355
+ },
356
+
357
+ /**
358
+ * find if a array contains the object using indexOf or a simple polyfill
359
+ * @method inArray
360
+ * @param {String} src
361
+ * @param {String} find
362
+ * @return {Boolean|Number} false when not found, or the index
363
+ */
364
+ inArray: function inArray(src, find) {
365
+ if(src.indexOf) {
366
+ var index = src.indexOf(find);
367
+ return (index === -1) ? false : index;
368
+ } else {
369
+ for(var i = 0, len = src.length; i < len; i++) {
370
+ if(src[i] === find) {
371
+ return i;
372
+ }
373
+ }
374
+ return false;
375
+ }
376
+ },
377
+
378
+ /**
379
+ * convert an array-like object (`arguments`, `touchlist`) to an array
380
+ * @method toArray
381
+ * @param {Object} obj
382
+ * @return {Array}
383
+ */
384
+ toArray: function toArray(obj) {
385
+ return Array.prototype.slice.call(obj, 0);
386
+ },
387
+
388
+ /**
389
+ * find if a node is in the given parent
390
+ * @method hasParent
391
+ * @param {HTMLElement} node
392
+ * @param {HTMLElement} parent
393
+ * @return {Boolean} found
394
+ */
395
+ hasParent: function hasParent(node, parent) {
396
+ while(node) {
397
+ if(node == parent) {
398
+ return true;
399
+ }
400
+ node = node.parentNode;
401
+ }
402
+ return false;
403
+ },
404
+
405
+ /**
406
+ * get the center of all the touches
407
+ * @method getCenter
408
+ * @param {Array} touches
409
+ * @return {Object} center contains `pageX`, `pageY`, `clientX` and `clientY` properties
410
+ */
411
+ getCenter: function getCenter(touches) {
412
+ var pageX = [],
413
+ pageY = [],
414
+ clientX = [],
415
+ clientY = [],
416
+ min = Math.min,
417
+ max = Math.max;
418
+
419
+ // no need to loop when only one touch
420
+ if(touches.length === 1) {
421
+ return {
422
+ pageX: touches[0].pageX,
423
+ pageY: touches[0].pageY,
424
+ clientX: touches[0].clientX,
425
+ clientY: touches[0].clientY
426
+ };
427
+ }
428
+
429
+ Utils.each(touches, function(touch) {
430
+ pageX.push(touch.pageX);
431
+ pageY.push(touch.pageY);
432
+ clientX.push(touch.clientX);
433
+ clientY.push(touch.clientY);
434
+ });
435
+
436
+ return {
437
+ pageX: (min.apply(Math, pageX) + max.apply(Math, pageX)) / 2,
438
+ pageY: (min.apply(Math, pageY) + max.apply(Math, pageY)) / 2,
439
+ clientX: (min.apply(Math, clientX) + max.apply(Math, clientX)) / 2,
440
+ clientY: (min.apply(Math, clientY) + max.apply(Math, clientY)) / 2
441
+ };
442
+ },
443
+
444
+ /**
445
+ * calculate the velocity between two points. unit is in px per ms.
446
+ * @method getVelocity
447
+ * @param {Number} deltaTime
448
+ * @param {Number} deltaX
449
+ * @param {Number} deltaY
450
+ * @return {Object} velocity `x` and `y`
451
+ */
452
+ getVelocity: function getVelocity(deltaTime, deltaX, deltaY) {
453
+ return {
454
+ x: Math.abs(deltaX / deltaTime) || 0,
455
+ y: Math.abs(deltaY / deltaTime) || 0
456
+ };
457
+ },
458
+
459
+ /**
460
+ * calculate the angle between two coordinates
461
+ * @method getAngle
462
+ * @param {Touch} touch1
463
+ * @param {Touch} touch2
464
+ * @return {Number} angle
465
+ */
466
+ getAngle: function getAngle(touch1, touch2) {
467
+ var x = touch2.clientX - touch1.clientX,
468
+ y = touch2.clientY - touch1.clientY;
469
+
470
+ return Math.atan2(y, x) * 180 / Math.PI;
471
+ },
472
+
473
+ /**
474
+ * do a small comparision to get the direction between two touches.
475
+ * @method getDirection
476
+ * @param {Touch} touch1
477
+ * @param {Touch} touch2
478
+ * @return {String} direction matches `DIRECTION_LEFT|RIGHT|UP|DOWN`
479
+ */
480
+ getDirection: function getDirection(touch1, touch2) {
481
+ var x = Math.abs(touch1.clientX - touch2.clientX),
482
+ y = Math.abs(touch1.clientY - touch2.clientY);
483
+
484
+ if(x >= y) {
485
+ return touch1.clientX - touch2.clientX > 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
486
+ }
487
+ return touch1.clientY - touch2.clientY > 0 ? DIRECTION_UP : DIRECTION_DOWN;
488
+ },
489
+
490
+ /**
491
+ * calculate the distance between two touches
492
+ * @method getDistance
493
+ * @param {Touch}touch1
494
+ * @param {Touch} touch2
495
+ * @return {Number} distance
496
+ */
497
+ getDistance: function getDistance(touch1, touch2) {
498
+ var x = touch2.clientX - touch1.clientX,
499
+ y = touch2.clientY - touch1.clientY;
500
+
501
+ return Math.sqrt((x * x) + (y * y));
502
+ },
503
+
504
+ /**
505
+ * calculate the scale factor between two touchLists
506
+ * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
507
+ * @method getScale
508
+ * @param {Array} start array of touches
509
+ * @param {Array} end array of touches
510
+ * @return {Number} scale
511
+ */
512
+ getScale: function getScale(start, end) {
513
+ // need two fingers...
514
+ if(start.length >= 2 && end.length >= 2) {
515
+ return this.getDistance(end[0], end[1]) / this.getDistance(start[0], start[1]);
516
+ }
517
+ return 1;
518
+ },
519
+
520
+ /**
521
+ * calculate the rotation degrees between two touchLists
522
+ * @method getRotation
523
+ * @param {Array} start array of touches
524
+ * @param {Array} end array of touches
525
+ * @return {Number} rotation
526
+ */
527
+ getRotation: function getRotation(start, end) {
528
+ // need two fingers
529
+ if(start.length >= 2 && end.length >= 2) {
530
+ return this.getAngle(end[1], end[0]) - this.getAngle(start[1], start[0]);
531
+ }
532
+ return 0;
533
+ },
534
+
535
+ /**
536
+ * find out if the direction is vertical *
537
+ * @method isVertical
538
+ * @param {String} direction matches `DIRECTION_UP|DOWN`
539
+ * @return {Boolean} is_vertical
540
+ */
541
+ isVertical: function isVertical(direction) {
542
+ return direction == DIRECTION_UP || direction == DIRECTION_DOWN;
543
+ },
544
+
545
+ /**
546
+ * set css properties with their prefixes
547
+ * @param {HTMLElement} element
548
+ * @param {String} prop
549
+ * @param {String} value
550
+ * @param {Boolean} [toggle=true]
551
+ * @return {Boolean}
552
+ */
553
+ setPrefixedCss: function setPrefixedCss(element, prop, value, toggle) {
554
+ var prefixes = ['', 'Webkit', 'Moz', 'O', 'ms'];
555
+ prop = Utils.toCamelCase(prop);
556
+
557
+ for(var i = 0; i < prefixes.length; i++) {
558
+ var p = prop;
559
+ // prefixes
560
+ if(prefixes[i]) {
561
+ p = prefixes[i] + p.slice(0, 1).toUpperCase() + p.slice(1);
562
+ }
563
+
564
+ // test the style
565
+ if(p in element.style) {
566
+ element.style[p] = (toggle == null || toggle) && value || '';
567
+ break;
568
+ }
569
+ }
570
+ },
571
+
572
+ /**
573
+ * toggle browser default behavior by setting css properties.
574
+ * `userSelect='none'` also sets `element.onselectstart` to false
575
+ * `userDrag='none'` also sets `element.ondragstart` to false
576
+ *
577
+ * @method toggleBehavior
578
+ * @param {HtmlElement} element
579
+ * @param {Object} props
580
+ * @param {Boolean} [toggle=true]
581
+ */
582
+ toggleBehavior: function toggleBehavior(element, props, toggle) {
583
+ if(!props || !element || !element.style) {
584
+ return;
585
+ }
586
+
587
+ // set the css properties
588
+ Utils.each(props, function(value, prop) {
589
+ Utils.setPrefixedCss(element, prop, value, toggle);
590
+ });
591
+
592
+ var falseFn = toggle && function() {
593
+ return false;
594
+ };
595
+
596
+ // also the disable onselectstart
597
+ if(props.userSelect == 'none') {
598
+ element.onselectstart = falseFn;
599
+ }
600
+ // and disable ondragstart
601
+ if(props.userDrag == 'none') {
602
+ element.ondragstart = falseFn;
603
+ }
604
+ },
605
+
606
+ /**
607
+ * convert a string with underscores to camelCase
608
+ * so prevent_default becomes preventDefault
609
+ * @param {String} str
610
+ * @return {String} camelCaseStr
611
+ */
612
+ toCamelCase: function toCamelCase(str) {
613
+ return str.replace(/[_-]([a-z])/g, function(s) {
614
+ return s[1].toUpperCase();
615
+ });
616
+ }
617
+ };
618
+
619
+
620
+ /**
621
+ * @module hammer
622
+ */
623
+ /**
624
+ * @class Event
625
+ * @static
626
+ */
627
+ var Event = Hammer.event = {
628
+ /**
629
+ * when touch events have been fired, this is true
630
+ * this is used to stop mouse events
631
+ * @property prevent_mouseevents
632
+ * @private
633
+ * @type {Boolean}
634
+ */
635
+ preventMouseEvents: false,
636
+
637
+ /**
638
+ * if EVENT_START has been fired
639
+ * @property started
640
+ * @private
641
+ * @type {Boolean}
642
+ */
643
+ started: false,
644
+
645
+ /**
646
+ * when the mouse is hold down, this is true
647
+ * @property should_detect
648
+ * @private
649
+ * @type {Boolean}
650
+ */
651
+ shouldDetect: false,
652
+
653
+ /**
654
+ * simple event binder with a hook and support for multiple types
655
+ * @method on
656
+ * @param {HTMLElement} element
657
+ * @param {String} type
658
+ * @param {Function} handler
659
+ * @param {Function} [hook]
660
+ * @param {Object} hook.type
661
+ */
662
+ on: function on(element, type, handler, hook) {
663
+ var types = type.split(' ');
664
+ Utils.each(types, function(type) {
665
+ Utils.on(element, type, handler);
666
+ hook && hook(type);
667
+ });
668
+ },
669
+
670
+ /**
671
+ * simple event unbinder with a hook and support for multiple types
672
+ * @method off
673
+ * @param {HTMLElement} element
674
+ * @param {String} type
675
+ * @param {Function} handler
676
+ * @param {Function} [hook]
677
+ * @param {Object} hook.type
678
+ */
679
+ off: function off(element, type, handler, hook) {
680
+ var types = type.split(' ');
681
+ Utils.each(types, function(type) {
682
+ Utils.off(element, type, handler);
683
+ hook && hook(type);
684
+ });
685
+ },
686
+
687
+ /**
688
+ * the core touch event handler.
689
+ * this finds out if we should to detect gestures
690
+ * @method onTouch
691
+ * @param {HTMLElement} element
692
+ * @param {String} eventType matches `EVENT_START|MOVE|END`
693
+ * @param {Function} handler
694
+ * @return onTouchHandler {Function} the core event handler
695
+ */
696
+ onTouch: function onTouch(element, eventType, handler) {
697
+ var self = this;
698
+
699
+ var onTouchHandler = function onTouchHandler(ev) {
700
+ var srcType = ev.type.toLowerCase(),
701
+ isPointer = Hammer.HAS_POINTEREVENTS,
702
+ isMouse = Utils.inStr(srcType, 'mouse'),
703
+ triggerType;
704
+
705
+ // if we are in a mouseevent, but there has been a touchevent triggered in this session
706
+ // we want to do nothing. simply break out of the event.
707
+ if(isMouse && self.preventMouseEvents) {
708
+ return;
709
+
710
+ // mousebutton must be down
711
+ } else if(isMouse && eventType == EVENT_START && ev.button === 0) {
712
+ self.preventMouseEvents = false;
713
+ self.shouldDetect = true;
714
+ } else if(isPointer && eventType == EVENT_START) {
715
+ self.shouldDetect = (ev.buttons === 1 || PointerEvent.matchType(POINTER_TOUCH, ev));
716
+ // just a valid start event, but no mouse
717
+ } else if(!isMouse && eventType == EVENT_START) {
718
+ self.preventMouseEvents = true;
719
+ self.shouldDetect = true;
720
+ }
721
+
722
+ // update the pointer event before entering the detection
723
+ if(isPointer && eventType != EVENT_END) {
724
+ PointerEvent.updatePointer(eventType, ev);
725
+ }
726
+
727
+ // we are in a touch/down state, so allowed detection of gestures
728
+ if(self.shouldDetect) {
729
+ triggerType = self.doDetect.call(self, ev, eventType, element, handler);
730
+ }
731
+
732
+ // ...and we are done with the detection
733
+ // so reset everything to start each detection totally fresh
734
+ if(triggerType == EVENT_END) {
735
+ self.preventMouseEvents = false;
736
+ self.shouldDetect = false;
737
+ PointerEvent.reset();
738
+ // update the pointerevent object after the detection
739
+ }
740
+
741
+ if(isPointer && eventType == EVENT_END) {
742
+ PointerEvent.updatePointer(eventType, ev);
743
+ }
744
+ };
745
+
746
+ this.on(element, EVENT_TYPES[eventType], onTouchHandler);
747
+ return onTouchHandler;
748
+ },
749
+
750
+ /**
751
+ * the core detection method
752
+ * this finds out what hammer-touch-events to trigger
753
+ * @method doDetect
754
+ * @param {Object} ev
755
+ * @param {String} eventType matches `EVENT_START|MOVE|END`
756
+ * @param {HTMLElement} element
757
+ * @param {Function} handler
758
+ * @return {String} triggerType matches `EVENT_START|MOVE|END`
759
+ */
760
+ doDetect: function doDetect(ev, eventType, element, handler) {
761
+ var touchList = this.getTouchList(ev, eventType);
762
+ var touchListLength = touchList.length;
763
+ var triggerType = eventType;
764
+ var triggerChange = touchList.trigger; // used by fakeMultitouch plugin
765
+ var changedLength = touchListLength;
766
+
767
+ // at each touchstart-like event we want also want to trigger a TOUCH event...
768
+ if(eventType == EVENT_START) {
769
+ triggerChange = EVENT_TOUCH;
770
+ // ...the same for a touchend-like event
771
+ } else if(eventType == EVENT_END) {
772
+ triggerChange = EVENT_RELEASE;
773
+
774
+ // keep track of how many touches have been removed
775
+ changedLength = touchList.length - ((ev.changedTouches) ? ev.changedTouches.length : 1);
776
+ }
777
+
778
+ // after there are still touches on the screen,
779
+ // we just want to trigger a MOVE event. so change the START or END to a MOVE
780
+ // but only after detection has been started, the first time we actualy want a START
781
+ if(changedLength > 0 && this.started) {
782
+ triggerType = EVENT_MOVE;
783
+ }
784
+
785
+ // detection has been started, we keep track of this, see above
786
+ this.started = true;
787
+
788
+ // generate some event data, some basic information
789
+ var evData = this.collectEventData(element, triggerType, touchList, ev);
790
+
791
+ // trigger the triggerType event before the change (TOUCH, RELEASE) events
792
+ // but the END event should be at last
793
+ if(eventType != EVENT_END) {
794
+ handler.call(Detection, evData);
795
+ }
796
+
797
+ // trigger a change (TOUCH, RELEASE) event, this means the length of the touches changed
798
+ if(triggerChange) {
799
+ evData.changedLength = changedLength;
800
+ evData.eventType = triggerChange;
801
+
802
+ handler.call(Detection, evData);
803
+
804
+ evData.eventType = triggerType;
805
+ delete evData.changedLength;
806
+ }
807
+
808
+ // trigger the END event
809
+ if(triggerType == EVENT_END) {
810
+ handler.call(Detection, evData);
811
+
812
+ // ...and we are done with the detection
813
+ // so reset everything to start each detection totally fresh
814
+ this.started = false;
815
+ }
816
+
817
+ return triggerType;
818
+ },
819
+
820
+ /**
821
+ * we have different events for each device/browser
822
+ * determine what we need and set them in the EVENT_TYPES constant
823
+ * the `onTouch` method is bind to these properties.
824
+ * @method determineEventTypes
825
+ * @return {Object} events
826
+ */
827
+ determineEventTypes: function determineEventTypes() {
828
+ var types;
829
+ if(Hammer.HAS_POINTEREVENTS) {
830
+ if(window.PointerEvent) {
831
+ types = [
832
+ 'pointerdown',
833
+ 'pointermove',
834
+ 'pointerup pointercancel lostpointercapture'
835
+ ];
836
+ } else {
837
+ types = [
838
+ 'MSPointerDown',
839
+ 'MSPointerMove',
840
+ 'MSPointerUp MSPointerCancel MSLostPointerCapture'
841
+ ];
842
+ }
843
+ } else if(Hammer.NO_MOUSEEVENTS) {
844
+ types = [
845
+ 'touchstart',
846
+ 'touchmove',
847
+ 'touchend touchcancel'
848
+ ];
849
+ } else {
850
+ types = [
851
+ 'touchstart mousedown',
852
+ 'touchmove mousemove',
853
+ 'touchend touchcancel mouseup'
854
+ ];
855
+ }
856
+
857
+ EVENT_TYPES[EVENT_START] = types[0];
858
+ EVENT_TYPES[EVENT_MOVE] = types[1];
859
+ EVENT_TYPES[EVENT_END] = types[2];
860
+ return EVENT_TYPES;
861
+ },
862
+
863
+ /**
864
+ * create touchList depending on the event
865
+ * @method getTouchList
866
+ * @param {Object} ev
867
+ * @param {String} eventType
868
+ * @return {Array} touches
869
+ */
870
+ getTouchList: function getTouchList(ev, eventType) {
871
+ // get the fake pointerEvent touchlist
872
+ if(Hammer.HAS_POINTEREVENTS) {
873
+ return PointerEvent.getTouchList();
874
+ }
875
+
876
+ // get the touchlist
877
+ if(ev.touches) {
878
+ if(eventType == EVENT_MOVE) {
879
+ return ev.touches;
880
+ }
881
+
882
+ var identifiers = [];
883
+ var concat = [].concat(Utils.toArray(ev.touches), Utils.toArray(ev.changedTouches));
884
+ var touchList = [];
885
+
886
+ Utils.each(concat, function(touch) {
887
+ if(Utils.inArray(identifiers, touch.identifier) === false) {
888
+ touchList.push(touch);
889
+ }
890
+ identifiers.push(touch.identifier);
891
+ });
892
+
893
+ return touchList;
894
+ }
895
+
896
+ // make fake touchList from mouse position
897
+ ev.identifier = 1;
898
+ return [ev];
899
+ },
900
+
901
+ /**
902
+ * collect basic event data
903
+ * @method collectEventData
904
+ * @param {HTMLElement} element
905
+ * @param {String} eventType matches `EVENT_START|MOVE|END`
906
+ * @param {Array} touches
907
+ * @param {Object} ev
908
+ * @return {Object} ev
909
+ */
910
+ collectEventData: function collectEventData(element, eventType, touches, ev) {
911
+ // find out pointerType
912
+ var pointerType = POINTER_TOUCH;
913
+ if(Utils.inStr(ev.type, 'mouse') || PointerEvent.matchType(POINTER_MOUSE, ev)) {
914
+ pointerType = POINTER_MOUSE;
915
+ } else if(PointerEvent.matchType(POINTER_PEN, ev)) {
916
+ pointerType = POINTER_PEN;
917
+ }
918
+
919
+ return {
920
+ center: Utils.getCenter(touches),
921
+ timeStamp: Date.now(),
922
+ target: ev.target,
923
+ touches: touches,
924
+ eventType: eventType,
925
+ pointerType: pointerType,
926
+ srcEvent: ev,
927
+
928
+ /**
929
+ * prevent the browser default actions
930
+ * mostly used to disable scrolling of the browser
931
+ */
932
+ preventDefault: function() {
933
+ var srcEvent = this.srcEvent;
934
+ srcEvent.preventManipulation && srcEvent.preventManipulation();
935
+ srcEvent.preventDefault && srcEvent.preventDefault();
936
+ },
937
+
938
+ /**
939
+ * stop bubbling the event up to its parents
940
+ */
941
+ stopPropagation: function() {
942
+ this.srcEvent.stopPropagation();
943
+ },
944
+
945
+ /**
946
+ * immediately stop gesture detection
947
+ * might be useful after a swipe was detected
948
+ * @return {*}
949
+ */
950
+ stopDetect: function() {
951
+ return Detection.stopDetect();
952
+ }
953
+ };
954
+ }
955
+ };
956
+
957
+
958
+ /**
959
+ * @module hammer
960
+ *
961
+ * @class PointerEvent
962
+ * @static
963
+ */
964
+ var PointerEvent = Hammer.PointerEvent = {
965
+ /**
966
+ * holds all pointers, by `identifier`
967
+ * @property pointers
968
+ * @type {Object}
969
+ */
970
+ pointers: {},
971
+
972
+ /**
973
+ * get the pointers as an array
974
+ * @method getTouchList
975
+ * @return {Array} touchlist
976
+ */
977
+ getTouchList: function getTouchList() {
978
+ var touchlist = [];
979
+ // we can use forEach since pointerEvents only is in IE10
980
+ Utils.each(this.pointers, function(pointer) {
981
+ touchlist.push(pointer);
982
+ });
983
+
984
+ return touchlist;
985
+ },
986
+
987
+ /**
988
+ * update the position of a pointer
989
+ * @method updatePointer
990
+ * @param {String} eventType matches `EVENT_START|MOVE|END`
991
+ * @param {Object} pointerEvent
992
+ */
993
+ updatePointer: function updatePointer(eventType, pointerEvent) {
994
+ if(eventType == EVENT_END) {
995
+ delete this.pointers[pointerEvent.pointerId];
996
+ } else {
997
+ pointerEvent.identifier = pointerEvent.pointerId;
998
+ this.pointers[pointerEvent.pointerId] = pointerEvent;
999
+ }
1000
+ },
1001
+
1002
+ /**
1003
+ * check if ev matches pointertype
1004
+ * @method matchType
1005
+ * @param {String} pointerType matches `POINTER_MOUSE|TOUCH|PEN`
1006
+ * @param {PointerEvent} ev
1007
+ */
1008
+ matchType: function matchType(pointerType, ev) {
1009
+ if(!ev.pointerType) {
1010
+ return false;
1011
+ }
1012
+
1013
+ var pt = ev.pointerType,
1014
+ types = {};
1015
+
1016
+ types[POINTER_MOUSE] = (pt === (ev.MSPOINTER_TYPE_MOUSE || POINTER_MOUSE));
1017
+ types[POINTER_TOUCH] = (pt === (ev.MSPOINTER_TYPE_TOUCH || POINTER_TOUCH));
1018
+ types[POINTER_PEN] = (pt === (ev.MSPOINTER_TYPE_PEN || POINTER_PEN));
1019
+ return types[pointerType];
1020
+ },
1021
+
1022
+ /**
1023
+ * reset the stored pointers
1024
+ * @method reset
1025
+ */
1026
+ reset: function resetList() {
1027
+ this.pointers = {};
1028
+ }
1029
+ };
1030
+
1031
+
1032
+ /**
1033
+ * @module hammer
1034
+ *
1035
+ * @class Detection
1036
+ * @static
1037
+ */
1038
+ var Detection = Hammer.detection = {
1039
+ // contains all registred Hammer.gestures in the correct order
1040
+ gestures: [],
1041
+
1042
+ // data of the current Hammer.gesture detection session
1043
+ current: null,
1044
+
1045
+ // the previous Hammer.gesture session data
1046
+ // is a full clone of the previous gesture.current object
1047
+ previous: null,
1048
+
1049
+ // when this becomes true, no gestures are fired
1050
+ stopped: false,
1051
+
1052
+ /**
1053
+ * start Hammer.gesture detection
1054
+ * @method startDetect
1055
+ * @param {Hammer.Instance} inst
1056
+ * @param {Object} eventData
1057
+ */
1058
+ startDetect: function startDetect(inst, eventData) {
1059
+ // already busy with a Hammer.gesture detection on an element
1060
+ if(this.current) {
1061
+ return;
1062
+ }
1063
+
1064
+ this.stopped = false;
1065
+
1066
+ // holds current session
1067
+ this.current = {
1068
+ inst: inst, // reference to HammerInstance we're working for
1069
+ startEvent: Utils.extend({}, eventData), // start eventData for distances, timing etc
1070
+ lastEvent: false, // last eventData
1071
+ lastCalcEvent: false, // last eventData for calculations.
1072
+ futureCalcEvent: false, // last eventData for calculations.
1073
+ lastCalcData: {}, // last lastCalcData
1074
+ name: '' // current gesture we're in/detected, can be 'tap', 'hold' etc
1075
+ };
1076
+
1077
+ this.detect(eventData);
1078
+ },
1079
+
1080
+ /**
1081
+ * Hammer.gesture detection
1082
+ * @method detect
1083
+ * @param {Object} eventData
1084
+ * @return {any}
1085
+ */
1086
+ detect: function detect(eventData) {
1087
+ if(!this.current || this.stopped) {
1088
+ return;
1089
+ }
1090
+
1091
+ // extend event data with calculations about scale, distance etc
1092
+ eventData = this.extendEventData(eventData);
1093
+
1094
+ // hammer instance and instance options
1095
+ var inst = this.current.inst,
1096
+ instOptions = inst.options;
1097
+
1098
+ // call Hammer.gesture handlers
1099
+ Utils.each(this.gestures, function triggerGesture(gesture) {
1100
+ // only when the instance options have enabled this gesture
1101
+ if(!this.stopped && inst.enabled && instOptions[gesture.name]) {
1102
+ gesture.handler.call(gesture, eventData, inst);
1103
+ }
1104
+ }, this);
1105
+
1106
+ // store as previous event event
1107
+ if(this.current) {
1108
+ this.current.lastEvent = eventData;
1109
+ }
1110
+
1111
+ if(eventData.eventType == EVENT_END) {
1112
+ this.stopDetect();
1113
+ }
1114
+
1115
+ return eventData;
1116
+ },
1117
+
1118
+ /**
1119
+ * clear the Hammer.gesture vars
1120
+ * this is called on endDetect, but can also be used when a final Hammer.gesture has been detected
1121
+ * to stop other Hammer.gestures from being fired
1122
+ * @method stopDetect
1123
+ */
1124
+ stopDetect: function stopDetect() {
1125
+ // clone current data to the store as the previous gesture
1126
+ // used for the double tap gesture, since this is an other gesture detect session
1127
+ this.previous = Utils.extend({}, this.current);
1128
+
1129
+ // reset the current
1130
+ this.current = null;
1131
+ this.stopped = true;
1132
+ },
1133
+
1134
+ /**
1135
+ * calculate velocity, angle and direction
1136
+ * @method getVelocityData
1137
+ * @param {Object} ev
1138
+ * @param {Object} center
1139
+ * @param {Number} deltaTime
1140
+ * @param {Number} deltaX
1141
+ * @param {Number} deltaY
1142
+ */
1143
+ getCalculatedData: function getCalculatedData(ev, center, deltaTime, deltaX, deltaY) {
1144
+ var cur = this.current,
1145
+ recalc = false,
1146
+ calcEv = cur.lastCalcEvent,
1147
+ calcData = cur.lastCalcData;
1148
+
1149
+ if(calcEv && ev.timeStamp - calcEv.timeStamp > Hammer.CALCULATE_INTERVAL) {
1150
+ center = calcEv.center;
1151
+ deltaTime = ev.timeStamp - calcEv.timeStamp;
1152
+ deltaX = ev.center.clientX - calcEv.center.clientX;
1153
+ deltaY = ev.center.clientY - calcEv.center.clientY;
1154
+ recalc = true;
1155
+ }
1156
+
1157
+ if(ev.eventType == EVENT_TOUCH || ev.eventType == EVENT_RELEASE) {
1158
+ cur.futureCalcEvent = ev;
1159
+ }
1160
+
1161
+ if(!cur.lastCalcEvent || recalc) {
1162
+ calcData.velocity = Utils.getVelocity(deltaTime, deltaX, deltaY);
1163
+ calcData.angle = Utils.getAngle(center, ev.center);
1164
+ calcData.direction = Utils.getDirection(center, ev.center);
1165
+
1166
+ cur.lastCalcEvent = cur.futureCalcEvent || ev;
1167
+ cur.futureCalcEvent = ev;
1168
+ }
1169
+
1170
+ ev.velocityX = calcData.velocity.x;
1171
+ ev.velocityY = calcData.velocity.y;
1172
+ ev.interimAngle = calcData.angle;
1173
+ ev.interimDirection = calcData.direction;
1174
+ },
1175
+
1176
+ /**
1177
+ * extend eventData for Hammer.gestures
1178
+ * @method extendEventData
1179
+ * @param {Object} ev
1180
+ * @return {Object} ev
1181
+ */
1182
+ extendEventData: function extendEventData(ev) {
1183
+ var cur = this.current,
1184
+ startEv = cur.startEvent,
1185
+ lastEv = cur.lastEvent || startEv;
1186
+
1187
+ // update the start touchlist to calculate the scale/rotation
1188
+ if(ev.eventType == EVENT_TOUCH || ev.eventType == EVENT_RELEASE) {
1189
+ startEv.touches = [];
1190
+ Utils.each(ev.touches, function(touch) {
1191
+ startEv.touches.push({
1192
+ clientX: touch.clientX,
1193
+ clientY: touch.clientY
1194
+ });
1195
+ });
1196
+ }
1197
+
1198
+ var deltaTime = ev.timeStamp - startEv.timeStamp,
1199
+ deltaX = ev.center.clientX - startEv.center.clientX,
1200
+ deltaY = ev.center.clientY - startEv.center.clientY;
1201
+
1202
+ this.getCalculatedData(ev, lastEv.center, deltaTime, deltaX, deltaY);
1203
+
1204
+ Utils.extend(ev, {
1205
+ startEvent: startEv,
1206
+
1207
+ deltaTime: deltaTime,
1208
+ deltaX: deltaX,
1209
+ deltaY: deltaY,
1210
+
1211
+ distance: Utils.getDistance(startEv.center, ev.center),
1212
+ angle: Utils.getAngle(startEv.center, ev.center),
1213
+ direction: Utils.getDirection(startEv.center, ev.center),
1214
+ scale: Utils.getScale(startEv.touches, ev.touches),
1215
+ rotation: Utils.getRotation(startEv.touches, ev.touches)
1216
+ });
1217
+
1218
+ return ev;
1219
+ },
1220
+
1221
+ /**
1222
+ * register new gesture
1223
+ * @method register
1224
+ * @param {Object} gesture object, see `gestures/` for documentation
1225
+ * @return {Array} gestures
1226
+ */
1227
+ register: function register(gesture) {
1228
+ // add an enable gesture options if there is no given
1229
+ var options = gesture.defaults || {};
1230
+ if(options[gesture.name] === undefined) {
1231
+ options[gesture.name] = true;
1232
+ }
1233
+
1234
+ // extend Hammer default options with the Hammer.gesture options
1235
+ Utils.extend(Hammer.defaults, options, true);
1236
+
1237
+ // set its index
1238
+ gesture.index = gesture.index || 1000;
1239
+
1240
+ // add Hammer.gesture to the list
1241
+ this.gestures.push(gesture);
1242
+
1243
+ // sort the list by index
1244
+ this.gestures.sort(function(a, b) {
1245
+ if(a.index < b.index) {
1246
+ return -1;
1247
+ }
1248
+ if(a.index > b.index) {
1249
+ return 1;
1250
+ }
1251
+ return 0;
1252
+ });
1253
+
1254
+ return this.gestures;
1255
+ }
1256
+ };
1257
+
1258
+
1259
+ /**
1260
+ * @module hammer
1261
+ */
1262
+
1263
+ /**
1264
+ * create new hammer instance
1265
+ * all methods should return the instance itself, so it is chainable.
1266
+ *
1267
+ * @class Instance
1268
+ * @constructor
1269
+ * @param {HTMLElement} element
1270
+ * @param {Object} [options={}] options are merged with `Hammer.defaults`
1271
+ * @return {Hammer.Instance}
1272
+ */
1273
+ Hammer.Instance = function(element, options) {
1274
+ var self = this;
1275
+
1276
+ // setup HammerJS window events and register all gestures
1277
+ // this also sets up the default options
1278
+ setup();
1279
+
1280
+ /**
1281
+ * @property element
1282
+ * @type {HTMLElement}
1283
+ */
1284
+ this.element = element;
1285
+
1286
+ /**
1287
+ * @property enabled
1288
+ * @type {Boolean}
1289
+ * @protected
1290
+ */
1291
+ this.enabled = true;
1292
+
1293
+ /**
1294
+ * options, merged with the defaults
1295
+ * options with an _ are converted to camelCase
1296
+ * @property options
1297
+ * @type {Object}
1298
+ */
1299
+ Utils.each(options, function(value, name) {
1300
+ delete options[name];
1301
+ options[Utils.toCamelCase(name)] = value;
1302
+ });
1303
+
1304
+ this.options = Utils.extend(Utils.extend({}, Hammer.defaults), options || {});
1305
+
1306
+ // add some css to the element to prevent the browser from doing its native behavoir
1307
+ if(this.options.behavior) {
1308
+ Utils.toggleBehavior(this.element, this.options.behavior, true);
1309
+ }
1310
+
1311
+ /**
1312
+ * event start handler on the element to start the detection
1313
+ * @property eventStartHandler
1314
+ * @type {Object}
1315
+ */
1316
+ this.eventStartHandler = Event.onTouch(element, EVENT_START, function(ev) {
1317
+ if(self.enabled && ev.eventType == EVENT_START) {
1318
+ Detection.startDetect(self, ev);
1319
+ } else if(ev.eventType == EVENT_TOUCH) {
1320
+ Detection.detect(ev);
1321
+ }
1322
+ });
1323
+
1324
+ /**
1325
+ * keep a list of user event handlers which needs to be removed when calling 'dispose'
1326
+ * @property eventHandlers
1327
+ * @type {Array}
1328
+ */
1329
+ this.eventHandlers = [];
1330
+ };
1331
+
1332
+ Hammer.Instance.prototype = {
1333
+ /**
1334
+ * bind events to the instance
1335
+ * @method on
1336
+ * @chainable
1337
+ * @param {String} gestures multiple gestures by splitting with a space
1338
+ * @param {Function} handler
1339
+ * @param {Object} handler.ev event object
1340
+ */
1341
+ on: function onEvent(gestures, handler) {
1342
+ var self = this;
1343
+ Event.on(self.element, gestures, handler, function(type) {
1344
+ self.eventHandlers.push({ gesture: type, handler: handler });
1345
+ });
1346
+ return self;
1347
+ },
1348
+
1349
+ /**
1350
+ * unbind events to the instance
1351
+ * @method off
1352
+ * @chainable
1353
+ * @param {String} gestures
1354
+ * @param {Function} handler
1355
+ */
1356
+ off: function offEvent(gestures, handler) {
1357
+ var self = this;
1358
+
1359
+ Event.off(self.element, gestures, handler, function(type) {
1360
+ var index = Utils.inArray({ gesture: type, handler: handler });
1361
+ if(index !== false) {
1362
+ self.eventHandlers.splice(index, 1);
1363
+ }
1364
+ });
1365
+ return self;
1366
+ },
1367
+
1368
+ /**
1369
+ * trigger gesture event
1370
+ * @method trigger
1371
+ * @chainable
1372
+ * @param {String} gesture
1373
+ * @param {Object} [eventData]
1374
+ */
1375
+ trigger: function triggerEvent(gesture, eventData) {
1376
+ // optional
1377
+ if(!eventData) {
1378
+ eventData = {};
1379
+ }
1380
+
1381
+ // create DOM event
1382
+ var event = Hammer.DOCUMENT.createEvent('Event');
1383
+ event.initEvent(gesture, true, true);
1384
+ event.gesture = eventData;
1385
+
1386
+ // trigger on the target if it is in the instance element,
1387
+ // this is for event delegation tricks
1388
+ var element = this.element;
1389
+ if(Utils.hasParent(eventData.target, element)) {
1390
+ element = eventData.target;
1391
+ }
1392
+
1393
+ element.dispatchEvent(event);
1394
+ return this;
1395
+ },
1396
+
1397
+ /**
1398
+ * enable of disable hammer.js detection
1399
+ * @method enable
1400
+ * @chainable
1401
+ * @param {Boolean} state
1402
+ */
1403
+ enable: function enable(state) {
1404
+ this.enabled = state;
1405
+ return this;
1406
+ },
1407
+
1408
+ /**
1409
+ * dispose this hammer instance
1410
+ * @method dispose
1411
+ * @return {Null}
1412
+ */
1413
+ dispose: function dispose() {
1414
+ var i, eh;
1415
+
1416
+ // undo all changes made by stop_browser_behavior
1417
+ Utils.toggleBehavior(this.element, this.options.behavior, false);
1418
+
1419
+ // unbind all custom event handlers
1420
+ for(i = -1; (eh = this.eventHandlers[++i]);) {
1421
+ Utils.off(this.element, eh.gesture, eh.handler);
1422
+ }
1423
+
1424
+ this.eventHandlers = [];
1425
+
1426
+ // unbind the start event listener
1427
+ Event.off(this.element, EVENT_TYPES[EVENT_START], this.eventStartHandler);
1428
+
1429
+ return null;
1430
+ }
1431
+ };
1432
+
1433
+
1434
+ /**
1435
+ * @module gestures
1436
+ */
1437
+ /**
1438
+ * Move with x fingers (default 1) around on the page.
1439
+ * Preventing the default browser behavior is a good way to improve feel and working.
1440
+ * ````
1441
+ * hammertime.on("drag", function(ev) {
1442
+ * console.log(ev);
1443
+ * ev.gesture.preventDefault();
1444
+ * });
1445
+ * ````
1446
+ *
1447
+ * @class Drag
1448
+ * @static
1449
+ */
1450
+ /**
1451
+ * @event drag
1452
+ * @param {Object} ev
1453
+ */
1454
+ /**
1455
+ * @event dragstart
1456
+ * @param {Object} ev
1457
+ */
1458
+ /**
1459
+ * @event dragend
1460
+ * @param {Object} ev
1461
+ */
1462
+ /**
1463
+ * @event drapleft
1464
+ * @param {Object} ev
1465
+ */
1466
+ /**
1467
+ * @event dragright
1468
+ * @param {Object} ev
1469
+ */
1470
+ /**
1471
+ * @event dragup
1472
+ * @param {Object} ev
1473
+ */
1474
+ /**
1475
+ * @event dragdown
1476
+ * @param {Object} ev
1477
+ */
1478
+
1479
+ /**
1480
+ * @param {String} name
1481
+ */
1482
+ (function(name) {
1483
+ var triggered = false;
1484
+
1485
+ function dragGesture(ev, inst) {
1486
+ var cur = Detection.current;
1487
+
1488
+ // max touches
1489
+ if(inst.options.dragMaxTouches > 0 &&
1490
+ ev.touches.length > inst.options.dragMaxTouches) {
1491
+ return;
1492
+ }
1493
+
1494
+ switch(ev.eventType) {
1495
+ case EVENT_START:
1496
+ triggered = false;
1497
+ break;
1498
+
1499
+ case EVENT_MOVE:
1500
+ // when the distance we moved is too small we skip this gesture
1501
+ // or we can be already in dragging
1502
+ if(ev.distance < inst.options.dragMinDistance &&
1503
+ cur.name != name) {
1504
+ return;
1505
+ }
1506
+
1507
+ var startCenter = cur.startEvent.center;
1508
+
1509
+ // we are dragging!
1510
+ if(cur.name != name) {
1511
+ cur.name = name;
1512
+ if(inst.options.dragDistanceCorrection && ev.distance > 0) {
1513
+ // When a drag is triggered, set the event center to dragMinDistance pixels from the original event center.
1514
+ // Without this correction, the dragged distance would jumpstart at dragMinDistance pixels instead of at 0.
1515
+ // It might be useful to save the original start point somewhere
1516
+ var factor = Math.abs(inst.options.dragMinDistance / ev.distance);
1517
+ startCenter.pageX += ev.deltaX * factor;
1518
+ startCenter.pageY += ev.deltaY * factor;
1519
+ startCenter.clientX += ev.deltaX * factor;
1520
+ startCenter.clientY += ev.deltaY * factor;
1521
+
1522
+ // recalculate event data using new start point
1523
+ ev = Detection.extendEventData(ev);
1524
+ }
1525
+ }
1526
+
1527
+ // lock drag to axis?
1528
+ if(cur.lastEvent.dragLockToAxis ||
1529
+ ( inst.options.dragLockToAxis &&
1530
+ inst.options.dragLockMinDistance <= ev.distance
1531
+ )) {
1532
+ ev.dragLockToAxis = true;
1533
+ }
1534
+
1535
+ // keep direction on the axis that the drag gesture started on
1536
+ var lastDirection = cur.lastEvent.direction;
1537
+ if(ev.dragLockToAxis && lastDirection !== ev.direction) {
1538
+ if(Utils.isVertical(lastDirection)) {
1539
+ ev.direction = (ev.deltaY < 0) ? DIRECTION_UP : DIRECTION_DOWN;
1540
+ } else {
1541
+ ev.direction = (ev.deltaX < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
1542
+ }
1543
+ }
1544
+
1545
+ // first time, trigger dragstart event
1546
+ if(!triggered) {
1547
+ inst.trigger(name + 'start', ev);
1548
+ triggered = true;
1549
+ }
1550
+
1551
+ // trigger events
1552
+ inst.trigger(name, ev);
1553
+ inst.trigger(name + ev.direction, ev);
1554
+
1555
+ var isVertical = Utils.isVertical(ev.direction);
1556
+
1557
+ // block the browser events
1558
+ if((inst.options.dragBlockVertical && isVertical) ||
1559
+ (inst.options.dragBlockHorizontal && !isVertical)) {
1560
+ ev.preventDefault();
1561
+ }
1562
+ break;
1563
+
1564
+ case EVENT_RELEASE:
1565
+ if(triggered && ev.changedLength <= inst.options.dragMaxTouches) {
1566
+ inst.trigger(name + 'end', ev);
1567
+ triggered = false;
1568
+ }
1569
+ break;
1570
+
1571
+ case EVENT_END:
1572
+ triggered = false;
1573
+ break;
1574
+ }
1575
+ }
1576
+
1577
+ Hammer.gestures.Drag = {
1578
+ name: name,
1579
+ index: 50,
1580
+ handler: dragGesture,
1581
+ defaults: {
1582
+ /**
1583
+ * minimal movement that have to be made before the drag event gets triggered
1584
+ * @property dragMinDistance
1585
+ * @type {Number}
1586
+ * @default 10
1587
+ */
1588
+ dragMinDistance: 10,
1589
+
1590
+ /**
1591
+ * Set dragDistanceCorrection to true to make the starting point of the drag
1592
+ * be calculated from where the drag was triggered, not from where the touch started.
1593
+ * Useful to avoid a jerk-starting drag, which can make fine-adjustments
1594
+ * through dragging difficult, and be visually unappealing.
1595
+ * @property dragDistanceCorrection
1596
+ * @type {Boolean}
1597
+ * @default true
1598
+ */
1599
+ dragDistanceCorrection: true,
1600
+
1601
+ /**
1602
+ * set 0 for unlimited, but this can conflict with transform
1603
+ * @property dragMaxTouches
1604
+ * @type {Number}
1605
+ * @default 1
1606
+ */
1607
+ dragMaxTouches: 1,
1608
+
1609
+ /**
1610
+ * prevent default browser behavior when dragging occurs
1611
+ * be careful with it, it makes the element a blocking element
1612
+ * when you are using the drag gesture, it is a good practice to set this true
1613
+ * @property dragBlockHorizontal
1614
+ * @type {Boolean}
1615
+ * @default false
1616
+ */
1617
+ dragBlockHorizontal: false,
1618
+
1619
+ /**
1620
+ * same as `dragBlockHorizontal`, but for vertical movement
1621
+ * @property dragBlockVertical
1622
+ * @type {Boolean}
1623
+ * @default false
1624
+ */
1625
+ dragBlockVertical: false,
1626
+
1627
+ /**
1628
+ * dragLockToAxis keeps the drag gesture on the axis that it started on,
1629
+ * It disallows vertical directions if the initial direction was horizontal, and vice versa.
1630
+ * @property dragLockToAxis
1631
+ * @type {Boolean}
1632
+ * @default false
1633
+ */
1634
+ dragLockToAxis: false,
1635
+
1636
+ /**
1637
+ * drag lock only kicks in when distance > dragLockMinDistance
1638
+ * This way, locking occurs only when the distance has become large enough to reliably determine the direction
1639
+ * @property dragLockMinDistance
1640
+ * @type {Number}
1641
+ * @default 25
1642
+ */
1643
+ dragLockMinDistance: 25
1644
+ }
1645
+ };
1646
+ })('drag');
1647
+
1648
+ /**
1649
+ * @module gestures
1650
+ */
1651
+ /**
1652
+ * trigger a simple gesture event, so you can do anything in your handler.
1653
+ * only usable if you know what your doing...
1654
+ *
1655
+ * @class Gesture
1656
+ * @static
1657
+ */
1658
+ /**
1659
+ * @event gesture
1660
+ * @param {Object} ev
1661
+ */
1662
+ Hammer.gestures.Gesture = {
1663
+ name: 'gesture',
1664
+ index: 1337,
1665
+ handler: function releaseGesture(ev, inst) {
1666
+ inst.trigger(this.name, ev);
1667
+ }
1668
+ };
1669
+
1670
+ /**
1671
+ * @module gestures
1672
+ */
1673
+ /**
1674
+ * Touch stays at the same place for x time
1675
+ *
1676
+ * @class Hold
1677
+ * @static
1678
+ */
1679
+ /**
1680
+ * @event hold
1681
+ * @param {Object} ev
1682
+ */
1683
+
1684
+ /**
1685
+ * @param {String} name
1686
+ */
1687
+ (function(name) {
1688
+ var timer;
1689
+
1690
+ function holdGesture(ev, inst) {
1691
+ var options = inst.options,
1692
+ current = Detection.current;
1693
+
1694
+ switch(ev.eventType) {
1695
+ case EVENT_START:
1696
+ clearTimeout(timer);
1697
+
1698
+ // set the gesture so we can check in the timeout if it still is
1699
+ current.name = name;
1700
+
1701
+ // set timer and if after the timeout it still is hold,
1702
+ // we trigger the hold event
1703
+ timer = setTimeout(function() {
1704
+ if(current && current.name == name) {
1705
+ inst.trigger(name, ev);
1706
+ }
1707
+ }, options.holdTimeout);
1708
+ break;
1709
+
1710
+ case EVENT_MOVE:
1711
+ if(ev.distance > options.holdThreshold) {
1712
+ clearTimeout(timer);
1713
+ }
1714
+ break;
1715
+
1716
+ case EVENT_RELEASE:
1717
+ clearTimeout(timer);
1718
+ break;
1719
+ }
1720
+ }
1721
+
1722
+ Hammer.gestures.Hold = {
1723
+ name: name,
1724
+ index: 10,
1725
+ defaults: {
1726
+ /**
1727
+ * @property holdTimeout
1728
+ * @type {Number}
1729
+ * @default 500
1730
+ */
1731
+ holdTimeout: 500,
1732
+
1733
+ /**
1734
+ * movement allowed while holding
1735
+ * @property holdThreshold
1736
+ * @type {Number}
1737
+ * @default 2
1738
+ */
1739
+ holdThreshold: 2
1740
+ },
1741
+ handler: holdGesture
1742
+ };
1743
+ })('hold');
1744
+
1745
+ /**
1746
+ * @module gestures
1747
+ */
1748
+ /**
1749
+ * when a touch is being released from the page
1750
+ *
1751
+ * @class Release
1752
+ * @static
1753
+ */
1754
+ /**
1755
+ * @event release
1756
+ * @param {Object} ev
1757
+ */
1758
+ Hammer.gestures.Release = {
1759
+ name: 'release',
1760
+ index: Infinity,
1761
+ handler: function releaseGesture(ev, inst) {
1762
+ if(ev.eventType == EVENT_RELEASE) {
1763
+ inst.trigger(this.name, ev);
1764
+ }
1765
+ }
1766
+ };
1767
+
1768
+ /**
1769
+ * @module gestures
1770
+ */
1771
+ /**
1772
+ * triggers swipe events when the end velocity is above the threshold
1773
+ * for best usage, set `preventDefault` (on the drag gesture) to `true`
1774
+ * ````
1775
+ * hammertime.on("dragleft swipeleft", function(ev) {
1776
+ * console.log(ev);
1777
+ * ev.gesture.preventDefault();
1778
+ * });
1779
+ * ````
1780
+ *
1781
+ * @class Swipe
1782
+ * @static
1783
+ */
1784
+ /**
1785
+ * @event swipe
1786
+ * @param {Object} ev
1787
+ */
1788
+ /**
1789
+ * @event swipeleft
1790
+ * @param {Object} ev
1791
+ */
1792
+ /**
1793
+ * @event swiperight
1794
+ * @param {Object} ev
1795
+ */
1796
+ /**
1797
+ * @event swipeup
1798
+ * @param {Object} ev
1799
+ */
1800
+ /**
1801
+ * @event swipedown
1802
+ * @param {Object} ev
1803
+ */
1804
+ Hammer.gestures.Swipe = {
1805
+ name: 'swipe',
1806
+ index: 40,
1807
+ defaults: {
1808
+ /**
1809
+ * @property swipeMinTouches
1810
+ * @type {Number}
1811
+ * @default 1
1812
+ */
1813
+ swipeMinTouches: 1,
1814
+
1815
+ /**
1816
+ * @property swipeMaxTouches
1817
+ * @type {Number}
1818
+ * @default 1
1819
+ */
1820
+ swipeMaxTouches: 1,
1821
+
1822
+ /**
1823
+ * horizontal swipe velocity
1824
+ * @property swipeVelocityX
1825
+ * @type {Number}
1826
+ * @default 0.6
1827
+ */
1828
+ swipeVelocityX: 0.6,
1829
+
1830
+ /**
1831
+ * vertical swipe velocity
1832
+ * @property swipeVelocityY
1833
+ * @type {Number}
1834
+ * @default 0.6
1835
+ */
1836
+ swipeVelocityY: 0.6
1837
+ },
1838
+
1839
+ handler: function swipeGesture(ev, inst) {
1840
+ if(ev.eventType == EVENT_RELEASE) {
1841
+ var touches = ev.touches.length,
1842
+ options = inst.options;
1843
+
1844
+ // max touches
1845
+ if(touches < options.swipeMinTouches ||
1846
+ touches > options.swipeMaxTouches) {
1847
+ return;
1848
+ }
1849
+
1850
+ // when the distance we moved is too small we skip this gesture
1851
+ // or we can be already in dragging
1852
+ if(ev.velocityX > options.swipeVelocityX ||
1853
+ ev.velocityY > options.swipeVelocityY) {
1854
+ // trigger swipe events
1855
+ inst.trigger(this.name, ev);
1856
+ inst.trigger(this.name + ev.direction, ev);
1857
+ }
1858
+ }
1859
+ }
1860
+ };
1861
+
1862
+ /**
1863
+ * @module gestures
1864
+ */
1865
+ /**
1866
+ * Single tap and a double tap on a place
1867
+ *
1868
+ * @class Tap
1869
+ * @static
1870
+ */
1871
+ /**
1872
+ * @event tap
1873
+ * @param {Object} ev
1874
+ */
1875
+ /**
1876
+ * @event doubletap
1877
+ * @param {Object} ev
1878
+ */
1879
+
1880
+ /**
1881
+ * @param {String} name
1882
+ */
1883
+ (function(name) {
1884
+ var hasMoved = false;
1885
+
1886
+ function tapGesture(ev, inst) {
1887
+ var options = inst.options,
1888
+ current = Detection.current,
1889
+ prev = Detection.previous,
1890
+ sincePrev,
1891
+ didDoubleTap;
1892
+
1893
+ switch(ev.eventType) {
1894
+ case EVENT_START:
1895
+ hasMoved = false;
1896
+ break;
1897
+
1898
+ case EVENT_MOVE:
1899
+ hasMoved = hasMoved || (ev.distance > options.tapMaxDistance);
1900
+ break;
1901
+
1902
+ case EVENT_END:
1903
+ if(!Utils.inStr(ev.srcEvent.type, 'cancel') && ev.deltaTime < options.tapMaxTime && !hasMoved) {
1904
+ // previous gesture, for the double tap since these are two different gesture detections
1905
+ sincePrev = prev && prev.lastEvent && ev.timeStamp - prev.lastEvent.timeStamp;
1906
+ didDoubleTap = false;
1907
+
1908
+ // check if double tap
1909
+ if(prev && prev.name == name &&
1910
+ (sincePrev && sincePrev < options.doubleTapInterval) &&
1911
+ ev.distance < options.doubleTapDistance) {
1912
+ inst.trigger('doubletap', ev);
1913
+ didDoubleTap = true;
1914
+ }
1915
+
1916
+ // do a single tap
1917
+ if(!didDoubleTap || options.tapAlways) {
1918
+ current.name = name;
1919
+ inst.trigger(current.name, ev);
1920
+ }
1921
+ }
1922
+ break;
1923
+ }
1924
+ }
1925
+
1926
+ Hammer.gestures.Tap = {
1927
+ name: name,
1928
+ index: 100,
1929
+ handler: tapGesture,
1930
+ defaults: {
1931
+ /**
1932
+ * max time of a tap, this is for the slow tappers
1933
+ * @property tapMaxTime
1934
+ * @type {Number}
1935
+ * @default 250
1936
+ */
1937
+ tapMaxTime: 250,
1938
+
1939
+ /**
1940
+ * max distance of movement of a tap, this is for the slow tappers
1941
+ * @property tapMaxDistance
1942
+ * @type {Number}
1943
+ * @default 10
1944
+ */
1945
+ tapMaxDistance: 10,
1946
+
1947
+ /**
1948
+ * always trigger the `tap` event, even while double-tapping
1949
+ * @property tapAlways
1950
+ * @type {Boolean}
1951
+ * @default true
1952
+ */
1953
+ tapAlways: true,
1954
+
1955
+ /**
1956
+ * max distance between two taps
1957
+ * @property doubleTapDistance
1958
+ * @type {Number}
1959
+ * @default 20
1960
+ */
1961
+ doubleTapDistance: 20,
1962
+
1963
+ /**
1964
+ * max time between two taps
1965
+ * @property doubleTapInterval
1966
+ * @type {Number}
1967
+ * @default 300
1968
+ */
1969
+ doubleTapInterval: 300
1970
+ }
1971
+ };
1972
+ })('tap');
1973
+
1974
+ /**
1975
+ * @module gestures
1976
+ */
1977
+ /**
1978
+ * when a touch is being touched at the page
1979
+ *
1980
+ * @class Touch
1981
+ * @static
1982
+ */
1983
+ /**
1984
+ * @event touch
1985
+ * @param {Object} ev
1986
+ */
1987
+ Hammer.gestures.Touch = {
1988
+ name: 'touch',
1989
+ index: -Infinity,
1990
+ defaults: {
1991
+ /**
1992
+ * call preventDefault at touchstart, and makes the element blocking by disabling the scrolling of the page,
1993
+ * but it improves gestures like transforming and dragging.
1994
+ * be careful with using this, it can be very annoying for users to be stuck on the page
1995
+ * @property preventDefault
1996
+ * @type {Boolean}
1997
+ * @default false
1998
+ */
1999
+ preventDefault: false,
2000
+
2001
+ /**
2002
+ * disable mouse events, so only touch (or pen!) input triggers events
2003
+ * @property preventMouse
2004
+ * @type {Boolean}
2005
+ * @default false
2006
+ */
2007
+ preventMouse: false
2008
+ },
2009
+ handler: function touchGesture(ev, inst) {
2010
+ if(inst.options.preventMouse && ev.pointerType == POINTER_MOUSE) {
2011
+ ev.stopDetect();
2012
+ return;
2013
+ }
2014
+
2015
+ if(inst.options.preventDefault) {
2016
+ ev.preventDefault();
2017
+ }
2018
+
2019
+ if(ev.eventType == EVENT_TOUCH) {
2020
+ inst.trigger('touch', ev);
2021
+ }
2022
+ }
2023
+ };
2024
+
2025
+ /**
2026
+ * @module gestures
2027
+ */
2028
+ /**
2029
+ * User want to scale or rotate with 2 fingers
2030
+ * Preventing the default browser behavior is a good way to improve feel and working. This can be done with the
2031
+ * `preventDefault` option.
2032
+ *
2033
+ * @class Transform
2034
+ * @static
2035
+ */
2036
+ /**
2037
+ * @event transform
2038
+ * @param {Object} ev
2039
+ */
2040
+ /**
2041
+ * @event transformstart
2042
+ * @param {Object} ev
2043
+ */
2044
+ /**
2045
+ * @event transformend
2046
+ * @param {Object} ev
2047
+ */
2048
+ /**
2049
+ * @event pinchin
2050
+ * @param {Object} ev
2051
+ */
2052
+ /**
2053
+ * @event pinchout
2054
+ * @param {Object} ev
2055
+ */
2056
+ /**
2057
+ * @event rotate
2058
+ * @param {Object} ev
2059
+ */
2060
+
2061
+ /**
2062
+ * @param {String} name
2063
+ */
2064
+ (function(name) {
2065
+ var triggered = false;
2066
+
2067
+ function transformGesture(ev, inst) {
2068
+ switch(ev.eventType) {
2069
+ case EVENT_START:
2070
+ triggered = false;
2071
+ break;
2072
+
2073
+ case EVENT_MOVE:
2074
+ // at least multitouch
2075
+ if(ev.touches.length < 2) {
2076
+ return;
2077
+ }
2078
+
2079
+ var scaleThreshold = Math.abs(1 - ev.scale);
2080
+ var rotationThreshold = Math.abs(ev.rotation);
2081
+
2082
+ // when the distance we moved is too small we skip this gesture
2083
+ // or we can be already in dragging
2084
+ if(scaleThreshold < inst.options.transformMinScale &&
2085
+ rotationThreshold < inst.options.transformMinRotation) {
2086
+ return;
2087
+ }
2088
+
2089
+ // we are transforming!
2090
+ Detection.current.name = name;
2091
+
2092
+ // first time, trigger dragstart event
2093
+ if(!triggered) {
2094
+ inst.trigger(name + 'start', ev);
2095
+ triggered = true;
2096
+ }
2097
+
2098
+ inst.trigger(name, ev); // basic transform event
2099
+
2100
+ // trigger rotate event
2101
+ if(rotationThreshold > inst.options.transformMinRotation) {
2102
+ inst.trigger('rotate', ev);
2103
+ }
2104
+
2105
+ // trigger pinch event
2106
+ if(scaleThreshold > inst.options.transformMinScale) {
2107
+ inst.trigger('pinch', ev);
2108
+ inst.trigger('pinch' + (ev.scale < 1 ? 'in' : 'out'), ev);
2109
+ }
2110
+ break;
2111
+
2112
+ case EVENT_RELEASE:
2113
+ if(triggered && ev.changedLength < 2) {
2114
+ inst.trigger(name + 'end', ev);
2115
+ triggered = false;
2116
+ }
2117
+ break;
2118
+ }
2119
+ }
2120
+
2121
+ Hammer.gestures.Transform = {
2122
+ name: name,
2123
+ index: 45,
2124
+ defaults: {
2125
+ /**
2126
+ * minimal scale factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1
2127
+ * @property transformMinScale
2128
+ * @type {Number}
2129
+ * @default 0.01
2130
+ */
2131
+ transformMinScale: 0.01,
2132
+
2133
+ /**
2134
+ * rotation in degrees
2135
+ * @property transformMinRotation
2136
+ * @type {Number}
2137
+ * @default 1
2138
+ */
2139
+ transformMinRotation: 1
2140
+ },
2141
+
2142
+ handler: transformGesture
2143
+ };
2144
+ })('transform');
2145
+
2146
+ /**
2147
+ * @module hammer
2148
+ */
2149
+
2150
+ // AMD export
2151
+ if(typeof define == 'function' && define.amd) {
2152
+ define(function() {
2153
+ return Hammer;
2154
+ });
2155
+ // commonjs export
2156
+ } else if(typeof module !== 'undefined' && module.exports) {
2157
+ module.exports = Hammer;
2158
+ // browser export
2159
+ } else {
2160
+ window.Hammer = Hammer;
2161
+ }
2162
+
2163
+ })(window);