hammerjs-rails 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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);