diff --git a/README.md b/README.md index 69363521..6f0a9875 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ PEP polyfills pointer events in all browsers that haven't yet implemented them, ## Getting Started 1. Place the PEP script in the document head - - `` + - `` 1. By default, no pointer events are sent from an element. This maximizes the possibility that a browser can deliver smooth scrolling and jank-free gestures. If you want to receive events, you must set the `touch-action` property of that element. Set up some elements to create events with the [`touch-action` attribute](http://www.w3.org/TR/pointerevents/#the-touch-action-css-property). @@ -30,7 +30,7 @@ PEP polyfills pointer events in all browsers that haven't yet implemented them, PEP (Pointer Events Polyfill) - + diff --git a/bower.json b/bower.json index d9d794fa..b450eeed 100644 --- a/bower.json +++ b/bower.json @@ -14,5 +14,6 @@ "**/.*", "node_modules", "test" - ] + ], + "version": "0.4.2" } diff --git a/dist/pep.js b/dist/pep.js new file mode 100644 index 00000000..5b12f64c --- /dev/null +++ b/dist/pep.js @@ -0,0 +1,1436 @@ +/*! + * PEP v0.4.2 | https://github.com/jquery/PEP + * Copyright jQuery Foundation and other contributors | http://jquery.org/license + */ + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.PointerEventsPolyfill = factory()); +}(this, function () { 'use strict'; + + /** + * This is the constructor for new PointerEvents. + * + * New Pointer Events must be given a type, and an optional dictionary of + * initialization properties. + * + * Due to certain platform requirements, events returned from the constructor + * identify as MouseEvents. + * + * @constructor + * @param {String} inType The type of the event to create. + * @param {Object} [inDict] An optional dictionary of initial event properties. + * @return {Event} A new PointerEvent of type `inType`, initialized with properties from `inDict`. + */ + var MOUSE_PROPS = [ + 'bubbles', + 'cancelable', + 'view', + 'detail', + 'screenX', + 'screenY', + 'clientX', + 'clientY', + 'ctrlKey', + 'altKey', + 'shiftKey', + 'metaKey', + 'button', + 'relatedTarget', + 'pageX', + 'pageY' + ]; + + var MOUSE_DEFAULTS = [ + false, + false, + null, + null, + 0, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + null, + 0, + 0 + ]; + + function PointerEvent(inType, inDict) { + inDict = inDict || Object.create(null); + + var e = document.createEvent('Event'); + e.initEvent(inType, inDict.bubbles || false, inDict.cancelable || false); + + // define inherited MouseEvent properties + // skip bubbles and cancelable since they're set above in initEvent() + for (var i = 2, p; i < MOUSE_PROPS.length; i++) { + p = MOUSE_PROPS[i]; + e[p] = inDict[p] || MOUSE_DEFAULTS[i]; + } + e.buttons = inDict.buttons || 0; + + // Spec requires that pointers without pressure specified use 0.5 for down + // state and 0 for up state. + var pressure = 0; + + if (inDict.pressure && e.buttons) { + pressure = inDict.pressure; + } else { + pressure = e.buttons ? 0.5 : 0; + } + + // add x/y properties aliased to clientX/Y + e.x = e.clientX; + e.y = e.clientY; + + // define the properties of the PointerEvent interface + e.pointerId = inDict.pointerId || 0; + e.width = inDict.width || 0; + e.height = inDict.height || 0; + e.pressure = pressure; + e.tiltX = inDict.tiltX || 0; + e.tiltY = inDict.tiltY || 0; + e.pointerType = inDict.pointerType || ''; + e.hwTimestamp = inDict.hwTimestamp || 0; + e.isPrimary = inDict.isPrimary || false; + return e; + } + + /** + * This module implements a map of pointer states + */ + var USE_MAP = window.Map && window.Map.prototype.forEach; + var PointerMap = USE_MAP ? Map : SparseArrayMap; + + function SparseArrayMap() { + this.array = []; + this.size = 0; + } + + SparseArrayMap.prototype = { + set: function(k, v) { + if (v === undefined) { + return this.delete(k); + } + if (!this.has(k)) { + this.size++; + } + this.array[k] = v; + }, + has: function(k) { + return this.array[k] !== undefined; + }, + delete: function(k) { + if (this.has(k)) { + delete this.array[k]; + this.size--; + } + }, + get: function(k) { + return this.array[k]; + }, + clear: function() { + this.array.length = 0; + this.size = 0; + }, + + // return value, key, map + forEach: function(callback, thisArg) { + return this.array.forEach(function(v, k) { + callback.call(thisArg, v, k, this); + }, this); + } + }; + + var CLONE_PROPS = [ + + // MouseEvent + 'bubbles', + 'cancelable', + 'view', + 'detail', + 'screenX', + 'screenY', + 'clientX', + 'clientY', + 'ctrlKey', + 'altKey', + 'shiftKey', + 'metaKey', + 'button', + 'relatedTarget', + + // DOM Level 3 + 'buttons', + + // PointerEvent + 'pointerId', + 'width', + 'height', + 'pressure', + 'tiltX', + 'tiltY', + 'pointerType', + 'hwTimestamp', + 'isPrimary', + + // event instance + 'type', + 'target', + 'currentTarget', + 'which', + 'pageX', + 'pageY', + 'timeStamp' + ]; + + var CLONE_DEFAULTS = [ + + // MouseEvent + false, + false, + null, + null, + 0, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + null, + + // DOM Level 3 + 0, + + // PointerEvent + 0, + 0, + 0, + 0, + 0, + 0, + '', + 0, + false, + + // event instance + '', + null, + null, + 0, + 0, + 0, + 0 + ]; + + var BOUNDARY_EVENTS = { + 'pointerover': 1, + 'pointerout': 1, + 'pointerenter': 1, + 'pointerleave': 1 + }; + + var HAS_SVG_INSTANCE = (typeof SVGElementInstance !== 'undefined'); + + /** + * This module is for normalizing events. Mouse and Touch events will be + * collected here, and fire PointerEvents that have the same semantics, no + * matter the source. + * Events fired: + * - pointerdown: a pointing is added + * - pointerup: a pointer is removed + * - pointermove: a pointer is moved + * - pointerover: a pointer crosses into an element + * - pointerout: a pointer leaves an element + * - pointercancel: a pointer will no longer generate events + */ + var dispatcher = { + pointermap: new PointerMap(), + eventMap: Object.create(null), + captureInfo: Object.create(null), + + // Scope objects for native events. + // This exists for ease of testing. + eventSources: Object.create(null), + eventSourceList: [], + /** + * Add a new event source that will generate pointer events. + * + * `inSource` must contain an array of event names named `events`, and + * functions with the names specified in the `events` array. + * @param {string} name A name for the event source + * @param {Object} source A new source of platform events. + */ + registerSource: function(name, source) { + var s = source; + var newEvents = s.events; + if (newEvents) { + newEvents.forEach(function(e) { + if (s[e]) { + this.eventMap[e] = s[e].bind(s); + } + }, this); + this.eventSources[name] = s; + this.eventSourceList.push(s); + } + }, + register: function(element) { + var l = this.eventSourceList.length; + for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { + + // call eventsource register + es.register.call(es, element); + } + }, + unregister: function(element) { + var l = this.eventSourceList.length; + for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { + + // call eventsource register + es.unregister.call(es, element); + } + }, + contains: /*scope.external.contains || */function(container, contained) { + try { + return container.contains(contained); + } catch (ex) { + + // most likely: https://bugzilla.mozilla.org/show_bug.cgi?id=208427 + return false; + } + }, + + // EVENTS + down: function(inEvent) { + inEvent.bubbles = true; + this.fireEvent('pointerdown', inEvent); + }, + move: function(inEvent) { + inEvent.bubbles = true; + this.fireEvent('pointermove', inEvent); + }, + up: function(inEvent) { + inEvent.bubbles = true; + this.fireEvent('pointerup', inEvent); + }, + enter: function(inEvent) { + inEvent.bubbles = false; + this.fireEvent('pointerenter', inEvent); + }, + leave: function(inEvent) { + inEvent.bubbles = false; + this.fireEvent('pointerleave', inEvent); + }, + over: function(inEvent) { + inEvent.bubbles = true; + this.fireEvent('pointerover', inEvent); + }, + out: function(inEvent) { + inEvent.bubbles = true; + this.fireEvent('pointerout', inEvent); + }, + cancel: function(inEvent) { + inEvent.bubbles = true; + this.fireEvent('pointercancel', inEvent); + }, + leaveOut: function(event) { + this.out(event); + this.propagate(event, this.leave, false); + }, + enterOver: function(event) { + this.over(event); + this.propagate(event, this.enter, true); + }, + + // LISTENER LOGIC + eventHandler: function(inEvent) { + + // This is used to prevent multiple dispatch of pointerevents from + // platform events. This can happen when two elements in different scopes + // are set up to create pointer events, which is relevant to Shadow DOM. + if (inEvent._handledByPE) { + return; + } + var type = inEvent.type; + var fn = this.eventMap && this.eventMap[type]; + if (fn) { + fn(inEvent); + } + inEvent._handledByPE = true; + }, + + // set up event listeners + listen: function(target, events) { + events.forEach(function(e) { + this.addEvent(target, e); + }, this); + }, + + // remove event listeners + unlisten: function(target, events) { + events.forEach(function(e) { + this.removeEvent(target, e); + }, this); + }, + addEvent: /*scope.external.addEvent || */function(target, eventName) { + target.addEventListener(eventName, this.boundHandler); + }, + removeEvent: /*scope.external.removeEvent || */function(target, eventName) { + target.removeEventListener(eventName, this.boundHandler); + }, + + // EVENT CREATION AND TRACKING + /** + * Creates a new Event of type `inType`, based on the information in + * `inEvent`. + * + * @param {string} inType A string representing the type of event to create + * @param {Event} inEvent A platform event with a target + * @return {Event} A PointerEvent of type `inType` + */ + makeEvent: function(inType, inEvent) { + + // relatedTarget must be null if pointer is captured + if (this.captureInfo[inEvent.pointerId]) { + inEvent.relatedTarget = null; + } + var e = new PointerEvent(inType, inEvent); + if (inEvent.preventDefault) { + e.preventDefault = inEvent.preventDefault; + } + e._target = e._target || inEvent.target; + return e; + }, + + // make and dispatch an event in one call + fireEvent: function(inType, inEvent) { + var e = this.makeEvent(inType, inEvent); + return this.dispatchEvent(e); + }, + /** + * Returns a snapshot of inEvent, with writable properties. + * + * @param {Event} inEvent An event that contains properties to copy. + * @return {Object} An object containing shallow copies of `inEvent`'s + * properties. + */ + cloneEvent: function(inEvent) { + var eventCopy = Object.create(null); + var p; + for (var i = 0; i < CLONE_PROPS.length; i++) { + p = CLONE_PROPS[i]; + eventCopy[p] = inEvent[p] || CLONE_DEFAULTS[i]; + + // Work around SVGInstanceElement shadow tree + // Return the element that is represented by the instance for Safari, Chrome, IE. + // This is the behavior implemented by Firefox. + if (HAS_SVG_INSTANCE && (p === 'target' || p === 'relatedTarget')) { + if (eventCopy[p] instanceof SVGElementInstance) { + eventCopy[p] = eventCopy[p].correspondingUseElement; + } + } + } + + // keep the semantics of preventDefault + if (inEvent.preventDefault) { + eventCopy.preventDefault = function() { + inEvent.preventDefault(); + }; + } + return eventCopy; + }, + getTarget: function(inEvent) { + var capture = this.captureInfo[inEvent.pointerId]; + if (!capture) { + return inEvent._target; + } + if (inEvent._target === capture || !(inEvent.type in BOUNDARY_EVENTS)) { + return capture; + } + }, + propagate: function(event, fn, propagateDown) { + var target = event.target; + var targets = []; + while (!target.contains(event.relatedTarget) && target !== document) { + targets.push(target); + target = target.parentNode; + } + if (propagateDown) { + targets.reverse(); + } + targets.forEach(function(target) { + event.target = target; + fn.call(this, event); + }, this); + }, + setCapture: function(inPointerId, inTarget) { + if (this.captureInfo[inPointerId]) { + this.releaseCapture(inPointerId); + } + this.captureInfo[inPointerId] = inTarget; + var e = new PointerEvent('gotpointercapture'); + e.pointerId = inPointerId; + this.implicitRelease = this.releaseCapture.bind(this, inPointerId); + document.addEventListener('pointerup', this.implicitRelease); + document.addEventListener('pointercancel', this.implicitRelease); + e._target = inTarget; + this.asyncDispatchEvent(e); + }, + releaseCapture: function(inPointerId) { + var t = this.captureInfo[inPointerId]; + if (t) { + var e = new PointerEvent('lostpointercapture'); + e.pointerId = inPointerId; + this.captureInfo[inPointerId] = undefined; + document.removeEventListener('pointerup', this.implicitRelease); + document.removeEventListener('pointercancel', this.implicitRelease); + e._target = t; + this.asyncDispatchEvent(e); + } + }, + /** + * Dispatches the event to its target. + * + * @param {Event} inEvent The event to be dispatched. + * @return {Boolean} True if an event handler returns true, false otherwise. + */ + dispatchEvent: /*scope.external.dispatchEvent || */function(inEvent) { + var t = this.getTarget(inEvent); + if (t) { + return t.dispatchEvent(inEvent); + } + }, + asyncDispatchEvent: function(inEvent) { + requestAnimationFrame(this.dispatchEvent.bind(this, inEvent)); + } + }; + dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher); + + var targeting = { + shadow: function(inEl) { + if (inEl) { + return inEl.shadowRoot || inEl.webkitShadowRoot; + } + }, + canTarget: function(shadow) { + return shadow && Boolean(shadow.elementFromPoint); + }, + targetingShadow: function(inEl) { + var s = this.shadow(inEl); + if (this.canTarget(s)) { + return s; + } + }, + olderShadow: function(shadow) { + var os = shadow.olderShadowRoot; + if (!os) { + var se = shadow.querySelector('shadow'); + if (se) { + os = se.olderShadowRoot; + } + } + return os; + }, + allShadows: function(element) { + var shadows = []; + var s = this.shadow(element); + while (s) { + shadows.push(s); + s = this.olderShadow(s); + } + return shadows; + }, + searchRoot: function(inRoot, x, y) { + if (inRoot) { + var t = inRoot.elementFromPoint(x, y); + var st, sr; + + // is element a shadow host? + sr = this.targetingShadow(t); + while (sr) { + + // find the the element inside the shadow root + st = sr.elementFromPoint(x, y); + if (!st) { + + // check for older shadows + sr = this.olderShadow(sr); + } else { + + // shadowed element may contain a shadow root + var ssr = this.targetingShadow(st); + return this.searchRoot(ssr, x, y) || st; + } + } + + // light dom element is the target + return t; + } + }, + owner: function(element) { + var s = element; + + // walk up until you hit the shadow root or document + while (s.parentNode) { + s = s.parentNode; + } + + // the owner element is expected to be a Document or ShadowRoot + if (s.nodeType !== Node.DOCUMENT_NODE && s.nodeType !== Node.DOCUMENT_FRAGMENT_NODE) { + s = document; + } + return s; + }, + findTarget: function(inEvent) { + var x = inEvent.clientX; + var y = inEvent.clientY; + + // if the listener is in the shadow root, it is much faster to start there + var s = this.owner(inEvent.target); + + // if x, y is not in this root, fall back to document search + if (!s.elementFromPoint(x, y)) { + s = document; + } + return this.searchRoot(s, x, y); + } + }; + + var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); + var map = Array.prototype.map.call.bind(Array.prototype.map); + var toArray = Array.prototype.slice.call.bind(Array.prototype.slice); + var filter = Array.prototype.filter.call.bind(Array.prototype.filter); + var MO = window.MutationObserver || window.WebKitMutationObserver; + var SELECTOR = '[touch-action]'; + var OBSERVER_INIT = { + subtree: true, + childList: true, + attributes: true, + attributeOldValue: true, + attributeFilter: ['touch-action'] + }; + + function Installer(add, remove, changed, binder) { + this.addCallback = add.bind(binder); + this.removeCallback = remove.bind(binder); + this.changedCallback = changed.bind(binder); + if (MO) { + this.observer = new MO(this.mutationWatcher.bind(this)); + } + } + + Installer.prototype = { + watchSubtree: function(target) { + + // Only watch scopes that can target find, as these are top-level. + // Otherwise we can see duplicate additions and removals that add noise. + // + // TODO(dfreedman): For some instances with ShadowDOMPolyfill, we can see + // a removal without an insertion when a node is redistributed among + // shadows. Since it all ends up correct in the document, watching only + // the document will yield the correct mutations to watch. + if (this.observer && targeting.canTarget(target)) { + this.observer.observe(target, OBSERVER_INIT); + } + }, + enableOnSubtree: function(target) { + this.watchSubtree(target); + if (target === document && document.readyState !== 'complete') { + this.installOnLoad(); + } else { + this.installNewSubtree(target); + } + }, + installNewSubtree: function(target) { + forEach(this.findElements(target), this.addElement, this); + }, + findElements: function(target) { + if (target.querySelectorAll) { + return target.querySelectorAll(SELECTOR); + } + return []; + }, + removeElement: function(el) { + this.removeCallback(el); + }, + addElement: function(el) { + this.addCallback(el); + }, + elementChanged: function(el, oldValue) { + this.changedCallback(el, oldValue); + }, + concatLists: function(accum, list) { + return accum.concat(toArray(list)); + }, + + // register all touch-action = none nodes on document load + installOnLoad: function() { + document.addEventListener('readystatechange', function() { + if (document.readyState === 'complete') { + this.installNewSubtree(document); + } + }.bind(this)); + }, + isElement: function(n) { + return n.nodeType === Node.ELEMENT_NODE; + }, + flattenMutationTree: function(inNodes) { + + // find children with touch-action + var tree = map(inNodes, this.findElements, this); + + // make sure the added nodes are accounted for + tree.push(filter(inNodes, this.isElement)); + + // flatten the list + return tree.reduce(this.concatLists, []); + }, + mutationWatcher: function(mutations) { + mutations.forEach(this.mutationHandler, this); + }, + mutationHandler: function(m) { + if (m.type === 'childList') { + var added = this.flattenMutationTree(m.addedNodes); + added.forEach(this.addElement, this); + var removed = this.flattenMutationTree(m.removedNodes); + removed.forEach(this.removeElement, this); + } else if (m.type === 'attributes') { + this.elementChanged(m.target, m.oldValue); + } + } + }; + + function shadowSelector(v) { + return 'body /shadow-deep/ ' + selector(v); + } + function selector(v) { + return '[touch-action="' + v + '"]'; + } + function rule(v) { + return '{ -ms-touch-action: ' + v + '; touch-action: ' + v + '; }'; + } + var attrib2css = [ + 'none', + 'auto', + 'pan-x', + 'pan-y', + { + rule: 'pan-x pan-y', + selectors: [ + 'pan-x pan-y', + 'pan-y pan-x' + ] + } + ]; + var styles = ''; + + // only install stylesheet if the browser has touch action support + var hasNativePE = window.PointerEvent || window.MSPointerEvent; + + // only add shadow selectors if shadowdom is supported + var hasShadowRoot = !window.ShadowDOMPolyfill && document.head.createShadowRoot; + + function applyAttributeStyles() { + if (hasNativePE) { + attrib2css.forEach(function(r) { + if (String(r) === r) { + styles += selector(r) + rule(r) + '\n'; + if (hasShadowRoot) { + styles += shadowSelector(r) + rule(r) + '\n'; + } + } else { + styles += r.selectors.map(selector) + rule(r.rule) + '\n'; + if (hasShadowRoot) { + styles += r.selectors.map(shadowSelector) + rule(r.rule) + '\n'; + } + } + }); + + var el = document.createElement('style'); + el.textContent = styles; + document.head.appendChild(el); + } + } + + var pointermap = dispatcher.pointermap; + + // radius around touchend that swallows mouse events + var DEDUP_DIST = 25; + + // left, middle, right, back, forward + var BUTTON_TO_BUTTONS = [1, 4, 2, 8, 16]; + + var HAS_BUTTONS = false; + try { + HAS_BUTTONS = new MouseEvent('test', { buttons: 1 }).buttons === 1; + } catch (e) {} + + // handler block for native mouse events + var mouseEvents = { + POINTER_ID: 1, + POINTER_TYPE: 'mouse', + events: [ + 'mousedown', + 'mousemove', + 'mouseup', + 'mouseover', + 'mouseout' + ], + register: function(target) { + dispatcher.listen(target, this.events); + }, + unregister: function(target) { + dispatcher.unlisten(target, this.events); + }, + lastTouches: [], + + // collide with the global mouse listener + isEventSimulatedFromTouch: function(inEvent) { + var lts = this.lastTouches; + var x = inEvent.clientX; + var y = inEvent.clientY; + for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) { + + // simulated mouse events will be swallowed near a primary touchend + var dx = Math.abs(x - t.x); + var dy = Math.abs(y - t.y); + if (dx <= DEDUP_DIST && dy <= DEDUP_DIST) { + return true; + } + } + }, + prepareEvent: function(inEvent) { + var e = dispatcher.cloneEvent(inEvent); + + // forward mouse preventDefault + var pd = e.preventDefault; + e.preventDefault = function() { + inEvent.preventDefault(); + pd(); + }; + e.pointerId = this.POINTER_ID; + e.isPrimary = true; + e.pointerType = this.POINTER_TYPE; + return e; + }, + prepareButtonsForMove: function(e, inEvent) { + var p = pointermap.get(this.POINTER_ID); + + // Update buttons state after possible out-of-document mouseup. + if (inEvent.which === 0 || !p) { + e.buttons = 0; + } else { + e.buttons = p.buttons; + } + inEvent.buttons = e.buttons; + }, + mousedown: function(inEvent) { + if (!this.isEventSimulatedFromTouch(inEvent)) { + var p = pointermap.get(this.POINTER_ID); + var e = this.prepareEvent(inEvent); + if (!HAS_BUTTONS) { + e.buttons = BUTTON_TO_BUTTONS[e.button]; + if (p) { e.buttons |= p.buttons; } + inEvent.buttons = e.buttons; + } + pointermap.set(this.POINTER_ID, inEvent); + if (!p || p.buttons === 0) { + dispatcher.down(e); + } else { + dispatcher.move(e); + } + } + }, + mousemove: function(inEvent) { + if (!this.isEventSimulatedFromTouch(inEvent)) { + var e = this.prepareEvent(inEvent); + if (!HAS_BUTTONS) { this.prepareButtonsForMove(e, inEvent); } + e.button = -1; + pointermap.set(this.POINTER_ID, inEvent); + dispatcher.move(e); + } + }, + mouseup: function(inEvent) { + if (!this.isEventSimulatedFromTouch(inEvent)) { + var p = pointermap.get(this.POINTER_ID); + var e = this.prepareEvent(inEvent); + if (!HAS_BUTTONS) { + var up = BUTTON_TO_BUTTONS[e.button]; + + // Produces wrong state of buttons in Browsers without `buttons` support + // when a mouse button that was pressed outside the document is released + // inside and other buttons are still pressed down. + e.buttons = p ? p.buttons & ~up : 0; + inEvent.buttons = e.buttons; + } + pointermap.set(this.POINTER_ID, inEvent); + + // Support: Firefox <=44 only + // FF Ubuntu includes the lifted button in the `buttons` property on + // mouseup. + // https://bugzilla.mozilla.org/show_bug.cgi?id=1223366 + e.buttons &= ~BUTTON_TO_BUTTONS[e.button]; + if (e.buttons === 0) { + dispatcher.up(e); + } else { + dispatcher.move(e); + } + } + }, + mouseover: function(inEvent) { + if (!this.isEventSimulatedFromTouch(inEvent)) { + var e = this.prepareEvent(inEvent); + if (!HAS_BUTTONS) { this.prepareButtonsForMove(e, inEvent); } + e.button = -1; + pointermap.set(this.POINTER_ID, inEvent); + dispatcher.enterOver(e); + } + }, + mouseout: function(inEvent) { + if (!this.isEventSimulatedFromTouch(inEvent)) { + var e = this.prepareEvent(inEvent); + if (!HAS_BUTTONS) { this.prepareButtonsForMove(e, inEvent); } + e.button = -1; + dispatcher.leaveOut(e); + } + }, + cancel: function(inEvent) { + var e = this.prepareEvent(inEvent); + dispatcher.cancel(e); + this.deactivateMouse(); + }, + deactivateMouse: function() { + pointermap.delete(this.POINTER_ID); + } + }; + + var captureInfo = dispatcher.captureInfo; + var findTarget = targeting.findTarget.bind(targeting); + var allShadows = targeting.allShadows.bind(targeting); + var pointermap$1 = dispatcher.pointermap; + + // This should be long enough to ignore compat mouse events made by touch + var DEDUP_TIMEOUT = 2500; + var CLICK_COUNT_TIMEOUT = 200; + var ATTRIB = 'touch-action'; + var INSTALLER; + + // handler block for native touch events + var touchEvents = { + events: [ + 'touchstart', + 'touchmove', + 'touchend', + 'touchcancel' + ], + register: function(target) { + INSTALLER.enableOnSubtree(target); + }, + unregister: function() { + + // TODO(dfreedman): is it worth it to disconnect the MO? + }, + elementAdded: function(el) { + var a = el.getAttribute(ATTRIB); + var st = this.touchActionToScrollType(a); + if (st) { + el._scrollType = st; + dispatcher.listen(el, this.events); + + // set touch-action on shadows as well + allShadows(el).forEach(function(s) { + s._scrollType = st; + dispatcher.listen(s, this.events); + }, this); + } + }, + elementRemoved: function(el) { + el._scrollType = undefined; + dispatcher.unlisten(el, this.events); + + // remove touch-action from shadow + allShadows(el).forEach(function(s) { + s._scrollType = undefined; + dispatcher.unlisten(s, this.events); + }, this); + }, + elementChanged: function(el, oldValue) { + var a = el.getAttribute(ATTRIB); + var st = this.touchActionToScrollType(a); + var oldSt = this.touchActionToScrollType(oldValue); + + // simply update scrollType if listeners are already established + if (st && oldSt) { + el._scrollType = st; + allShadows(el).forEach(function(s) { + s._scrollType = st; + }, this); + } else if (oldSt) { + this.elementRemoved(el); + } else if (st) { + this.elementAdded(el); + } + }, + scrollTypes: { + EMITTER: 'none', + XSCROLLER: 'pan-x', + YSCROLLER: 'pan-y', + SCROLLER: /^(?:pan-x pan-y)|(?:pan-y pan-x)|auto$/ + }, + touchActionToScrollType: function(touchAction) { + var t = touchAction; + var st = this.scrollTypes; + if (t === 'none') { + return 'none'; + } else if (t === st.XSCROLLER) { + return 'X'; + } else if (t === st.YSCROLLER) { + return 'Y'; + } else if (st.SCROLLER.exec(t)) { + return 'XY'; + } + }, + POINTER_TYPE: 'touch', + firstTouch: null, + isPrimaryTouch: function(inTouch) { + return this.firstTouch === inTouch.identifier; + }, + setPrimaryTouch: function(inTouch) { + + // set primary touch if there no pointers, or the only pointer is the mouse + if (pointermap$1.size === 0 || (pointermap$1.size === 1 && pointermap$1.has(1))) { + this.firstTouch = inTouch.identifier; + this.firstXY = { X: inTouch.clientX, Y: inTouch.clientY }; + this.scrolling = false; + this.cancelResetClickCount(); + } + }, + removePrimaryPointer: function(inPointer) { + if (inPointer.isPrimary) { + this.firstTouch = null; + this.firstXY = null; + this.resetClickCount(); + } + }, + clickCount: 0, + resetId: null, + resetClickCount: function() { + var fn = function() { + this.clickCount = 0; + this.resetId = null; + }.bind(this); + this.resetId = setTimeout(fn, CLICK_COUNT_TIMEOUT); + }, + cancelResetClickCount: function() { + if (this.resetId) { + clearTimeout(this.resetId); + } + }, + typeToButtons: function(type) { + var ret = 0; + if (type === 'touchstart' || type === 'touchmove') { + ret = 1; + } + return ret; + }, + touchToPointer: function(inTouch) { + var cte = this.currentTouchEvent; + var e = dispatcher.cloneEvent(inTouch); + + // We reserve pointerId 1 for Mouse. + // Touch identifiers can start at 0. + // Add 2 to the touch identifier for compatibility. + var id = e.pointerId = inTouch.identifier + 2; + e.target = captureInfo[id] || findTarget(e); + e.bubbles = true; + e.cancelable = true; + e.detail = this.clickCount; + e.button = 0; + e.buttons = this.typeToButtons(cte.type); + e.width = inTouch.radiusX || inTouch.webkitRadiusX || 0; + e.height = inTouch.radiusY || inTouch.webkitRadiusY || 0; + e.pressure = inTouch.force || inTouch.webkitForce || 0.5; + e.isPrimary = this.isPrimaryTouch(inTouch); + e.pointerType = this.POINTER_TYPE; + + // forward modifier keys + e.altKey = cte.altKey; + e.ctrlKey = cte.ctrlKey; + e.metaKey = cte.metaKey; + e.shiftKey = cte.shiftKey; + + // forward touch preventDefaults + var self = this; + e.preventDefault = function() { + self.scrolling = false; + self.firstXY = null; + cte.preventDefault(); + }; + return e; + }, + processTouches: function(inEvent, inFunction) { + var tl = inEvent.changedTouches; + this.currentTouchEvent = inEvent; + for (var i = 0, t; i < tl.length; i++) { + t = tl[i]; + inFunction.call(this, this.touchToPointer(t)); + } + }, + + // For single axis scrollers, determines whether the element should emit + // pointer events or behave as a scroller + shouldScroll: function(inEvent) { + if (this.firstXY) { + var ret; + var scrollAxis = inEvent.currentTarget._scrollType; + if (scrollAxis === 'none') { + + // this element is a touch-action: none, should never scroll + ret = false; + } else if (scrollAxis === 'XY') { + + // this element should always scroll + ret = true; + } else { + var t = inEvent.changedTouches[0]; + + // check the intended scroll axis, and other axis + var a = scrollAxis; + var oa = scrollAxis === 'Y' ? 'X' : 'Y'; + var da = Math.abs(t['client' + a] - this.firstXY[a]); + var doa = Math.abs(t['client' + oa] - this.firstXY[oa]); + + // if delta in the scroll axis > delta other axis, scroll instead of + // making events + ret = da >= doa; + } + this.firstXY = null; + return ret; + } + }, + findTouch: function(inTL, inId) { + for (var i = 0, l = inTL.length, t; i < l && (t = inTL[i]); i++) { + if (t.identifier === inId) { + return true; + } + } + }, + + // In some instances, a touchstart can happen without a touchend. This + // leaves the pointermap in a broken state. + // Therefore, on every touchstart, we remove the touches that did not fire a + // touchend event. + // To keep state globally consistent, we fire a + // pointercancel for this "abandoned" touch + vacuumTouches: function(inEvent) { + var tl = inEvent.touches; + + // pointermap.size should be < tl.length here, as the touchstart has not + // been processed yet. + if (pointermap$1.size >= tl.length) { + var d = []; + pointermap$1.forEach(function(value, key) { + + // Never remove pointerId == 1, which is mouse. + // Touch identifiers are 2 smaller than their pointerId, which is the + // index in pointermap. + if (key !== 1 && !this.findTouch(tl, key - 2)) { + var p = value.out; + d.push(p); + } + }, this); + d.forEach(this.cancelOut, this); + } + }, + touchstart: function(inEvent) { + this.vacuumTouches(inEvent); + this.setPrimaryTouch(inEvent.changedTouches[0]); + this.dedupSynthMouse(inEvent); + if (!this.scrolling) { + this.clickCount++; + this.processTouches(inEvent, this.overDown); + } + }, + overDown: function(inPointer) { + pointermap$1.set(inPointer.pointerId, { + target: inPointer.target, + out: inPointer, + outTarget: inPointer.target + }); + dispatcher.enterOver(inPointer); + dispatcher.down(inPointer); + }, + touchmove: function(inEvent) { + if (!this.scrolling) { + if (this.shouldScroll(inEvent)) { + this.scrolling = true; + this.touchcancel(inEvent); + } else { + inEvent.preventDefault(); + this.processTouches(inEvent, this.moveOverOut); + } + } + }, + moveOverOut: function(inPointer) { + var event = inPointer; + var pointer = pointermap$1.get(event.pointerId); + + // a finger drifted off the screen, ignore it + if (!pointer) { + return; + } + var outEvent = pointer.out; + var outTarget = pointer.outTarget; + dispatcher.move(event); + if (outEvent && outTarget !== event.target) { + outEvent.relatedTarget = event.target; + event.relatedTarget = outTarget; + + // recover from retargeting by shadow + outEvent.target = outTarget; + if (event.target) { + dispatcher.leaveOut(outEvent); + dispatcher.enterOver(event); + } else { + + // clean up case when finger leaves the screen + event.target = outTarget; + event.relatedTarget = null; + this.cancelOut(event); + } + } + pointer.out = event; + pointer.outTarget = event.target; + }, + touchend: function(inEvent) { + this.dedupSynthMouse(inEvent); + this.processTouches(inEvent, this.upOut); + }, + upOut: function(inPointer) { + if (!this.scrolling) { + dispatcher.up(inPointer); + dispatcher.leaveOut(inPointer); + } + this.cleanUpPointer(inPointer); + }, + touchcancel: function(inEvent) { + this.processTouches(inEvent, this.cancelOut); + }, + cancelOut: function(inPointer) { + dispatcher.cancel(inPointer); + dispatcher.leaveOut(inPointer); + this.cleanUpPointer(inPointer); + }, + cleanUpPointer: function(inPointer) { + pointermap$1.delete(inPointer.pointerId); + this.removePrimaryPointer(inPointer); + }, + + // prevent synth mouse events from creating pointer events + dedupSynthMouse: function(inEvent) { + var lts = mouseEvents.lastTouches; + var t = inEvent.changedTouches[0]; + + // only the primary finger will synth mouse events + if (this.isPrimaryTouch(t)) { + + // remember x/y of last touch + var lt = { x: t.clientX, y: t.clientY }; + lts.push(lt); + var fn = (function(lts, lt) { + var i = lts.indexOf(lt); + if (i > -1) { + lts.splice(i, 1); + } + }).bind(null, lts, lt); + setTimeout(fn, DEDUP_TIMEOUT); + } + } + }; + + INSTALLER = new Installer(touchEvents.elementAdded, touchEvents.elementRemoved, + touchEvents.elementChanged, touchEvents); + + var pointermap$2 = dispatcher.pointermap; + var HAS_BITMAP_TYPE = window.MSPointerEvent && + typeof window.MSPointerEvent.MSPOINTER_TYPE_MOUSE === 'number'; + var msEvents = { + events: [ + 'MSPointerDown', + 'MSPointerMove', + 'MSPointerUp', + 'MSPointerOut', + 'MSPointerOver', + 'MSPointerCancel', + 'MSGotPointerCapture', + 'MSLostPointerCapture' + ], + register: function(target) { + dispatcher.listen(target, this.events); + }, + unregister: function(target) { + dispatcher.unlisten(target, this.events); + }, + POINTER_TYPES: [ + '', + 'unavailable', + 'touch', + 'pen', + 'mouse' + ], + prepareEvent: function(inEvent) { + var e = inEvent; + if (HAS_BITMAP_TYPE) { + e = dispatcher.cloneEvent(inEvent); + e.pointerType = this.POINTER_TYPES[inEvent.pointerType]; + } + return e; + }, + cleanup: function(id) { + pointermap$2.delete(id); + }, + MSPointerDown: function(inEvent) { + pointermap$2.set(inEvent.pointerId, inEvent); + var e = this.prepareEvent(inEvent); + dispatcher.down(e); + }, + MSPointerMove: function(inEvent) { + var e = this.prepareEvent(inEvent); + dispatcher.move(e); + }, + MSPointerUp: function(inEvent) { + var e = this.prepareEvent(inEvent); + dispatcher.up(e); + this.cleanup(inEvent.pointerId); + }, + MSPointerOut: function(inEvent) { + var e = this.prepareEvent(inEvent); + dispatcher.leaveOut(e); + }, + MSPointerOver: function(inEvent) { + var e = this.prepareEvent(inEvent); + dispatcher.enterOver(e); + }, + MSPointerCancel: function(inEvent) { + var e = this.prepareEvent(inEvent); + dispatcher.cancel(e); + this.cleanup(inEvent.pointerId); + }, + MSLostPointerCapture: function(inEvent) { + var e = dispatcher.makeEvent('lostpointercapture', inEvent); + dispatcher.dispatchEvent(e); + }, + MSGotPointerCapture: function(inEvent) { + var e = dispatcher.makeEvent('gotpointercapture', inEvent); + dispatcher.dispatchEvent(e); + } + }; + + function applyPolyfill() { + + // only activate if this platform does not have pointer events + if (!window.PointerEvent) { + window.PointerEvent = PointerEvent; + + if (window.navigator.msPointerEnabled) { + var tp = window.navigator.msMaxTouchPoints; + Object.defineProperty(window.navigator, 'maxTouchPoints', { + value: tp, + enumerable: true + }); + dispatcher.registerSource('ms', msEvents); + } else { + dispatcher.registerSource('mouse', mouseEvents); + if (window.ontouchstart !== undefined) { + dispatcher.registerSource('touch', touchEvents); + } + } + + dispatcher.register(document); + } + } + + var n = window.navigator; + var s; + var r; + function assertActive(id) { + if (!dispatcher.pointermap.has(id)) { + var error = new Error('InvalidPointerId'); + error.name = 'InvalidPointerId'; + throw error; + } + } + function assertConnected(elem) { + if (!elem.ownerDocument.contains(elem)) { + var error = new Error('InvalidStateError'); + error.name = 'InvalidStateError'; + throw error; + } + } + function inActiveButtonState(id) { + var p = dispatcher.pointermap.get(id); + return p.buttons !== 0; + } + if (n.msPointerEnabled) { + s = function(pointerId) { + assertActive(pointerId); + assertConnected(this); + if (inActiveButtonState(pointerId)) { + this.msSetPointerCapture(pointerId); + } + }; + r = function(pointerId) { + assertActive(pointerId); + this.msReleasePointerCapture(pointerId); + }; + } else { + s = function setPointerCapture(pointerId) { + assertActive(pointerId); + assertConnected(this); + if (inActiveButtonState(pointerId)) { + dispatcher.setCapture(pointerId, this); + } + }; + r = function releasePointerCapture(pointerId) { + assertActive(pointerId); + dispatcher.releaseCapture(pointerId, this); + }; + } + + function applyPolyfill$1() { + if (window.Element && !Element.prototype.setPointerCapture) { + Object.defineProperties(Element.prototype, { + 'setPointerCapture': { + value: s + }, + 'releasePointerCapture': { + value: r + } + }); + } + } + + applyAttributeStyles(); + applyPolyfill(); + applyPolyfill$1(); + + var pointerevents = { + dispatcher: dispatcher, + Installer: Installer, + PointerEvent: PointerEvent, + PointerMap: PointerMap, + targetFinding: targeting + }; + + return pointerevents; + +})); \ No newline at end of file diff --git a/dist/pep.min.js b/dist/pep.min.js new file mode 100644 index 00000000..d7c993c9 --- /dev/null +++ b/dist/pep.min.js @@ -0,0 +1,206 @@ +/*! + * PEP v0.4.2 | https://github.com/jquery/PEP + * Copyright jQuery Foundation and other contributors | http://jquery.org/license + */ +!function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.PointerEventsPolyfill=b()}(this,function(){"use strict";function a(a,b){b=b||Object.create(null);var c=document.createEvent("Event");c.initEvent(a,b.bubbles||!1,b.cancelable||!1); +// define inherited MouseEvent properties +// skip bubbles and cancelable since they're set above in initEvent() +for(var d,e=2;e element that is represented by the instance for Safari, Chrome, IE. +// This is the behavior implemented by Firefox. +!t||"target"!==b&&"relatedTarget"!==b||c[b]instanceof SVGElementInstance&&(c[b]=c[b].correspondingUseElement); +// keep the semantics of preventDefault +return a.preventDefault&&(c.preventDefault=function(){a.preventDefault()}),c},getTarget:function(a){var b=this.captureInfo[a.pointerId];return b?a._target!==b&&a.type in s?void 0:b:a._target},propagate:function(a,b,c){for(var d=a.target,e=[];!d.contains(a.relatedTarget)&&d!==document;)e.push(d),d=d.parentNode;c&&e.reverse(),e.forEach(function(c){a.target=c,b.call(this,a)},this)},setCapture:function(b,c){this.captureInfo[b]&&this.releaseCapture(b),this.captureInfo[b]=c;var d=new a("gotpointercapture");d.pointerId=b,this.implicitRelease=this.releaseCapture.bind(this,b),document.addEventListener("pointerup",this.implicitRelease),document.addEventListener("pointercancel",this.implicitRelease),d._target=c,this.asyncDispatchEvent(d)},releaseCapture:function(b){var c=this.captureInfo[b];if(c){var d=new a("lostpointercapture");d.pointerId=b,this.captureInfo[b]=void 0,document.removeEventListener("pointerup",this.implicitRelease),document.removeEventListener("pointercancel",this.implicitRelease),d._target=c,this.asyncDispatchEvent(d)}},/** + * Dispatches the event to its target. + * + * @param {Event} inEvent The event to be dispatched. + * @return {Boolean} True if an event handler returns true, false otherwise. + */ +dispatchEvent:/*scope.external.dispatchEvent || */function(a){var b=this.getTarget(a);if(b)return b.dispatchEvent(a)},asyncDispatchEvent:function(a){requestAnimationFrame(this.dispatchEvent.bind(this,a))}};u.boundHandler=u.eventHandler.bind(u);var v={shadow:function(a){if(a)return a.shadowRoot||a.webkitShadowRoot},canTarget:function(a){return a&&Boolean(a.elementFromPoint)},targetingShadow:function(a){var b=this.shadow(a);if(this.canTarget(b))return b},olderShadow:function(a){var b=a.olderShadowRoot;if(!b){var c=a.querySelector("shadow");c&&(b=c.olderShadowRoot)}return b},allShadows:function(a){for(var b=[],c=this.shadow(a);c;)b.push(c),c=this.olderShadow(c);return b},searchRoot:function(a,b,c){if(a){var d,e,f=a.elementFromPoint(b,c);for( +// is element a shadow host? +e=this.targetingShadow(f);e;){if( +// find the the element inside the shadow root +d=e.elementFromPoint(b,c)){ +// shadowed element may contain a shadow root +var g=this.targetingShadow(d);return this.searchRoot(g,b,c)||d} +// check for older shadows +e=this.olderShadow(e)} +// light dom element is the target +return f}},owner:function(a){ +// walk up until you hit the shadow root or document +for(var b=a;b.parentNode;)b=b.parentNode; +// the owner element is expected to be a Document or ShadowRoot +return b.nodeType!==Node.DOCUMENT_NODE&&b.nodeType!==Node.DOCUMENT_FRAGMENT_NODE&&(b=document),b},findTarget:function(a){var b=a.clientX,c=a.clientY,d=this.owner(a.target); +// if x, y is not in this root, fall back to document search +return d.elementFromPoint(b,c)||(d=document),this.searchRoot(d,b,c)}},w=Array.prototype.forEach.call.bind(Array.prototype.forEach),x=Array.prototype.map.call.bind(Array.prototype.map),y=Array.prototype.slice.call.bind(Array.prototype.slice),z=Array.prototype.filter.call.bind(Array.prototype.filter),A=window.MutationObserver||window.WebKitMutationObserver,B="[touch-action]",C={subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0,attributeFilter:["touch-action"]};c.prototype={watchSubtree:function(a){ +// Only watch scopes that can target find, as these are top-level. +// Otherwise we can see duplicate additions and removals that add noise. +// +// TODO(dfreedman): For some instances with ShadowDOMPolyfill, we can see +// a removal without an insertion when a node is redistributed among +// shadows. Since it all ends up correct in the document, watching only +// the document will yield the correct mutations to watch. +this.observer&&v.canTarget(a)&&this.observer.observe(a,C)},enableOnSubtree:function(a){this.watchSubtree(a),a===document&&"complete"!==document.readyState?this.installOnLoad():this.installNewSubtree(a)},installNewSubtree:function(a){w(this.findElements(a),this.addElement,this)},findElements:function(a){return a.querySelectorAll?a.querySelectorAll(B):[]},removeElement:function(a){this.removeCallback(a)},addElement:function(a){this.addCallback(a)},elementChanged:function(a,b){this.changedCallback(a,b)},concatLists:function(a,b){return a.concat(y(b))}, +// register all touch-action = none nodes on document load +installOnLoad:function(){document.addEventListener("readystatechange",function(){"complete"===document.readyState&&this.installNewSubtree(document)}.bind(this))},isElement:function(a){return a.nodeType===Node.ELEMENT_NODE},flattenMutationTree:function(a){ +// find children with touch-action +var b=x(a,this.findElements,this); +// flatten the list +// make sure the added nodes are accounted for +return b.push(z(a,this.isElement)),b.reduce(this.concatLists,[])},mutationWatcher:function(a){a.forEach(this.mutationHandler,this)},mutationHandler:function(a){if("childList"===a.type){var b=this.flattenMutationTree(a.addedNodes);b.forEach(this.addElement,this);var c=this.flattenMutationTree(a.removedNodes);c.forEach(this.removeElement,this)}else"attributes"===a.type&&this.elementChanged(a.target,a.oldValue)}};var D=["none","auto","pan-x","pan-y",{rule:"pan-x pan-y",selectors:["pan-x pan-y","pan-y pan-x"]}],E="",F=window.PointerEvent||window.MSPointerEvent,G=!window.ShadowDOMPolyfill&&document.head.createShadowRoot,H=u.pointermap,I=25,J=[1,4,2,8,16],K=!1;try{K=1===new MouseEvent("test",{buttons:1}).buttons}catch(L){} +// handler block for native mouse events +var M,N={POINTER_ID:1,POINTER_TYPE:"mouse",events:["mousedown","mousemove","mouseup","mouseover","mouseout"],register:function(a){u.listen(a,this.events)},unregister:function(a){u.unlisten(a,this.events)},lastTouches:[], +// collide with the global mouse listener +isEventSimulatedFromTouch:function(a){for(var b,c=this.lastTouches,d=a.clientX,e=a.clientY,f=0,g=c.length;f delta other axis, scroll instead of +// making events +b=g>=h}return this.firstXY=null,b}},findTouch:function(a,b){for(var c,d=0,e=a.length;d=b.length){var c=[];R.forEach(function(a,d){ +// Never remove pointerId == 1, which is mouse. +// Touch identifiers are 2 smaller than their pointerId, which is the +// index in pointermap. +if(1!==d&&!this.findTouch(b,d-2)){var e=a.out;c.push(e)}},this),c.forEach(this.cancelOut,this)}},touchstart:function(a){this.vacuumTouches(a),this.setPrimaryTouch(a.changedTouches[0]),this.dedupSynthMouse(a),this.scrolling||(this.clickCount++,this.processTouches(a,this.overDown))},overDown:function(a){R.set(a.pointerId,{target:a.target,out:a,outTarget:a.target}),u.enterOver(a),u.down(a)},touchmove:function(a){this.scrolling||(this.shouldScroll(a)?(this.scrolling=!0,this.touchcancel(a)):(a.preventDefault(),this.processTouches(a,this.moveOverOut)))},moveOverOut:function(a){var b=a,c=R.get(b.pointerId); +// a finger drifted off the screen, ignore it +if(c){var d=c.out,e=c.outTarget;u.move(b),d&&e!==b.target&&(d.relatedTarget=b.target,b.relatedTarget=e, +// recover from retargeting by shadow +d.target=e,b.target?(u.leaveOut(d),u.enterOver(b)):( +// clean up case when finger leaves the screen +b.target=e,b.relatedTarget=null,this.cancelOut(b))),c.out=b,c.outTarget=b.target}},touchend:function(a){this.dedupSynthMouse(a),this.processTouches(a,this.upOut)},upOut:function(a){this.scrolling||(u.up(a),u.leaveOut(a)),this.cleanUpPointer(a)},touchcancel:function(a){this.processTouches(a,this.cancelOut)},cancelOut:function(a){u.cancel(a),u.leaveOut(a),this.cleanUpPointer(a)},cleanUpPointer:function(a){R["delete"](a.pointerId),this.removePrimaryPointer(a)}, +// prevent synth mouse events from creating pointer events +dedupSynthMouse:function(a){var b=N.lastTouches,c=a.changedTouches[0]; +// only the primary finger will synth mouse events +if(this.isPrimaryTouch(c)){ +// remember x/y of last touch +var d={x:c.clientX,y:c.clientY};b.push(d);var e=function(a,b){var c=a.indexOf(b);c>-1&&a.splice(c,1)}.bind(null,b,d);setTimeout(e,S)}}};M=new c(V.elementAdded,V.elementRemoved,V.elementChanged,V);var W,X,Y=u.pointermap,Z=window.MSPointerEvent&&"number"==typeof window.MSPointerEvent.MSPOINTER_TYPE_MOUSE,$={events:["MSPointerDown","MSPointerMove","MSPointerUp","MSPointerOut","MSPointerOver","MSPointerCancel","MSGotPointerCapture","MSLostPointerCapture"],register:function(a){u.listen(a,this.events)},unregister:function(a){u.unlisten(a,this.events)},POINTER_TYPES:["","unavailable","touch","pen","mouse"],prepareEvent:function(a){var b=a;return Z&&(b=u.cloneEvent(a),b.pointerType=this.POINTER_TYPES[a.pointerType]),b},cleanup:function(a){Y["delete"](a)},MSPointerDown:function(a){Y.set(a.pointerId,a);var b=this.prepareEvent(a);u.down(b)},MSPointerMove:function(a){var b=this.prepareEvent(a);u.move(b)},MSPointerUp:function(a){var b=this.prepareEvent(a);u.up(b),this.cleanup(a.pointerId)},MSPointerOut:function(a){var b=this.prepareEvent(a);u.leaveOut(b)},MSPointerOver:function(a){var b=this.prepareEvent(a);u.enterOver(b)},MSPointerCancel:function(a){var b=this.prepareEvent(a);u.cancel(b),this.cleanup(a.pointerId)},MSLostPointerCapture:function(a){var b=u.makeEvent("lostpointercapture",a);u.dispatchEvent(b)},MSGotPointerCapture:function(a){var b=u.makeEvent("gotpointercapture",a);u.dispatchEvent(b)}},_=window.navigator;_.msPointerEnabled?(W=function(a){i(a),j(this),k(a)&&this.msSetPointerCapture(a)},X=function(a){i(a),this.msReleasePointerCapture(a)}):(W=function(a){i(a),j(this),k(a)&&u.setCapture(a,this)},X=function(a){i(a),u.releaseCapture(a,this)}),g(),h(),l();var aa={dispatcher:u,Installer:c,PointerEvent:a,PointerMap:p,targetFinding:v};return aa}); \ No newline at end of file diff --git a/package.json b/package.json index 4a48a2d9..6665d228 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "pepjs", - "version": "0.4.2-pre", + "version": "0.4.2", "main": "dist/pep.js", "description": "Polyfill of the PointerEvents W3C spec", "author": { "name": "jQuery Foundation and other contributors", - "url": "https://github.com/jquery/PEP/blob/master/AUTHORS.txt" + "url": "https://github.com/jquery/PEP/blob/0.4.2/AUTHORS.txt" }, "repository": { "type": "git",