1330 lines
39 KiB
JavaScript
1330 lines
39 KiB
JavaScript
/*!
|
|
* PEP v0.3.0 | 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 module implements an map of pointer states
|
|
*/
|
|
var USE_MAP = window.Map && window.Map.prototype.forEach;
|
|
var POINTERS_FN = function(){ return this.size; };
|
|
function PointerMap() {
|
|
if (USE_MAP) {
|
|
var m = new Map();
|
|
m.pointers = POINTERS_FN;
|
|
return m;
|
|
} else {
|
|
this.keys = [];
|
|
this.values = [];
|
|
}
|
|
}
|
|
|
|
PointerMap.prototype = {
|
|
set: function(inId, inEvent) {
|
|
var i = this.keys.indexOf(inId);
|
|
if (i > -1) {
|
|
this.values[i] = inEvent;
|
|
} else {
|
|
this.keys.push(inId);
|
|
this.values.push(inEvent);
|
|
}
|
|
},
|
|
has: function(inId) {
|
|
return this.keys.indexOf(inId) > -1;
|
|
},
|
|
'delete': function(inId) {
|
|
var i = this.keys.indexOf(inId);
|
|
if (i > -1) {
|
|
this.keys.splice(i, 1);
|
|
this.values.splice(i, 1);
|
|
}
|
|
},
|
|
get: function(inId) {
|
|
var i = this.keys.indexOf(inId);
|
|
return this.values[i];
|
|
},
|
|
clear: function() {
|
|
this.keys.length = 0;
|
|
this.values.length = 0;
|
|
},
|
|
// return value, key, map
|
|
forEach: function(callback, thisArg) {
|
|
this.values.forEach(function(v, i) {
|
|
callback.call(thisArg, v, this.keys[i], this);
|
|
}, this);
|
|
},
|
|
pointers: function() {
|
|
return this.keys.length;
|
|
}
|
|
};
|
|
|
|
var _pointermap = PointerMap;
|
|
|
|
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 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) {
|
|
return container.contains(contained);
|
|
},
|
|
// 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);
|
|
if (!this.contains(event.target, event.relatedTarget)) {
|
|
this.leave(event);
|
|
}
|
|
},
|
|
enterOver: function(event) {
|
|
this.over(event);
|
|
if (!this.contains(event.target, event.relatedTarget)) {
|
|
this.enter(event);
|
|
}
|
|
},
|
|
// 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), 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 <use> 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) {
|
|
// if pointer capture is set, route all events for the specified pointerId
|
|
// to the capture target
|
|
return this.captureInfo[inEvent.pointerId] || inEvent._target;
|
|
},
|
|
setCapture: function(inPointerId, inTarget) {
|
|
if (this.captureInfo[inPointerId]) {
|
|
this.releaseCapture(inPointerId);
|
|
}
|
|
this.captureInfo[inPointerId] = inTarget;
|
|
var e = document.createEvent('Event');
|
|
e.initEvent('gotpointercapture', true, false);
|
|
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 = document.createEvent('Event');
|
|
e.initEvent('lostpointercapture', true, false);
|
|
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 _dispatcher = 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 = [], 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, os;
|
|
// 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, 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);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* This module uses Mutation Observers to dynamically adjust which nodes will
|
|
* generate Pointer Events.
|
|
*
|
|
* All nodes that wish to generate Pointer Events must have the attribute
|
|
* `touch-action` set to `none`.
|
|
*/
|
|
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 (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);
|
|
}
|
|
}
|
|
};
|
|
|
|
if (!MO) {
|
|
Installer.prototype.watchSubtree = function(){
|
|
console.warn('PointerEventsPolyfill: MutationObservers not found, touch-action will not be dynamically detected');
|
|
};
|
|
}
|
|
|
|
var installer = Installer;
|
|
|
|
/**
|
|
* 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` and 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__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) {
|
|
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;
|
|
}
|
|
|
|
var _PointerEvent = _PointerEvent__PointerEvent;
|
|
|
|
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 + '; touch-action-delay: none; }';
|
|
}
|
|
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 head = document.head;
|
|
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 mouse__pointermap = _dispatcher.pointermap;
|
|
// radius around touchend that swallows mouse events
|
|
var DEDUP_DIST = 25;
|
|
|
|
var WHICH_TO_BUTTONS = [0, 1, 4, 2];
|
|
|
|
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, 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), 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;
|
|
if (!HAS_BUTTONS) {
|
|
e.buttons = WHICH_TO_BUTTONS[e.which] || 0;
|
|
}
|
|
return e;
|
|
},
|
|
mousedown: function(inEvent) {
|
|
if (!this.isEventSimulatedFromTouch(inEvent)) {
|
|
var p = mouse__pointermap.has(this.POINTER_ID);
|
|
// TODO(dfreedman) workaround for some elements not sending mouseup
|
|
// http://crbug/149091
|
|
if (p) {
|
|
this.cancel(inEvent);
|
|
}
|
|
var e = this.prepareEvent(inEvent);
|
|
mouse__pointermap.set(this.POINTER_ID, inEvent);
|
|
_dispatcher.down(e);
|
|
}
|
|
},
|
|
mousemove: function(inEvent) {
|
|
if (!this.isEventSimulatedFromTouch(inEvent)) {
|
|
var e = this.prepareEvent(inEvent);
|
|
_dispatcher.move(e);
|
|
}
|
|
},
|
|
mouseup: function(inEvent) {
|
|
if (!this.isEventSimulatedFromTouch(inEvent)) {
|
|
var p = mouse__pointermap.get(this.POINTER_ID);
|
|
if (p && p.button === inEvent.button) {
|
|
var e = this.prepareEvent(inEvent);
|
|
_dispatcher.up(e);
|
|
this.cleanupMouse();
|
|
}
|
|
}
|
|
},
|
|
mouseover: function(inEvent) {
|
|
if (!this.isEventSimulatedFromTouch(inEvent)) {
|
|
var e = this.prepareEvent(inEvent);
|
|
_dispatcher.enterOver(e);
|
|
}
|
|
},
|
|
mouseout: function(inEvent) {
|
|
if (!this.isEventSimulatedFromTouch(inEvent)) {
|
|
var e = this.prepareEvent(inEvent);
|
|
_dispatcher.leaveOut(e);
|
|
}
|
|
},
|
|
cancel: function(inEvent) {
|
|
var e = this.prepareEvent(inEvent);
|
|
_dispatcher.cancel(e);
|
|
this.cleanupMouse();
|
|
},
|
|
cleanupMouse: function() {
|
|
mouse__pointermap['delete'](this.POINTER_ID);
|
|
}
|
|
};
|
|
|
|
var mouse = mouseEvents;
|
|
|
|
var captureInfo = _dispatcher.captureInfo;
|
|
var findTarget = targeting.findTarget.bind(targeting);
|
|
var allShadows = targeting.allShadows.bind(targeting);
|
|
var touch__pointermap = _dispatcher.pointermap;
|
|
var touchMap = Array.prototype.map.call.bind(Array.prototype.map);
|
|
// 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;
|
|
// The presence of touch event handlers blocks scrolling, and so we must be careful to
|
|
// avoid adding handlers unnecessarily. Chrome plans to add a touch-action-delay property
|
|
// (crbug.com/329559) to address this, and once we have that we can opt-in to a simpler
|
|
// handler registration mechanism. Rather than try to predict how exactly to opt-in to
|
|
// that we'll just leave this disabled until there is a build of Chrome to test.
|
|
var HAS_TOUCH_ACTION_DELAY = false;
|
|
|
|
// handler block for native touch events
|
|
var touchEvents = {
|
|
events: [
|
|
'touchstart',
|
|
'touchmove',
|
|
'touchend',
|
|
'touchcancel'
|
|
],
|
|
register: function(target) {
|
|
if (HAS_TOUCH_ACTION_DELAY) {
|
|
_dispatcher.listen(target, this.events);
|
|
} else {
|
|
INSTALLER.enableOnSubtree(target);
|
|
}
|
|
},
|
|
unregister: function(target) {
|
|
if (HAS_TOUCH_ACTION_DELAY) {
|
|
_dispatcher.unlisten(target, this.events);
|
|
} else {
|
|
// 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 (touch__pointermap.pointers() === 0 || (touch__pointermap.pointers() === 1 && touch__pointermap.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);
|
|
// Spec specifies that pointerId 1 is reserved 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.webkitRadiusX || inTouch.radiusX || 0;
|
|
e.height = inTouch.webkitRadiusY || inTouch.radiusY || 0;
|
|
e.pressure = inTouch.webkitForce || inTouch.force || 0.5;
|
|
e.isPrimary = this.isPrimaryTouch(inTouch);
|
|
e.pointerType = this.POINTER_TYPE;
|
|
// 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.pointers() should be < tl.length here, as the touchstart has not
|
|
// been processed yet.
|
|
if (touch__pointermap.pointers() >= tl.length) {
|
|
var d = [];
|
|
touch__pointermap.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) {
|
|
var p = touch__pointermap.set(inPointer.pointerId, {
|
|
target: inPointer.target,
|
|
out: inPointer,
|
|
outTarget: inPointer.target
|
|
});
|
|
_dispatcher.over(inPointer);
|
|
_dispatcher.enter(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 = touch__pointermap.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.out(inPointer);
|
|
_dispatcher.leave(inPointer);
|
|
}
|
|
this.cleanUpPointer(inPointer);
|
|
},
|
|
touchcancel: function(inEvent) {
|
|
this.processTouches(inEvent, this.cancelOut);
|
|
},
|
|
cancelOut: function(inPointer) {
|
|
_dispatcher.cancel(inPointer);
|
|
_dispatcher.out(inPointer);
|
|
_dispatcher.leave(inPointer);
|
|
this.cleanUpPointer(inPointer);
|
|
},
|
|
cleanUpPointer: function(inPointer) {
|
|
touch__pointermap['delete'](inPointer.pointerId);
|
|
this.removePrimaryPointer(inPointer);
|
|
},
|
|
// prevent synth mouse events from creating pointer events
|
|
dedupSynthMouse: function(inEvent) {
|
|
var lts = mouse.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);
|
|
}
|
|
}
|
|
};
|
|
|
|
if (!HAS_TOUCH_ACTION_DELAY) {
|
|
INSTALLER = new installer(touchEvents.elementAdded, touchEvents.elementRemoved, touchEvents.elementChanged, touchEvents);
|
|
}
|
|
|
|
var touch = touchEvents;
|
|
|
|
var ms__pointermap = _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) {
|
|
ms__pointermap['delete'](id);
|
|
},
|
|
MSPointerDown: function(inEvent) {
|
|
ms__pointermap.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);
|
|
}
|
|
};
|
|
|
|
var ms = msEvents;
|
|
|
|
function platform_events__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', ms);
|
|
} else {
|
|
_dispatcher.registerSource('mouse', mouse);
|
|
if (window.ontouchstart !== undefined) {
|
|
_dispatcher.registerSource('touch', touch);
|
|
}
|
|
}
|
|
|
|
_dispatcher.register(document);
|
|
}
|
|
}
|
|
|
|
var n = window.navigator;
|
|
var s, r;
|
|
function assertDown(id) {
|
|
if (!_dispatcher.pointermap.has(id)) {
|
|
throw new Error('InvalidPointerId');
|
|
}
|
|
}
|
|
if (n.msPointerEnabled) {
|
|
s = function(pointerId) {
|
|
assertDown(pointerId);
|
|
this.msSetPointerCapture(pointerId);
|
|
};
|
|
r = function(pointerId) {
|
|
assertDown(pointerId);
|
|
this.msReleasePointerCapture(pointerId);
|
|
};
|
|
} else {
|
|
s = function setPointerCapture(pointerId) {
|
|
assertDown(pointerId);
|
|
_dispatcher.setCapture(pointerId, this);
|
|
};
|
|
r = function releasePointerCapture(pointerId) {
|
|
assertDown(pointerId);
|
|
_dispatcher.releaseCapture(pointerId, this);
|
|
};
|
|
}
|
|
|
|
function capture__applyPolyfill() {
|
|
if (window.Element && !Element.prototype.setPointerCapture) {
|
|
Object.defineProperties(Element.prototype, {
|
|
'setPointerCapture': {
|
|
value: s
|
|
},
|
|
'releasePointerCapture': {
|
|
value: r
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
applyAttributeStyles();
|
|
platform_events__applyPolyfill();
|
|
capture__applyPolyfill();
|
|
|
|
var pointerevents = {
|
|
dispatcher: _dispatcher,
|
|
Installer: installer,
|
|
PointerEvent: _PointerEvent,
|
|
PointerMap: _pointermap,
|
|
targetFinding: targeting
|
|
};
|
|
|
|
return pointerevents;
|
|
|
|
})); |