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