1/*! 2 * PEP v0.4.3 | https://github.com/jquery/PEP 3 * Copyright jQuery Foundation and other contributors | http://jquery.org/license 4 */ 5 6(function (global, factory) { 7 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 8 typeof define === 'function' && define.amd ? define(factory) : 9 (global.PointerEventsPolyfill = factory()); 10}(this, function () { 'use strict'; 11 12 /** 13 * This is the constructor for new PointerEvents. 14 * 15 * New Pointer Events must be given a type, and an optional dictionary of 16 * initialization properties. 17 * 18 * Due to certain platform requirements, events returned from the constructor 19 * identify as MouseEvents. 20 * 21 * @constructor 22 * @param {String} inType The type of the event to create. 23 * @param {Object} [inDict] An optional dictionary of initial event properties. 24 * @return {Event} A new PointerEvent of type `inType`, initialized with properties from `inDict`. 25 */ 26 var MOUSE_PROPS = [ 27 'bubbles', 28 'cancelable', 29 'view', 30 'detail', 31 'screenX', 32 'screenY', 33 'clientX', 34 'clientY', 35 'ctrlKey', 36 'altKey', 37 'shiftKey', 38 'metaKey', 39 'button', 40 'relatedTarget', 41 'pageX', 42 'pageY' 43 ]; 44 45 var MOUSE_DEFAULTS = [ 46 false, 47 false, 48 null, 49 null, 50 0, 51 0, 52 0, 53 0, 54 false, 55 false, 56 false, 57 false, 58 0, 59 null, 60 0, 61 0 62 ]; 63 64 function PointerEvent(inType, inDict) { 65 inDict = inDict || Object.create(null); 66 67 var e = document.createEvent('Event'); 68 e.initEvent(inType, inDict.bubbles || false, inDict.cancelable || false); 69 70 // define inherited MouseEvent properties 71 // skip bubbles and cancelable since they're set above in initEvent() 72 for (var i = 2, p; i < MOUSE_PROPS.length; i++) { 73 p = MOUSE_PROPS[i]; 74 e[p] = inDict[p] || MOUSE_DEFAULTS[i]; 75 } 76 e.buttons = inDict.buttons || 0; 77 78 // Spec requires that pointers without pressure specified use 0.5 for down 79 // state and 0 for up state. 80 var pressure = 0; 81 82 if (inDict.pressure && e.buttons) { 83 pressure = inDict.pressure; 84 } else { 85 pressure = e.buttons ? 0.5 : 0; 86 } 87 88 // add x/y properties aliased to clientX/Y 89 e.x = e.clientX; 90 e.y = e.clientY; 91 92 // define the properties of the PointerEvent interface 93 e.pointerId = inDict.pointerId || 0; 94 e.width = inDict.width || 0; 95 e.height = inDict.height || 0; 96 e.pressure = pressure; 97 e.tiltX = inDict.tiltX || 0; 98 e.tiltY = inDict.tiltY || 0; 99 e.twist = inDict.twist || 0; 100 e.tangentialPressure = inDict.tangentialPressure || 0; 101 e.pointerType = inDict.pointerType || ''; 102 e.hwTimestamp = inDict.hwTimestamp || 0; 103 e.isPrimary = inDict.isPrimary || false; 104 return e; 105 } 106 107 /** 108 * This module implements a map of pointer states 109 */ 110 var USE_MAP = window.Map && window.Map.prototype.forEach; 111 var PointerMap = USE_MAP ? Map : SparseArrayMap; 112 113 function SparseArrayMap() { 114 this.array = []; 115 this.size = 0; 116 } 117 118 SparseArrayMap.prototype = { 119 set: function(k, v) { 120 if (v === undefined) { 121 return this.delete(k); 122 } 123 if (!this.has(k)) { 124 this.size++; 125 } 126 this.array[k] = v; 127 }, 128 has: function(k) { 129 return this.array[k] !== undefined; 130 }, 131 delete: function(k) { 132 if (this.has(k)) { 133 delete this.array[k]; 134 this.size--; 135 } 136 }, 137 get: function(k) { 138 return this.array[k]; 139 }, 140 clear: function() { 141 this.array.length = 0; 142 this.size = 0; 143 }, 144 145 // return value, key, map 146 forEach: function(callback, thisArg) { 147 return this.array.forEach(function(v, k) { 148 callback.call(thisArg, v, k, this); 149 }, this); 150 } 151 }; 152 153 var CLONE_PROPS = [ 154 155 // MouseEvent 156 'bubbles', 157 'cancelable', 158 'view', 159 'detail', 160 'screenX', 161 'screenY', 162 'clientX', 163 'clientY', 164 'ctrlKey', 165 'altKey', 166 'shiftKey', 167 'metaKey', 168 'button', 169 'relatedTarget', 170 171 // DOM Level 3 172 'buttons', 173 174 // PointerEvent 175 'pointerId', 176 'width', 177 'height', 178 'pressure', 179 'tiltX', 180 'tiltY', 181 'pointerType', 182 'hwTimestamp', 183 'isPrimary', 184 185 // event instance 186 'type', 187 'target', 188 'currentTarget', 189 'which', 190 'pageX', 191 'pageY', 192 'timeStamp' 193 ]; 194 195 var CLONE_DEFAULTS = [ 196 197 // MouseEvent 198 false, 199 false, 200 null, 201 null, 202 0, 203 0, 204 0, 205 0, 206 false, 207 false, 208 false, 209 false, 210 0, 211 null, 212 213 // DOM Level 3 214 0, 215 216 // PointerEvent 217 0, 218 0, 219 0, 220 0, 221 0, 222 0, 223 '', 224 0, 225 false, 226 227 // event instance 228 '', 229 null, 230 null, 231 0, 232 0, 233 0, 234 0 235 ]; 236 237 var BOUNDARY_EVENTS = { 238 'pointerover': 1, 239 'pointerout': 1, 240 'pointerenter': 1, 241 'pointerleave': 1 242 }; 243 244 var HAS_SVG_INSTANCE = (typeof SVGElementInstance !== 'undefined'); 245 246 /** 247 * This module is for normalizing events. Mouse and Touch events will be 248 * collected here, and fire PointerEvents that have the same semantics, no 249 * matter the source. 250 * Events fired: 251 * - pointerdown: a pointing is added 252 * - pointerup: a pointer is removed 253 * - pointermove: a pointer is moved 254 * - pointerover: a pointer crosses into an element 255 * - pointerout: a pointer leaves an element 256 * - pointercancel: a pointer will no longer generate events 257 */ 258 var dispatcher = { 259 pointermap: new PointerMap(), 260 eventMap: Object.create(null), 261 captureInfo: Object.create(null), 262 263 // Scope objects for native events. 264 // This exists for ease of testing. 265 eventSources: Object.create(null), 266 eventSourceList: [], 267 /** 268 * Add a new event source that will generate pointer events. 269 * 270 * `inSource` must contain an array of event names named `events`, and 271 * functions with the names specified in the `events` array. 272 * @param {string} name A name for the event source 273 * @param {Object} source A new source of platform events. 274 */ 275 registerSource: function(name, source) { 276 var s = source; 277 var newEvents = s.events; 278 if (newEvents) {
279 newEvents.forEach(function(e) { 280 if (s[e]) { 281 this.eventMap[e] = s[e].bind(s); 282 } 283 }, this); 284 this.eventSources[name] = s; 285 this.eventSourceList.push(s); 286 } 287 }, 288 register: function(element) { 289 var l = this.eventSourceList.length; 290 for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { 291 292 // call eventsource register 293 es.register.call(es, element); 294 } 295 }, 296 unregister: function(element) { 297 var l = this.eventSourceList.length; 298 for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { 299 300 // call eventsource register 301 es.unregister.call(es, element); 302 } 303 }, 304 contains: /*scope.external.contains || */function(container, contained) { 305 try { 306 return container.contains(contained); 307 } catch (ex) { 308 309 // most likely: https://bugzilla.mozilla.org/show_bug.cgi?id=208427 310 return false; 311 } 312 }, 313 314 // EVENTS 315 down: function(inEvent) { 316 inEvent.bubbles = true; 317 this.fireEvent('pointerdown', inEvent); 318 }, 319 move: function(inEvent) { 320 inEvent.bubbles = true; 321 this.fireEvent('pointermove', inEvent); 322 }, 323 up: function(inEvent) { 324 inEvent.bubbles = true; 325 this.fireEvent('pointerup', inEvent); 326 }, 327 enter: function(inEvent) { 328 inEvent.bubbles = false; 329 this.fireEvent('pointerenter', inEvent); 330 }, 331 leave: function(inEvent) { 332 inEvent.bubbles = false; 333 this.fireEvent('pointerleave', inEvent); 334 }, 335 over: function(inEvent) { 336 inEvent.bubbles = true; 337 this.fireEvent('pointerover', inEvent); 338 }, 339 out: function(inEvent) { 340 inEvent.bubbles = true; 341 this.fireEvent('pointerout', inEvent); 342 }, 343 cancel: function(inEvent) { 344 inEvent.bubbles = true; 345 this.fireEvent('pointercancel', inEvent); 346 }, 347 leaveOut: function(event) { 348 this.out(event); 349 this.propagate(event, this.leave, false); 350 }, 351 enterOver: function(event) { 352 this.over(event); 353 this.propagate(event, this.enter, true); 354 }, 355 356 // LISTENER LOGIC 357 eventHandler: function(inEvent) { 358 359 // This is used to prevent multiple dispatch of pointerevents from 360 // platform events. This can happen when two elements in different scopes 361 // are set up to create pointer events, which is relevant to Shadow DOM. 362 if (inEvent._handledByPE) { 363 return; 364 } 365 var type = inEvent.type; 366 var fn = this.eventMap && this.eventMap[type]; 367 if (fn) { 368 fn(inEvent); 369 } 370 inEvent._handledByPE = true; 371 }, 372 373 // set up event listeners 374 listen: function(target, events) { 375 events.forEach(function(e) { 376 this.addEvent(target, e); 377 }, this); 378 }, 379 380 // remove event listeners 381 unlisten: function(target, events) { 382 events.forEach(function(e) { 383 this.removeEvent(target, e); 384 }, this); 385 }, 386 addEvent: /*scope.external.addEvent || */function(target, eventName) { 387 target.addEventListener(eventName, this.boundHandler); 388 }, 389 removeEvent: /*scope.external.removeEvent || */function(target, eventName) { 390 target.removeEventListener(eventName, this.boundHandler); 391 }, 392 393 // EVENT CREATION AND TRACKING 394 /** 395 * Creates a new Event of type `inType`, based on the information in 396 * `inEvent`. 397 * 398 * @param {string} inType A string representing the type of event to create 399 * @param {Event} inEvent A platform event with a target 400 * @return {Event} A PointerEvent of type `inType` 401 */ 402 makeEvent: function(inType, inEvent) { 403 404 // relatedTarget must be null if pointer is captured 405 if (this.captureInfo[inEvent.pointerId]) { 406 inEvent.relatedTarget = null; 407 } 408 var e = new PointerEvent(inType, inEvent); 409 if (inEvent.preventDefault) { 410 e.preventDefault = inEvent.preventDefault; 411 } 412 e._target = e._target || inEvent.target; 413 return e; 414 }, 415 416 // make and dispatch an event in one call 417 fireEvent: function(inType, inEvent) { 418 var e = this.makeEvent(inType, inEvent); 419 return this.dispatchEvent(e); 420 }, 421 /** 422 * Returns a snapshot of inEvent, with writable properties. 423 * 424 * @param {Event} inEvent An event that contains properties to copy. 425 * @return {Object}
425 An object containing shallow copies of `inEvent`'s 426 * properties. 427 */ 428 cloneEvent: function(inEvent) { 429 var eventCopy = Object.create(null); 430 var p; 431 for (var i = 0; i < CLONE_PROPS.length; i++) { 432 p = CLONE_PROPS[i]; 433 eventCopy[p] = inEvent[p] || CLONE_DEFAULTS[i]; 434 435 // Work around SVGInstanceElement shadow tree 436 // Return the <use> element that is represented by the instance for Safari, Chrome, IE. 437 // This is the behavior implemented by Firefox. 438 if (HAS_SVG_INSTANCE && (p === 'target' || p === 'relatedTarget')) { 439 if (eventCopy[p] instanceof SVGElementInstance) { 440 eventCopy[p] = eventCopy[p].correspondingUseElement; 441 } 442 } 443 } 444 445 // keep the semantics of preventDefault 446 if (inEvent.preventDefault) { 447 eventCopy.preventDefault = function() { 448 inEvent.preventDefault(); 449 }; 450 } 451 return eventCopy; 452 }, 453 getTarget: function(inEvent) { 454 var capture = this.captureInfo[inEvent.pointerId]; 455 if (!capture) { 456 return inEvent._target; 457 } 458 if (inEvent._target === capture || !(inEvent.type in BOUNDARY_EVENTS)) { 459 return capture; 460 } 461 }, 462 propagate: function(event, fn, propagateDown) { 463 var target = event.target; 464 var targets = []; 465 466 // Order of conditions due to document.contains() missing in IE. 467 while (target !== document && !target.contains(event.relatedTarget)) { 468 targets.push(target); 469 target = target.parentNode; 470 471 // Touch: Do not propagate if node is detached. 472 if (!target) { 473 return; 474 } 475 } 476 if (propagateDown) { 477 targets.reverse(); 478 } 479 targets.forEach(function(target) { 480 event.target = target; 481 fn.call(this, event); 482 }, this); 483 }, 484 setCapture: function(inPointerId, inTarget, skipDispatch) { 485 if (this.captureInfo[inPointerId]) { 486 this.releaseCapture(inPointerId, skipDispatch); 487 } 488 489 this.captureInfo[inPointerId] = inTarget; 490 this.implicitRelease = this.releaseCapture.bind(this, inPointerId, skipDispatch); 491 document.addEventListener('pointerup', this.implicitRelease); 492 document.addEventListener('pointercancel', this.implicitRelease); 493 494 var e = new PointerEvent('gotpointercapture'); 495 e.pointerId = inPointerId; 496 e._target = inTarget; 497 498 if (!skipDispatch) { 499 this.asyncDispatchEvent(e); 500 } 501 }, 502 releaseCapture: function(inPointerId, skipDispatch) { 503 var t = this.captureInfo[inPointerId]; 504 if (!t) { 505 return; 506 } 507 508 this.captureInfo[inPointerId] = undefined; 509 document.removeEventListener('pointerup', this.implicitRelease); 510 document.removeEventListener('pointercancel', this.implicitRelease); 511 512 var e = new PointerEvent('lostpointercapture'); 513 e.pointerId = inPointerId; 514 e._target = t; 515 516 if (!skipDispatch) { 517 this.asyncDispatchEvent(e); 518 } 519 }, 520 /** 521 * Dispatches the event to its target. 522 * 523 * @param {Event} inEvent The event to be dispatched. 524 * @return {Boolean} True if an event handler returns true, false otherwise. 525 */ 526 dispatchEvent: /*scope.external.dispatchEvent || */function(inEvent) { 527 var t = this.getTarget(inEvent); 528 if (t) { 529 return t.dispatchEvent(inEvent); 530 } 531 }, 532 asyncDispatchEvent: function(inEvent) { 533 requestAnimationFrame(this.dispatchEvent.bind(this, inEvent)); 534 } 535 }; 536 dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher); 537 538 var targeting = { 539 shadow: function(inEl) { 540 if (inEl) { 541 return inEl.shadowRoot || inEl.webkitShadowRoot; 542 } 543 }, 544 canTarget: function(shadow) { 545 return shadow && Boolean(shadow.elementFromPoint); 546 }, 547 targetingShadow: function(inEl) { 548 var s = this.shadow(inEl); 549 if (this.canTarget(s)) { 550 return s; 551 } 552 }, 553 olderShadow: function(shadow) { 554 var os = shadow.olderShadowRoot; 555 if (!os) { 556 var se = shadow.querySelector('shadow'); 557 if (se) { 558 os = se.olderShadowRoot; 559 } 560 } 561 return os; 562 }, 563 allShadows: function(element) { 564 var shadows = []; 565 var s = this.shadow(element); 566 while (s) { 567 shadows.push(s); 568 s = this.olderShadow(s); 569 } 570 return shadows; 571 }, 572 searchRoot: function(inRoot, x, y) { 573 if (inRoot) { 574 var t = inRoot.elementFromPoint(x, y); 575 var st, sr; 576 577 // is element a shadow host? 578 sr = this.targetingShadow(t); 579 while (sr) { 580 581 // find the the element inside the shadow root 582 st = sr.elementFromPoint(x, y); 583 if (!st) { 584 585 // check for older shadows 586 sr = this.olderShadow(sr); 587 } else { 588 589 // shadowed element may contain a shadow root 590 var ssr = this.targetingShadow(st); 591 return this.searchRoot(ssr, x, y) || st; 592 } 593 } 594 595 // light dom element is the target 596 return t; 597 } 598 }, 599 owner: function(element) { 600 var s = element; 601 602 // walk up until you hit the shadow root or document 603 while (s.parentNode) { 604 s = s.parentNode; 605 } 606 607 // the owner element is expected to be a Document or ShadowRoot 608 if (s.nodeType !== Node.DOCUMENT_NODE && s.nodeType !== Node.DOCUMENT_FRAGMENT_NODE) { 609 s = document; 610 } 611 return s; 612 }, 613 findTarget: function(inEvent) { 614 var x = inEvent.clientX; 615 var y = inEvent.clientY; 616 617 // if the listener is in the shadow root, it is much faster to start there 618 var s = this.owner(inEvent.target); 619 620 // if x, y is not in this root, fall back to document search 621 if (!s.elementFromPoint(x, y)) { 622 s = document; 623 } 624 return this.searchRoot(s, x, y); 625 } 626 }; 627
628 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); 629 var map = Array.prototype.map.call.bind(Array.prototype.map); 630 var toArray = Array.prototype.slice.call.bind(Array.prototype.slice); 631 var filter = Array.prototype.filter.call.bind(Array.prototype.filter); 632 var MO = window.MutationObserver || window.WebKitMutationObserver; 633 var SELECTOR = '[touch-action]'; 634 var OBSERVER_INIT = { 635 subtree: true, 636 childList: true, 637 attributes: true, 638 attributeOldValue: true, 639 attributeFilter: ['touch-action'] 640 }; 641 642 function Installer(add, remove, changed, binder) { 643 this.addCallback = add.bind(binder); 644 this.removeCallback = remove.bind(binder); 645 this.changedCallback = changed.bind(binder); 646 if (MO) { 647 this.observer = new MO(this.mutationWatcher.bind(this)); 648 } 649 } 650 651 Installer.prototype = { 652 watchSubtree: function(target) { 653 654 // Only watch scopes that can target find, as these are top-level. 655 // Otherwise we can see duplicate additions and removals that add noise. 656 // 657 // TODO(dfreedman): For some instances with ShadowDOMPolyfill, we can see 658 // a removal without an insertion when a node is redistributed among 659 // shadows. Since it all ends up correct in the document, watching only 660 // the document will yield the correct mutations to watch. 661 if (this.observer && targeting.canTarget(target)) { 662 this.observer.observe(target, OBSERVER_INIT); 663 } 664 }, 665 enableOnSubtree: function(target) { 666 this.watchSubtree(target); 667 if (target === document && document.readyState !== 'complete') { 668 this.installOnLoad(); 669 } else { 670 this.installNewSubtree(target); 671 } 672 }, 673 installNewSubtree: function(target) { 674 forEach(this.findElements(target), this.addElement, this); 675 }, 676 findElements: function(target) { 677 if (target.querySelectorAll) { 678 return target.querySelectorAll(SELECTOR); 679 } 680 return []; 681 }, 682 removeElement: function(el) { 683 this.removeCallback(el); 684 }, 685 addElement: function(el) { 686 this.addCallback(el); 687 }, 688 elementChanged: function(el, oldValue) { 689 this.changedCallback(el, oldValue); 690 }, 691 concatLists: function(accum, list) { 692 return accum.concat(toArray(list)); 693 }, 694 695 // register all touch-action = none nodes on document load 696 installOnLoad: function() { 697 document.addEventListener('readystatechange', function() { 698 if (document.readyState === 'complete') { 699 this.installNewSubtree(document); 700 } 701 }.bind(this)); 702 }, 703 isElement: function(n) { 704 return n.nodeType === Node.ELEMENT_NODE; 705 }, 706 flattenMutationTree: function(inNodes) { 707 708 // find children with touch-action 709 var tree = map(inNodes, this.findElements, this); 710 711 // make sure the added nodes are accounted for 712 tree.push(filter(inNodes, this.isElement)); 713 714 // flatten the list 715 return tree.reduce(this.concatLists, []); 716 }, 717 mutationWatcher: function(mutations) { 718 mutations.forEach(this.mutationHandler, this); 719 }, 720 mutationHandler: function(m) { 721 if (m.type === 'childList') { 722 var added = this.flattenMutationTree(m.addedNodes); 723 added.forEach(this.addElement, this); 724 var removed = this.flattenMutationTree(m.removedNodes); 725 removed.forEach(this.removeElement, this); 726 } else if (m.type === 'attributes') { 727 this.elementChanged(m.target, m.oldValue); 728 } 729 } 730 }; 731 732 function shadowSelector(v) { 733 return 'body /shadow-deep/ ' + selector(v); 734 } 735 function selector(v) { 736 return '[touch-action="' + v + '"]'; 737 } 738 function rule(v) { 739 return '{ -ms-touch-action: ' + v + '; touch-action: ' + v + '; }'; 740 } 741 var attrib2css = [ 742 'none', 743 'auto', 744 'pan-x', 745 'pan-y', 746 { 747 rule: 'pan-x pan-y', 748 selectors: [ 749 'pan-x pan-y', 750 'pan-y pan-x' 751 ] 752 } 753 ]; 754 var styles = ''; 755 756 // only install stylesheet if the browser has touch action support 757 var hasNativePE = window.PointerEvent || window.MSPointerEvent; 758 759 // only add shadow selectors if shadowdom is supported 760 var hasShadowRoot = !window.ShadowDOMPolyfill && document.head.createShadowRoot; 761 762 function applyAttributeStyles() { 763 if (hasNativePE) { 764 attrib2css.forEach(function(r) { 765 if (String(r) === r) { 766 styles += selector(r) + rule(r) + '\n'; 767 if (hasShadowRoot) { 768 styles += shadowSelector(r) + rule(r) + '\n'; 769 } 770 } else { 771 styles += r.selectors.map(selector) + rule(r.rule) + '\n'; 772 if (hasShadowRoot) { 773 styles += r.selectors.map(shadowSelector) + rule(r.rule) + '\n'; 774 } 775 } 776 }); 777 778 var el = document.createElement('style'); 779 el.textContent = styles; 780 document.head.appendChild(el); 781 } 782 } 783 784 var pointermap = dispatcher.pointermap; 785 786 // radius around touchend that swallows mouse events 787 var DEDUP_DIST = 25; 788 789 // left, middle, right, back, forward 790 var BUTTON_TO_BUTTONS = [1, 4, 2, 8, 16]; 791 792 var HAS_BUTTONS = false;
793 try { 794 HAS_BUTTONS = new MouseEvent('test', { buttons: 1 }).buttons === 1; 795 } catch (e) {} 796 797 // handler block for native mouse events 798 var mouseEvents = { 799 POINTER_ID: 1, 800 POINTER_TYPE: 'mouse', 801 events: [ 802 'mousedown', 803 'mousemove', 804 'mouseup', 805 'mouseover', 806 'mouseout' 807 ], 808 register: function(target) { 809 dispatcher.listen(target, this.events); 810 }, 811 unregister: function(target) { 812 dispatcher.unlisten(target, this.events); 813 }, 814 lastTouches: [], 815 816 // collide with the global mouse listener 817 isEventSimulatedFromTouch: function(inEvent) { 818 var lts = this.lastTouches; 819 var x = inEvent.clientX; 820 var y = inEvent.clientY; 821 for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) { 822 823 // simulated mouse events will be swallowed near a primary touchend 824 var dx = Math.abs(x - t.x); 825 var dy = Math.abs(y - t.y); 826 if (dx <= DEDUP_DIST && dy <= DEDUP_DIST) { 827 return true; 828 } 829 } 830 }, 831 prepareEvent: function(inEvent) { 832 var e = dispatcher.cloneEvent(inEvent); 833 834 // forward mouse preventDefault 835 var pd = e.preventDefault; 836 e.preventDefault = function() { 837 inEvent.preventDefault(); 838 pd(); 839 }; 840 e.pointerId = this.POINTER_ID; 841 e.isPrimary = true; 842 e.pointerType = this.POINTER_TYPE; 843 return e; 844 }, 845 prepareButtonsForMove: function(e, inEvent) { 846 var p = pointermap.get(this.POINTER_ID); 847 848 // Update buttons state after possible out-of-document mouseup. 849 if (inEvent.which === 0 || !p) { 850 e.buttons = 0; 851 } else { 852 e.buttons = p.buttons; 853 } 854 inEvent.buttons = e.buttons; 855 }, 856 mousedown: function(inEvent) { 857 if (!this.isEventSimulatedFromTouch(inEvent)) { 858 var p = pointermap.get(this.POINTER_ID); 859 var e = this.prepareEvent(inEvent); 860 if (!HAS_BUTTONS) { 861 e.buttons = BUTTON_TO_BUTTONS[e.button]; 862 if (p) { e.buttons |= p.buttons; } 863 inEvent.buttons = e.buttons; 864 } 865 pointermap.set(this.POINTER_ID, inEvent); 866 if (!p || p.buttons === 0) { 867 dispatcher.down(e); 868 } else { 869 dispatcher.move(e); 870 } 871 } 872 }, 873 mousemove: function(inEvent) { 874 if (!this.isEventSimulatedFromTouch(inEvent)) { 875 var e = this.prepareEvent(inEvent); 876 if (!HAS_BUTTONS) { this.prepareButtonsForMove(e, inEvent); } 877 e.button = -1; 878 pointermap.set(this.POINTER_ID, inEvent); 879 dispatcher.move(e); 880 } 881 }, 882 mouseup: function(inEvent) { 883 if (!this.isEventSimulatedFromTouch(inEvent)) { 884 var p = pointermap.get(this.POINTER_ID); 885 var e = this.prepareEvent(inEvent); 886 if (!HAS_BUTTONS) { 887 var up = BUTTON_TO_BUTTONS[e.button]; 888 889 // Produces wrong state of buttons in Browsers without `buttons` support 890 // when a mouse button that was pressed outside the document is released 891 // inside and other buttons are still pressed down. 892 e.buttons = p ? p.buttons & ~up : 0; 893 inEvent.buttons = e.buttons; 894 } 895 pointermap.set(this.POINTER_ID, inEvent); 896 897 // Support: Firefox <=44 only 898 // FF Ubuntu includes the lifted button in the `buttons` property on 899 // mouseup. 900 // https://bugzilla.mozilla.org/show_bug.cgi?id=1223366 901 e.buttons &= ~BUTTON_TO_BUTTONS[e.button]; 902 if (e.buttons === 0) { 903 dispatcher.up(e); 904 } else { 905 dispatcher.move(e); 906 } 907 } 908 }, 909 mouseover: function(inEvent) { 910 if (!this.isEventSimulatedFromTouch(inEvent)) { 911 var e = this.prepareEvent(inEvent); 912 if (!HAS_BUTTONS) { this.prepareButtonsForMove(e, inEvent); } 913 e.button = -1; 914 pointermap.set(this.POINTER_ID, inEvent); 915 dispatcher.enterOver(e); 916 } 917 }, 918 mouseout: function(inEvent) { 919 if (!this.isEventSimulatedFromTouch(inEvent)) { 920 var e = this.prepareEvent(inEvent); 921 if (!HAS_BUTTONS) { this.prepareButtonsForMove(e, inEvent); } 922 e.button = -1; 923 dispatcher.leaveOut(e); 924 } 925 }, 926 cancel: function(inEvent) { 927 var e = this.prepareEvent(inEvent); 928 dispatcher.cancel(e); 929 this.deactivateMouse(); 930 }, 931 deactivateMouse: function() { 932 pointermap.delete(this.POINTER_ID); 933 } 934 }; 935
936 var captureInfo = dispatcher.captureInfo; 937 var findTarget = targeting.findTarget.bind(targeting); 938 var allShadows = targeting.allShadows.bind(targeting); 939 var pointermap$1 = dispatcher.pointermap; 940 941 // This should be long enough to ignore compat mouse events made by touch 942 var DEDUP_TIMEOUT = 2500; 943 var CLICK_COUNT_TIMEOUT = 200; 944 var ATTRIB = 'touch-action'; 945 var INSTALLER; 946 947 // handler block for native touch events 948 var touchEvents = { 949 events: [ 950 'touchstart', 951 'touchmove', 952 'touchend', 953 'touchcancel' 954 ], 955 register: function(target) { 956 INSTALLER.enableOnSubtree(target); 957 }, 958 unregister: function() { 959 960 // TODO(dfreedman): is it worth it to disconnect the MO? 961 }, 962 elementAdded: function(el) { 963 var a = el.getAttribute(ATTRIB); 964 var st = this.touchActionToScrollType(a); 965 if (st) { 966 el._scrollType = st; 967 dispatcher.listen(el, this.events); 968 969 // set touch-action on shadows as well 970 allShadows(el).forEach(function(s) { 971 s._scrollType = st; 972 dispatcher.listen(s, this.events); 973 }, this); 974 } 975 }, 976 elementRemoved: function(el) { 977 el._scrollType = undefined; 978 dispatcher.unlisten(el, this.events); 979 980 // remove touch-action from shadow 981 allShadows(el).forEach(function(s) { 982 s._scrollType = undefined; 983 dispatcher.unlisten(s, this.events); 984 }, this); 985 }, 986 elementChanged: function(el, oldValue) { 987 var a = el.getAttribute(ATTRIB); 988 var st = this.touchActionToScrollType(a); 989 var oldSt = this.touchActionToScrollType(oldValue); 990 991 // simply update scrollType if listeners are already established 992 if (st && oldSt) { 993 el._scrollType = st; 994 allShadows(el).forEach(function(s) { 995 s._scrollType = st; 996 }, this); 997 } else if (oldSt) { 998 this.elementRemoved(el); 999 } else if (st) { 1000 this.elementAdded(el); 1001 } 1002 }, 1003 scrollTypes: { 1004 EMITTER: 'none', 1005 XSCROLLER: 'pan-x', 1006 YSCROLLER: 'pan-y', 1007 SCROLLER: /^(?:pan-x pan-y)|(?:pan-y pan-x)|auto$/ 1008 }, 1009 touchActionToScrollType: function(touchAction) { 1010 var t = touchAction; 1011 var st = this.scrollTypes; 1012 if (t === 'none') { 1013 return 'none'; 1014 } else if (t === st.XSCROLLER) { 1015 return 'X'; 1016 } else if (t === st.YSCROLLER) { 1017 return 'Y'; 1018 } else if (st.SCROLLER.exec(t)) { 1019 return 'XY'; 1020 } 1021 }, 1022 POINTER_TYPE: 'touch', 1023 firstTouch: null, 1024 isPrimaryTouch: function(inTouch) { 1025 return this.firstTouch === inTouch.identifier; 1026 }, 1027 setPrimaryTouch: function(inTouch) { 1028 1029 // set primary touch if there no pointers, or the only pointer is the mouse 1030 if (pointermap$1.size === 0 || (pointermap$1.size === 1 && pointermap$1.has(1))) { 1031 this.firstTouch = inTouch.identifier; 1032 this.firstXY = { X: inTouch.clientX, Y: inTouch.clientY }; 1033 this.scrolling = false; 1034 this.cancelResetClickCount(); 1035 } 1036 }, 1037 removePrimaryPointer: function(inPointer) { 1038 if (inPointer.isPrimary) { 1039 this.firstTouch = null; 1040 this.firstXY = null; 1041 this.resetClickCount(); 1042 } 1043 }, 1044 clickCount: 0, 1045 resetId: null, 1046 resetClickCount: function() { 1047 var fn = function() { 1048 this.clickCount = 0; 1049 this.resetId = null; 1050 }.bind(this); 1051 this.resetId = setTimeout(fn, CLICK_COUNT_TIMEOUT); 1052 }, 1053 cancelResetClickCount: function() { 1054 if (this.resetId) { 1055 clearTimeout(this.resetId); 1056 } 1057 }, 1058 typeToButtons: function(type) { 1059 var ret = 0; 1060 if (type === 'touchstart' || type === 'touchmove') { 1061 ret = 1; 1062 } 1063 return ret; 1064 }, 1065 touchToPointer: function(inTouch) { 1066 var cte = this.currentTouchEvent; 1067 var e = dispatcher.cloneEvent(inTouch); 1068 1069 // We reserve pointerId 1 for Mouse. 1070 // Touch identifiers can start at 0. 1071 // Add 2 to the touch identifier for compatibility. 1072 var id = e.pointerId = inTouch.identifier + 2; 1073 e.target = captureInfo[id] || findTarget(e); 1074 e.bubbles = true; 1075 e.cancelable = true; 1076 e.detail = this.clickCount; 1077 e.button = 0; 1078 e.buttons = this.typeToButtons(cte.type); 1079 e.width = (inTouch.radiusX || inTouch.webkitRadiusX || 0) * 2; 1080 e.height = (inTouch.radiusY || inTouch.webkitRadiusY || 0) * 2; 1081 e.pressure = inTouch.force || inTouch.webkitForce || 0.5; 1082 e.isPrimary = this.isPrimaryTouch(inTouch); 1083 e.pointerType = this.POINTER_TYPE; 1084 1085 // forward modifier keys 1086 e.altKey = cte.altKey; 1087 e.ctrlKey = cte.ctrlKey; 1088 e.metaKey = cte.metaKey; 1089 e.shiftKey = cte.shiftKey; 1090 1091 // forward touch preventDefaults 1092 var self = this; 1093 e.preventDefault = function() { 1094 self.scrolling = false;
1095 self.firstXY = null; 1096 cte.preventDefault(); 1097 }; 1098 return e; 1099 }, 1100 processTouches: function(inEvent, inFunction) { 1101 var tl = inEvent.changedTouches; 1102 this.currentTouchEvent = inEvent; 1103 for (var i = 0, t; i < tl.length; i++) { 1104 t = tl[i]; 1105 inFunction.call(this, this.touchToPointer(t)); 1106 } 1107 }, 1108 1109 // For single axis scrollers, determines whether the element should emit 1110 // pointer events or behave as a scroller 1111 shouldScroll: function(inEvent) { 1112 if (this.firstXY) { 1113 var ret; 1114 var scrollAxis = inEvent.currentTarget._scrollType; 1115 if (scrollAxis === 'none') { 1116 1117 // this element is a touch-action: none, should never scroll 1118 ret = false; 1119 } else if (scrollAxis === 'XY') { 1120 1121 // this element should always scroll 1122 ret = true; 1123 } else { 1124 var t = inEvent.changedTouches[0]; 1125 1126 // check the intended scroll axis, and other axis 1127 var a = scrollAxis; 1128 var oa = scrollAxis === 'Y' ? 'X' : 'Y'; 1129 var da = Math.abs(t['client' + a] - this.firstXY[a]); 1130 var doa = Math.abs(t['client' + oa] - this.firstXY[oa]); 1131 1132 // if delta in the scroll axis > delta other axis, scroll instead of 1133 // making events 1134 ret = da >= doa; 1135 } 1136 this.firstXY = null; 1137 return ret; 1138 } 1139 }, 1140 findTouch: function(inTL, inId) { 1141 for (var i = 0, l = inTL.length, t; i < l && (t = inTL[i]); i++) { 1142 if (t.identifier === inId) { 1143 return true; 1144 } 1145 } 1146 }, 1147 1148 // In some instances, a touchstart can happen without a touchend. This 1149 // leaves the pointermap in a broken state. 1150 // Therefore, on every touchstart, we remove the touches that did not fire a 1151 // touchend event. 1152 // To keep state globally consistent, we fire a 1153 // pointercancel for this "abandoned" touch 1154 vacuumTouches: function(inEvent) { 1155 var tl = inEvent.touches; 1156 1157 // pointermap.size should be < tl.length here, as the touchstart has not 1158 // been processed yet. 1159 if (pointermap$1.size >= tl.length) { 1160 var d = []; 1161 pointermap$1.forEach(function(value, key) { 1162 1163 // Never remove pointerId == 1, which is mouse. 1164 // Touch identifiers are 2 smaller than their pointerId, which is the 1165 // index in pointermap. 1166 if (key !== 1 && !this.findTouch(tl, key - 2)) { 1167 var p = value.out; 1168 d.push(p); 1169 } 1170 }, this); 1171 d.forEach(this.cancelOut, this); 1172 } 1173 }, 1174 touchstart: function(inEvent) { 1175 this.vacuumTouches(inEvent); 1176 this.setPrimaryTouch(inEvent.changedTouches[0]); 1177 this.dedupSynthMouse(inEvent); 1178 if (!this.scrolling) { 1179 this.clickCount++; 1180 this.processTouches(inEvent, this.overDown); 1181 } 1182 }, 1183 overDown: function(inPointer) { 1184 pointermap$1.set(inPointer.pointerId, { 1185 target: inPointer.target, 1186 out: inPointer, 1187 outTarget: inPointer.target 1188 }); 1189 dispatcher.enterOver(inPointer); 1190 dispatcher.down(inPointer); 1191 }, 1192 touchmove: function(inEvent) { 1193 if (!this.scrolling) { 1194 if (this.shouldScroll(inEvent)) { 1195 this.scrolling = true; 1196 this.touchcancel(inEvent); 1197 } else { 1198 inEvent.preventDefault(); 1199 this.processTouches(inEvent, this.moveOverOut); 1200 } 1201 } 1202 }, 1203 moveOverOut: function(inPointer) { 1204 var event = inPointer; 1205 var pointer = pointermap$1.get(event.pointerId); 1206 1207 // a finger drifted off the screen, ignore it 1208 if (!pointer) { 1209 return; 1210 } 1211 var outEvent = pointer.out; 1212 var outTarget = pointer.outTarget; 1213 dispatcher.move(event); 1214 if (outEvent && outTarget !== event.target) { 1215 outEvent.relatedTarget = event.target; 1216 event.relatedTarget = outTarget; 1217 1218 // recover from retargeting by shadow 1219 outEvent.target = outTarget; 1220 if (event.target) { 1221 dispatcher.leaveOut(outEvent); 1222 dispatcher.enterOver(event); 1223 } else { 1224 1225 // clean up case when finger leaves the screen 1226 event.target = outTarget; 1227 event.relatedTarget = null; 1228 this.cancelOut(event); 1229 } 1230 } 1231 pointer.out = event; 1232 pointer.outTarget = event.target; 1233 }, 1234 touchend: function(inEvent) { 1235 this.dedupSynthMouse(inEvent); 1236 this.processTouches(inEvent, this.upOut); 1237 }, 1238 upOut: function(inPointer) { 1239 if (!this.scrolling) { 1240 dispatcher.up(inPointer); 1241 dispatcher.leaveOut(inPointer); 1242 } 1243 this.cleanUpPointer(inPointer); 1244 }, 1245 touchcancel: function(inEvent) { 1246 this.processTouches(inEvent, this.cancelOut); 1247 }, 1248 cancelOut: function(inPointer) { 1249 dispatcher.cancel(inPointer); 1250 dispatcher.leaveOut(inPointer); 1251 this.cleanUpPointer(inPointer); 1252 }, 1253 cleanUpPointer: function(inPointer) { 1254 pointermap$1.delete(inPointer.pointerId); 1255 this.removePrimaryPointer(inPointer); 1256 }, 1257 1258 // prevent synth mouse events from creating pointer events 1259 dedupSynthMouse: function(inEvent) { 1260 var lts = mouseEvents.lastTouches; 1261 var t = inEvent.changedTouches[0]; 1262 1263 // only the primary finger will synth mouse events 1264 if (this.isPrimaryTouch(t)) { 1265 1266 // remember x/y of last touch 1267 var lt = { x: t.clientX, y: t.clientY }; 1268 lts.push(lt); 1269 var fn = (function(lts, lt) { 1270 var i = lts.indexOf(lt); 1271 if (i > -1) { 1272 lts.splice(i, 1); 1273 } 1274 }).bind(null, lts, lt); 1275 setTimeout(fn, DEDUP_TIMEOUT); 1276 } 1277 } 1278 }; 1279 1280 INSTALLER = new Installer(touchEvents.elementAdded, touchEvents.elementRemoved, 1281 touchEvents.elementChanged, touchEvents); 1282 1283 var pointermap$2 = dispatcher.pointermap; 1284 var HAS_BITMAP_TYPE = window.MSPointerEvent && 1285 typeof window.MSPointerEvent.MSPOINTER_TYPE_MOUSE === 'number'; 1286 var msEvents = { 1287 events: [ 1288 'MSPointerDown', 1289 'MSPointerMove', 1290 'MSPointerUp', 1291 'MSPointerOut', 1292 'MSPointerOver', 1293 'MSPointerCancel', 1294 'MSGotPointerCapture', 1295 'MSLostPointerCapture' 1296 ], 1297 register: function(target) { 1298 dispatcher.listen(target, this.events); 1299 }, 1300 unregister: function(target) { 1301 dispatcher.unlisten(target, this.events); 1302 }, 1303 POINTER_TYPES: [ 1304 '', 1305 'unavailable', 1306 'touch', 1307 'pen', 1308 'mouse' 1309 ], 1310 prepareEvent: function(inEvent) { 1311 var e = inEvent; 1312 if (HAS_BITMAP_TYPE) { 1313 e = dispatcher.cloneEvent(inEvent); 1314 e.pointerType = this.POINTER_TYPES[inEvent.pointerType]; 1315 } 1316 return e; 1317 }, 1318 cleanup: function(id) { 1319 pointermap$2.delete(id); 1320 }, 1321 MSPointerDown: function(inEvent) { 1322 pointermap$2.set(inEvent.pointerId, inEvent); 1323 var e = this.prepareEvent(inEvent); 1324 dispatcher.down(e); 1325 }, 1326 MSPointerMove: function(inEvent) { 1327 var e = this.prepareEvent(inEvent); 1328 dispatcher.move(e); 1329 }, 1330 MSPointerUp: function(inEvent) { 1331 var e = this.prepareEvent(inEvent); 1332 dispatcher.up(e); 1333 this.cleanup(inEvent.pointerId); 1334 }, 1335 MSPointerOut: function(inEvent) { 1336 var e = this.prepareEvent(inEvent); 1337 dispatcher.leaveOut(e); 1338 }, 1339 MSPointerOver: function(inEvent) { 1340 var e = this.prepareEvent(inEvent); 1341 dispatcher.enterOver(e); 1342 }, 1343 MSPointerCancel: function(inEvent) { 1344 var e = this.prepareEvent(inEvent); 1345 dispatcher.cancel(e); 1346 this.cleanup(inEvent.pointerId); 1347 }, 1348 MSLostPointerCapture: function(inEvent) { 1349 var e = dispatcher.makeEvent('lostpointercapture', inEvent); 1350 dispatcher.dispatchEvent(e); 1351 }, 1352 MSGotPointerCapture: function(inEvent) { 1353 var e = dispatcher.makeEvent('gotpointercapture', inEvent); 1354 dispatcher.dispatchEvent(e); 1355 } 1356 }; 1357 1358 function applyPolyfill() { 1359 1360 // only activate if this platform does not have pointer events 1361 if (!window.PointerEvent) { 1362 window.PointerEvent = PointerEvent; 1363 1364 if (window.navigator.msPointerEnabled) { 1365 var tp = window.navigator.msMaxTouchPoints; 1366 Object.defineProperty(window.navigator, 'maxTouchPoints', { 1367 value: tp, 1368 enumerable: true 1369 }); 1370 dispatcher.registerSource('ms', msEvents); 1371 } else { 1372 Object.defineProperty(window.navigator, 'maxTouchPoints', { 1373 value: 0, 1374 enumerable: true 1375 });
1376 dispatcher.registerSource('mouse', mouseEvents); 1377 if (window.ontouchstart !== undefined) { 1378 dispatcher.registerSource('touch', touchEvents); 1379 } 1380 } 1381 1382 dispatcher.register(document); 1383 } 1384 } 1385 1386 var n = window.navigator; 1387 var s; 1388 var r; 1389 var h; 1390 function assertActive(id) { 1391 if (!dispatcher.pointermap.has(id)) { 1392 var error = new Error('InvalidPointerId'); 1393 error.name = 'InvalidPointerId'; 1394 throw error; 1395 } 1396 } 1397 function assertConnected(elem) { 1398 var parent = elem.parentNode; 1399 while (parent && parent !== elem.ownerDocument) { 1400 parent = parent.parentNode; 1401 } 1402 if (!parent) { 1403 var error = new Error('InvalidStateError'); 1404 error.name = 'InvalidStateError'; 1405 throw error; 1406 } 1407 } 1408 function inActiveButtonState(id) { 1409 var p = dispatcher.pointermap.get(id); 1410 return p.buttons !== 0; 1411 } 1412 if (n.msPointerEnabled) { 1413 s = function(pointerId) { 1414 assertActive(pointerId); 1415 assertConnected(this); 1416 if (inActiveButtonState(pointerId)) { 1417 dispatcher.setCapture(pointerId, this, true); 1418 this.msSetPointerCapture(pointerId); 1419 } 1420 }; 1421 r = function(pointerId) { 1422 assertActive(pointerId); 1423 dispatcher.releaseCapture(pointerId, true); 1424 this.msReleasePointerCapture(pointerId); 1425 }; 1426 } else { 1427 s = function setPointerCapture(pointerId) { 1428 assertActive(pointerId); 1429 assertConnected(this); 1430 if (inActiveButtonState(pointerId)) { 1431 dispatcher.setCapture(pointerId, this); 1432 } 1433 }; 1434 r = function releasePointerCapture(pointerId) { 1435 assertActive(pointerId); 1436 dispatcher.releaseCapture(pointerId); 1437 }; 1438 } 1439 h = function hasPointerCapture(pointerId) { 1440 return !!dispatcher.captureInfo[pointerId]; 1441 }; 1442 1443 function applyPolyfill$1() { 1444 if (window.Element && !Element.prototype.setPointerCapture) { 1445 Object.defineProperties(Element.prototype, { 1446 'setPointerCapture': { 1447 value: s 1448 }, 1449 'releasePointerCapture': { 1450 value: r 1451 }, 1452 'hasPointerCapture': { 1453 value: h 1454 } 1455 }); 1456 } 1457 } 1458 1459 applyAttributeStyles(); 1460 applyPolyfill(); 1461 applyPolyfill$1(); 1462 1463 var pointerevents = { 1464 dispatcher: dispatcher, 1465 Installer: Installer, 1466 PointerEvent: PointerEvent, 1467 PointerMap: PointerMap, 1468 targetFinding: targeting 1469 }; 1470 1471 return pointerevents; 1472 1473}));
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.