You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1473 lines
42 KiB
1473 lines
42 KiB
/*!
|
|
* PEP v0.4.3 | 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.twist = inDict.twist || 0;
|
|
e.tangentialPressure = inDict.tangentialPressure || 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 <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) {
|
|
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 = [];
|
|
|
|
// Order of conditions due to document.contains() missing in IE.
|
|
while (target !== document && !target.contains(event.relatedTarget)) {
|
|
targets.push(target);
|
|
target = target.parentNode;
|
|
|
|
// Touch: Do not propagate if node is detached.
|
|
if (!target) {
|
|
return;
|
|
}
|
|
}
|
|
if (propagateDown) {
|
|
targets.reverse();
|
|
}
|
|
targets.forEach(function(target) {
|
|
event.target = target;
|
|
fn.call(this, event);
|
|
}, this);
|
|
},
|
|
setCapture: function(inPointerId, inTarget, skipDispatch) {
|
|
if (this.captureInfo[inPointerId]) {
|
|
this.releaseCapture(inPointerId, skipDispatch);
|
|
}
|
|
|
|
this.captureInfo[inPointerId] = inTarget;
|
|
this.implicitRelease = this.releaseCapture.bind(this, inPointerId, skipDispatch);
|
|
document.addEventListener('pointerup', this.implicitRelease);
|
|
document.addEventListener('pointercancel', this.implicitRelease);
|
|
|
|
var e = new PointerEvent('gotpointercapture');
|
|
e.pointerId = inPointerId;
|
|
e._target = inTarget;
|
|
|
|
if (!skipDispatch) {
|
|
this.asyncDispatchEvent(e);
|
|
}
|
|
},
|
|
releaseCapture: function(inPointerId, skipDispatch) {
|
|
var t = this.captureInfo[inPointerId];
|
|
if (!t) {
|
|
return;
|
|
}
|
|
|
|
this.captureInfo[inPointerId] = undefined;
|
|
document.removeEventListener('pointerup', this.implicitRelease);
|
|
document.removeEventListener('pointercancel', this.implicitRelease);
|
|
|
|
var e = new PointerEvent('lostpointercapture');
|
|
e.pointerId = inPointerId;
|
|
e._target = t;
|
|
|
|
if (!skipDispatch) {
|
|
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) * 2;
|
|
e.height = (inTouch.radiusY || inTouch.webkitRadiusY || 0) * 2;
|
|
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 {
|
|
Object.defineProperty(window.navigator, 'maxTouchPoints', {
|
|
value: 0,
|
|
enumerable: true
|
|
});
|
|
dispatcher.registerSource('mouse', mouseEvents);
|
|
if (window.ontouchstart !== undefined) {
|
|
dispatcher.registerSource('touch', touchEvents);
|
|
}
|
|
}
|
|
|
|
dispatcher.register(document);
|
|
}
|
|
}
|
|
|
|
var n = window.navigator;
|
|
var s;
|
|
var r;
|
|
var h;
|
|
function assertActive(id) {
|
|
if (!dispatcher.pointermap.has(id)) {
|
|
var error = new Error('InvalidPointerId');
|
|
error.name = 'InvalidPointerId';
|
|
throw error;
|
|
}
|
|
}
|
|
function assertConnected(elem) {
|
|
var parent = elem.parentNode;
|
|
while (parent && parent !== elem.ownerDocument) {
|
|
parent = parent.parentNode;
|
|
}
|
|
if (!parent) {
|
|
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)) {
|
|
dispatcher.setCapture(pointerId, this, true);
|
|
this.msSetPointerCapture(pointerId);
|
|
}
|
|
};
|
|
r = function(pointerId) {
|
|
assertActive(pointerId);
|
|
dispatcher.releaseCapture(pointerId, true);
|
|
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);
|
|
};
|
|
}
|
|
h = function hasPointerCapture(pointerId) {
|
|
return !!dispatcher.captureInfo[pointerId];
|
|
};
|
|
|
|
function applyPolyfill$1() {
|
|
if (window.Element && !Element.prototype.setPointerCapture) {
|
|
Object.defineProperties(Element.prototype, {
|
|
'setPointerCapture': {
|
|
value: s
|
|
},
|
|
'releasePointerCapture': {
|
|
value: r
|
|
},
|
|
'hasPointerCapture': {
|
|
value: h
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
applyAttributeStyles();
|
|
applyPolyfill();
|
|
applyPolyfill$1();
|
|
|
|
var pointerevents = {
|
|
dispatcher: dispatcher,
|
|
Installer: Installer,
|
|
PointerEvent: PointerEvent,
|
|
PointerMap: PointerMap,
|
|
targetFinding: targeting
|
|
};
|
|
|
|
return pointerevents;
|
|
|
|
}));
|