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