hammerjs-rails 0.2.0 → 0.3.0

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