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