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)
-
+
Test button!
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",