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