Use pepjs instead of our own pointerevent polyfill

This commit is contained in:
ahocevar
2019-08-28 21:48:06 +02:00
parent aca4a39863
commit 74e8e013cf
27 changed files with 197 additions and 2430 deletions

View File

@@ -1,45 +0,0 @@
/**
* @module ol/pointer/EventSource
*/
class EventSource {
/**
* @param {import("./PointerEventHandler.js").default} dispatcher Event handler.
* @param {!Object<string, function(Event): void>} mapping Event mapping.
*/
constructor(dispatcher, mapping) {
/**
* @type {import("./PointerEventHandler.js").default}
*/
this.dispatcher = dispatcher;
/**
* @private
* @const
* @type {!Object<string, function(Event): void>}
*/
this.mapping_ = mapping;
}
/**
* List of events supported by this source.
* @return {Array<string>} Event names
*/
getEvents() {
return Object.keys(this.mapping_);
}
/**
* Returns the handler that should handle a given event type.
* @param {string} eventType The event type.
* @return {function(Event)} Handler
*/
getHandlerForEvent(eventType) {
return this.mapping_[eventType];
}
}
export default EventSource;

View File

@@ -1,247 +0,0 @@
/**
* @module ol/pointer/MouseSource
*/
// Based on https://github.com/Polymer/PointerEvents
// Copyright (c) 2013 The Polymer Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import EventSource from './EventSource.js';
/**
* @type {number}
*/
export const POINTER_ID = 1;
/**
* @type {string}
*/
export const POINTER_TYPE = 'mouse';
/**
* Radius around touchend that swallows mouse events.
*
* @type {number}
*/
const DEDUP_DIST = 25;
/**
* Handler for `mousedown`.
*
* @this {MouseSource}
* @param {MouseEvent} inEvent The in event.
*/
function mousedown(inEvent) {
if (!this.isEventSimulatedFromTouch_(inEvent)) {
// TODO(dfreedman) workaround for some elements not sending mouseup
// http://crbug/149091
if (POINTER_ID.toString() in this.pointerMap) {
this.cancel(inEvent);
}
const e = prepareEvent(inEvent, this.dispatcher);
this.pointerMap[POINTER_ID.toString()] = inEvent;
this.dispatcher.down(e, inEvent);
}
}
/**
* Handler for `mousemove`.
*
* @this {MouseSource}
* @param {MouseEvent} inEvent The in event.
*/
function mousemove(inEvent) {
if (!this.isEventSimulatedFromTouch_(inEvent)) {
const e = prepareEvent(inEvent, this.dispatcher);
this.dispatcher.move(e, inEvent);
}
}
/**
* Handler for `mouseup`.
*
* @this {MouseSource}
* @param {MouseEvent} inEvent The in event.
*/
function mouseup(inEvent) {
if (!this.isEventSimulatedFromTouch_(inEvent)) {
const p = this.pointerMap[POINTER_ID.toString()];
if (p && p.button === inEvent.button) {
const e = prepareEvent(inEvent, this.dispatcher);
this.dispatcher.up(e, inEvent);
this.cleanupMouse();
}
}
}
/**
* Handler for `mouseover`.
*
* @this {MouseSource}
* @param {MouseEvent} inEvent The in event.
*/
function mouseover(inEvent) {
if (!this.isEventSimulatedFromTouch_(inEvent)) {
const e = prepareEvent(inEvent, this.dispatcher);
this.dispatcher.enterOver(e, inEvent);
}
}
/**
* Handler for `mouseout`.
*
* @this {MouseSource}
* @param {MouseEvent} inEvent The in event.
*/
function mouseout(inEvent) {
if (!this.isEventSimulatedFromTouch_(inEvent)) {
const e = prepareEvent(inEvent, this.dispatcher);
this.dispatcher.leaveOut(e, inEvent);
}
}
class MouseSource extends EventSource {
/**
* @param {import("./PointerEventHandler.js").default} dispatcher Event handler.
*/
constructor(dispatcher) {
const mapping = {
'mousedown': mousedown,
'mousemove': mousemove,
'mouseup': mouseup,
'mouseover': mouseover,
'mouseout': mouseout
};
super(dispatcher, mapping);
/**
* @const
* @type {!Object<string, Event|Object>}
*/
this.pointerMap = dispatcher.pointerMap;
/**
* @const
* @type {Array<import("../pixel.js").Pixel>}
*/
this.lastTouches = [];
}
/**
* Detect if a mouse event was simulated from a touch by
* checking if previously there was a touch event at the
* same position.
*
* FIXME - Known problem with the native Android browser on
* Samsung GT-I9100 (Android 4.1.2):
* In case the page is scrolled, this function does not work
* correctly when a canvas is used (WebGL or canvas renderer).
* Mouse listeners on canvas elements (for this browser), create
* two mouse events: One 'good' and one 'bad' one (on other browsers or
* when a div is used, there is only one event). For the 'bad' one,
* clientX/clientY and also pageX/pageY are wrong when the page
* is scrolled. Because of that, this function can not detect if
* the events were simulated from a touch event. As result, a
* pointer event at a wrong position is dispatched, which confuses
* the map interactions.
* It is unclear, how one can get the correct position for the event
* or detect that the positions are invalid.
*
* @private
* @param {MouseEvent} inEvent The in event.
* @return {boolean} True, if the event was generated by a touch.
*/
isEventSimulatedFromTouch_(inEvent) {
const lts = this.lastTouches;
const x = inEvent.clientX;
const y = inEvent.clientY;
for (let i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) {
// simulated mouse events will be swallowed near a primary touchend
const dx = Math.abs(x - t[0]);
const dy = Math.abs(y - t[1]);
if (dx <= DEDUP_DIST && dy <= DEDUP_DIST) {
return true;
}
}
return false;
}
/**
* Dispatches a `pointercancel` event.
*
* @param {Event} inEvent The in event.
*/
cancel(inEvent) {
const e = prepareEvent(inEvent, this.dispatcher);
this.dispatcher.cancel(e, inEvent);
this.cleanupMouse();
}
/**
* Remove the mouse from the list of active pointers.
*/
cleanupMouse() {
delete this.pointerMap[POINTER_ID.toString()];
}
}
/**
* Creates a copy of the original event that will be used
* for the fake pointer event.
*
* @param {Event} inEvent The in event.
* @param {import("./PointerEventHandler.js").default} dispatcher Event handler.
* @return {Object} The copied event.
*/
export function prepareEvent(inEvent, dispatcher) {
const e = dispatcher.cloneEvent(inEvent, inEvent);
// forward mouse preventDefault
const pd = e.preventDefault;
e.preventDefault = function() {
inEvent.preventDefault();
pd();
};
e.pointerId = POINTER_ID;
e.isPrimary = true;
e.pointerType = POINTER_TYPE;
return e;
}
export default MouseSource;

View File

@@ -1,194 +0,0 @@
/**
* @module ol/pointer/MsSource
*/
// Based on https://github.com/Polymer/PointerEvents
// Copyright (c) 2013 The Polymer Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import EventSource from './EventSource.js';
/**
* @const
* @type {Array<string>}
*/
const POINTER_TYPES = [
'',
'unavailable',
'touch',
'pen',
'mouse'
];
/**
* Handler for `msPointerDown`.
*
* @this {MsSource}
* @param {MSPointerEvent} inEvent The in event.
*/
function msPointerDown(inEvent) {
this.pointerMap[inEvent.pointerId.toString()] = inEvent;
const e = this.prepareEvent_(inEvent);
this.dispatcher.down(e, inEvent);
}
/**
* Handler for `msPointerMove`.
*
* @this {MsSource}
* @param {MSPointerEvent} inEvent The in event.
*/
function msPointerMove(inEvent) {
const e = this.prepareEvent_(inEvent);
this.dispatcher.move(e, inEvent);
}
/**
* Handler for `msPointerUp`.
*
* @this {MsSource}
* @param {MSPointerEvent} inEvent The in event.
*/
function msPointerUp(inEvent) {
const e = this.prepareEvent_(inEvent);
this.dispatcher.up(e, inEvent);
this.cleanup(inEvent.pointerId);
}
/**
* Handler for `msPointerOut`.
*
* @this {MsSource}
* @param {MSPointerEvent} inEvent The in event.
*/
function msPointerOut(inEvent) {
const e = this.prepareEvent_(inEvent);
this.dispatcher.leaveOut(e, inEvent);
}
/**
* Handler for `msPointerOver`.
*
* @this {MsSource}
* @param {MSPointerEvent} inEvent The in event.
*/
function msPointerOver(inEvent) {
const e = this.prepareEvent_(inEvent);
this.dispatcher.enterOver(e, inEvent);
}
/**
* Handler for `msPointerCancel`.
*
* @this {MsSource}
* @param {MSPointerEvent} inEvent The in event.
*/
function msPointerCancel(inEvent) {
const e = this.prepareEvent_(inEvent);
this.dispatcher.cancel(e, inEvent);
this.cleanup(inEvent.pointerId);
}
/**
* Handler for `msLostPointerCapture`.
*
* @this {MsSource}
* @param {MSPointerEvent} inEvent The in event.
*/
function msLostPointerCapture(inEvent) {
const e = this.dispatcher.makeEvent('lostpointercapture', inEvent, inEvent);
this.dispatcher.dispatchEvent(e);
}
/**
* Handler for `msGotPointerCapture`.
*
* @this {MsSource}
* @param {MSPointerEvent} inEvent The in event.
*/
function msGotPointerCapture(inEvent) {
const e = this.dispatcher.makeEvent('gotpointercapture', inEvent, inEvent);
this.dispatcher.dispatchEvent(e);
}
class MsSource extends EventSource {
/**
* @param {import("./PointerEventHandler.js").default} dispatcher Event handler.
*/
constructor(dispatcher) {
const mapping = {
'MSPointerDown': msPointerDown,
'MSPointerMove': msPointerMove,
'MSPointerUp': msPointerUp,
'MSPointerOut': msPointerOut,
'MSPointerOver': msPointerOver,
'MSPointerCancel': msPointerCancel,
'MSGotPointerCapture': msGotPointerCapture,
'MSLostPointerCapture': msLostPointerCapture
};
super(dispatcher, mapping);
/**
* @const
* @type {!Object<string, MSPointerEvent|Object>}
*/
this.pointerMap = dispatcher.pointerMap;
}
/**
* Creates a copy of the original event that will be used
* for the fake pointer event.
*
* @private
* @param {MSPointerEvent} inEvent The in event.
* @return {Object} The copied event.
*/
prepareEvent_(inEvent) {
/** @type {MSPointerEvent|Object} */
let e = inEvent;
if (typeof inEvent.pointerType === 'number') {
e = this.dispatcher.cloneEvent(inEvent, inEvent);
e.pointerType = POINTER_TYPES[inEvent.pointerType];
}
return e;
}
/**
* Remove this pointer from the list of active pointers.
* @param {number} pointerId Pointer identifier.
*/
cleanup(pointerId) {
delete this.pointerMap[pointerId.toString()];
}
}
export default MsSource;

View File

@@ -1,138 +0,0 @@
/**
* @module ol/pointer/NativeSource
*/
// Based on https://github.com/Polymer/PointerEvents
// Copyright (c) 2013 The Polymer Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import EventSource from './EventSource.js';
/**
* Handler for `pointerdown`.
*
* @this {NativeSource}
* @param {Event} inEvent The in event.
*/
function pointerDown(inEvent) {
this.dispatcher.fireNativeEvent(inEvent);
}
/**
* Handler for `pointermove`.
*
* @this {NativeSource}
* @param {Event} inEvent The in event.
*/
function pointerMove(inEvent) {
this.dispatcher.fireNativeEvent(inEvent);
}
/**
* Handler for `pointerup`.
*
* @this {NativeSource}
* @param {Event} inEvent The in event.
*/
function pointerUp(inEvent) {
this.dispatcher.fireNativeEvent(inEvent);
}
/**
* Handler for `pointerout`.
*
* @this {NativeSource}
* @param {Event} inEvent The in event.
*/
function pointerOut(inEvent) {
this.dispatcher.fireNativeEvent(inEvent);
}
/**
* Handler for `pointerover`.
*
* @this {NativeSource}
* @param {Event} inEvent The in event.
*/
function pointerOver(inEvent) {
this.dispatcher.fireNativeEvent(inEvent);
}
/**
* Handler for `pointercancel`.
*
* @this {NativeSource}
* @param {Event} inEvent The in event.
*/
function pointerCancel(inEvent) {
this.dispatcher.fireNativeEvent(inEvent);
}
/**
* Handler for `lostpointercapture`.
*
* @this {NativeSource}
* @param {Event} inEvent The in event.
*/
function lostPointerCapture(inEvent) {
this.dispatcher.fireNativeEvent(inEvent);
}
/**
* Handler for `gotpointercapture`.
*
* @this {NativeSource}
* @param {Event} inEvent The in event.
*/
function gotPointerCapture(inEvent) {
this.dispatcher.fireNativeEvent(inEvent);
}
class NativeSource extends EventSource {
/**
* @param {import("./PointerEventHandler.js").default} dispatcher Event handler.
*/
constructor(dispatcher) {
const mapping = {
'pointerdown': pointerDown,
'pointermove': pointerMove,
'pointerup': pointerUp,
'pointerout': pointerOut,
'pointerover': pointerOver,
'pointercancel': pointerCancel,
'gotpointercapture': gotPointerCapture,
'lostpointercapture': lostPointerCapture
};
super(dispatcher, mapping);
}
}
export default NativeSource;

View File

@@ -1,277 +0,0 @@
/**
* @module ol/pointer/PointerEvent
*/
// Based on https://github.com/Polymer/PointerEvents
// Copyright (c) 2013 The Polymer Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import _Event from '../events/Event.js';
/**
* Is the `buttons` property supported?
* @type {boolean}
*/
let HAS_BUTTONS = false;
class PointerEvent extends _Event {
/**
* A class for pointer events.
*
* This class is used as an abstraction for mouse events,
* touch events and even native pointer events.
*
* @param {string} type The type of the event to create.
* @param {Event} originalEvent The event.
* @param {Object<string, ?>=} opt_eventDict An optional dictionary of
* initial event properties.
*/
constructor(type, originalEvent, opt_eventDict) {
super(type);
/**
* @const
* @type {Event}
*/
this.originalEvent = originalEvent;
const eventDict = opt_eventDict ? opt_eventDict : {};
/**
* @type {number}
*/
this.buttons = getButtons(eventDict);
/**
* @type {number}
*/
this.pressure = getPressure(eventDict, this.buttons);
// MouseEvent related properties
/**
* @type {boolean}
*/
this.bubbles = 'bubbles' in eventDict ? eventDict['bubbles'] : false;
/**
* @type {boolean}
*/
this.cancelable = 'cancelable' in eventDict ? eventDict['cancelable'] : false;
/**
* @type {Object}
*/
this.view = 'view' in eventDict ? eventDict['view'] : null;
/**
* @type {number}
*/
this.detail = 'detail' in eventDict ? eventDict['detail'] : null;
/**
* @type {number}
*/
this.screenX = 'screenX' in eventDict ? eventDict['screenX'] : 0;
/**
* @type {number}
*/
this.screenY = 'screenY' in eventDict ? eventDict['screenY'] : 0;
/**
* @type {number}
*/
this.clientX = 'clientX' in eventDict ? eventDict['clientX'] : 0;
/**
* @type {number}
*/
this.clientY = 'clientY' in eventDict ? eventDict['clientY'] : 0;
/**
* @type {boolean}
*/
this.ctrlKey = 'ctrlKey' in eventDict ? eventDict['ctrlKey'] : false;
/**
* @type {boolean}
*/
this.altKey = 'altKey' in eventDict ? eventDict['altKey'] : false;
/**
* @type {boolean}
*/
this.shiftKey = 'shiftKey' in eventDict ? eventDict['shiftKey'] : false;
/**
* @type {boolean}
*/
this.metaKey = 'metaKey' in eventDict ? eventDict['metaKey'] : false;
/**
* @type {number}
*/
this.button = 'button' in eventDict ? eventDict['button'] : 0;
/**
* @type {Node}
*/
this.relatedTarget = 'relatedTarget' in eventDict ?
eventDict['relatedTarget'] : null;
// PointerEvent related properties
/**
* @const
* @type {number}
*/
this.pointerId = 'pointerId' in eventDict ? eventDict['pointerId'] : 0;
/**
* @type {number}
*/
this.width = 'width' in eventDict ? eventDict['width'] : 0;
/**
* @type {number}
*/
this.height = 'height' in eventDict ? eventDict['height'] : 0;
/**
* @type {number}
*/
this.tiltX = 'tiltX' in eventDict ? eventDict['tiltX'] : 0;
/**
* @type {number}
*/
this.tiltY = 'tiltY' in eventDict ? eventDict['tiltY'] : 0;
/**
* @type {string}
*/
this.pointerType = 'pointerType' in eventDict ? eventDict['pointerType'] : '';
/**
* @type {number}
*/
this.hwTimestamp = 'hwTimestamp' in eventDict ? eventDict['hwTimestamp'] : 0;
/**
* @type {boolean}
*/
this.isPrimary = 'isPrimary' in eventDict ? eventDict['isPrimary'] : false;
// keep the semantics of preventDefault
if (originalEvent.preventDefault) {
this.preventDefault = function() {
originalEvent.preventDefault();
};
}
}
}
/**
* @param {Object<string, ?>} eventDict The event dictionary.
* @return {number} Button indicator.
*/
function getButtons(eventDict) {
// According to the w3c spec,
// http://www.w3.org/TR/DOM-Level-3-Events/#events-MouseEvent-button
// MouseEvent.button == 0 can mean either no mouse button depressed, or the
// left mouse button depressed.
//
// As of now, the only way to distinguish between the two states of
// MouseEvent.button is by using the deprecated MouseEvent.which property, as
// this maps mouse buttons to positive integers > 0, and uses 0 to mean that
// no mouse button is held.
//
// MouseEvent.which is derived from MouseEvent.button at MouseEvent creation,
// but initMouseEvent does not expose an argument with which to set
// MouseEvent.which. Calling initMouseEvent with a buttonArg of 0 will set
// MouseEvent.button == 0 and MouseEvent.which == 1, breaking the expectations
// of app developers.
//
// The only way to propagate the correct state of MouseEvent.which and
// MouseEvent.button to a new MouseEvent.button == 0 and MouseEvent.which == 0
// is to call initMouseEvent with a buttonArg value of -1.
//
// This is fixed with DOM Level 4's use of buttons
let buttons;
if (eventDict.buttons || HAS_BUTTONS) {
buttons = eventDict.buttons;
} else {
switch (eventDict.which) {
case 1: buttons = 1; break;
case 2: buttons = 4; break;
case 3: buttons = 2; break;
default: buttons = 0;
}
}
return buttons;
}
/**
* @param {Object<string, ?>} eventDict The event dictionary.
* @param {number} buttons Button indicator.
* @return {number} The pressure.
*/
function getPressure(eventDict, buttons) {
// Spec requires that pointers without pressure specified use 0.5 for down
// state and 0 for up state.
let pressure = 0;
if (eventDict.pressure) {
pressure = eventDict.pressure;
} else {
pressure = buttons ? 0.5 : 0;
}
return pressure;
}
/**
* Checks if the `buttons` property is supported.
*/
(function() {
try {
const ev = new MouseEvent('click', {buttons: 1});
HAS_BUTTONS = ev.buttons === 1;
} catch (e) {
// pass
}
})();
export default PointerEvent;

View File

@@ -1,417 +0,0 @@
/**
* @module ol/pointer/PointerEventHandler
*/
// Based on https://github.com/Polymer/PointerEvents
// Copyright (c) 2013 The Polymer Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import {listen, unlisten} from '../events.js';
import EventTarget from '../events/Target.js';
import PointerEventType from './EventType.js';
import MouseSource, {prepareEvent as prepareMouseEvent} from './MouseSource.js';
import MsSource from './MsSource.js';
import NativeSource from './NativeSource.js';
import PointerEvent from './PointerEvent.js';
import TouchSource from './TouchSource.js';
/**
* Properties to copy when cloning an event, with default values.
* @type {Array<Array>}
*/
const CLONE_PROPS = [
// MouseEvent
['bubbles', false],
['cancelable', false],
['view', null],
['detail', null],
['screenX', 0],
['screenY', 0],
['clientX', 0],
['clientY', 0],
['ctrlKey', false],
['altKey', false],
['shiftKey', false],
['metaKey', false],
['button', 0],
['relatedTarget', null],
// DOM Level 3
['buttons', 0],
// PointerEvent
['pointerId', 0],
['width', 0],
['height', 0],
['pressure', 0],
['tiltX', 0],
['tiltY', 0],
['pointerType', ''],
['hwTimestamp', 0],
['isPrimary', false],
// event instance
['type', ''],
['target', null],
['currentTarget', null],
['which', 0]
];
class PointerEventHandler extends EventTarget {
/**
* @param {Element|HTMLDocument} element Viewport element.
*/
constructor(element) {
super();
/**
* @const
* @private
* @type {Element|HTMLDocument}
*/
this.element_ = element;
/**
* @const
* @type {!Object<string, Event|Object>}
*/
this.pointerMap = {};
/**
* @type {Object<string, function(Event): void>}
* @private
*/
this.eventMap_ = {};
/**
* @type {Array<import("./EventSource.js").default>}
* @private
*/
this.eventSourceList_ = [];
this.registerSources();
}
/**
* Set up the event sources (mouse, touch and native pointers)
* that generate pointer events.
*/
registerSources() {
if ('PointerEvent' in window) {
this.registerSource('native', new NativeSource(this));
} else if (window.navigator.msPointerEnabled) {
this.registerSource('ms', new MsSource(this));
} else {
const mouseSource = new MouseSource(this);
this.registerSource('mouse', mouseSource);
if ('ontouchstart' in window) {
this.registerSource('touch', new TouchSource(this, mouseSource));
}
}
// register events on the viewport element
this.register_();
}
/**
* Add a new event source that will generate pointer events.
*
* @param {string} name A name for the event source
* @param {import("./EventSource.js").default} source The source event.
*/
registerSource(name, source) {
const s = source;
const newEvents = s.getEvents();
if (newEvents) {
newEvents.forEach(function(e) {
const handler = s.getHandlerForEvent(e);
if (handler) {
this.eventMap_[e] = handler.bind(s);
}
}.bind(this));
this.eventSourceList_.push(s);
}
}
/**
* Set up the events for all registered event sources.
* @private
*/
register_() {
const l = this.eventSourceList_.length;
for (let i = 0; i < l; i++) {
const eventSource = this.eventSourceList_[i];
this.addEvents_(eventSource.getEvents());
}
}
/**
* Remove all registered events.
* @private
*/
unregister_() {
const l = this.eventSourceList_.length;
for (let i = 0; i < l; i++) {
const eventSource = this.eventSourceList_[i];
this.removeEvents_(eventSource.getEvents());
}
}
/**
* Calls the right handler for a new event.
* @private
* @param {Event} inEvent Browser event.
*/
eventHandler_(inEvent) {
const type = inEvent.type;
const handler = this.eventMap_[type];
if (handler) {
handler(inEvent);
}
}
/**
* Setup listeners for the given events.
* @private
* @param {Array<string>} events List of events.
*/
addEvents_(events) {
events.forEach(function(eventName) {
listen(this.element_, eventName, this.eventHandler_, this);
}.bind(this));
}
/**
* Unregister listeners for the given events.
* @private
* @param {Array<string>} events List of events.
*/
removeEvents_(events) {
events.forEach(function(e) {
unlisten(this.element_, e, this.eventHandler_, this);
}.bind(this));
}
/**
* Returns a snapshot of inEvent, with writable properties.
*
* @param {Event} event Browser event.
* @param {Event|Touch} inEvent An event that contains
* properties to copy.
* @return {Object} An object containing shallow copies of
* `inEvent`'s properties.
*/
cloneEvent(event, inEvent) {
const eventCopy = {};
for (let i = 0, ii = CLONE_PROPS.length; i < ii; i++) {
const p = CLONE_PROPS[i][0];
eventCopy[p] = event[p] || inEvent[p] || CLONE_PROPS[i][1];
}
return eventCopy;
}
// EVENTS
/**
* Triggers a 'pointerdown' event.
* @param {Object} data Pointer event data.
* @param {Event} event The event.
*/
down(data, event) {
this.fireEvent(PointerEventType.POINTERDOWN, data, event);
}
/**
* Triggers a 'pointermove' event.
* @param {Object} data Pointer event data.
* @param {Event} event The event.
*/
move(data, event) {
this.fireEvent(PointerEventType.POINTERMOVE, data, event);
}
/**
* Triggers a 'pointerup' event.
* @param {Object} data Pointer event data.
* @param {Event} event The event.
*/
up(data, event) {
this.fireEvent(PointerEventType.POINTERUP, data, event);
}
/**
* Triggers a 'pointerenter' event.
* @param {Object} data Pointer event data.
* @param {Event} event The event.
*/
enter(data, event) {
data.bubbles = false;
this.fireEvent(PointerEventType.POINTERENTER, data, event);
}
/**
* Triggers a 'pointerleave' event.
* @param {Object} data Pointer event data.
* @param {Event} event The event.
*/
leave(data, event) {
data.bubbles = false;
this.fireEvent(PointerEventType.POINTERLEAVE, data, event);
}
/**
* Triggers a 'pointerover' event.
* @param {Object} data Pointer event data.
* @param {Event} event The event.
*/
over(data, event) {
data.bubbles = true;
this.fireEvent(PointerEventType.POINTEROVER, data, event);
}
/**
* Triggers a 'pointerout' event.
* @param {Object} data Pointer event data.
* @param {Event} event The event.
*/
out(data, event) {
data.bubbles = true;
this.fireEvent(PointerEventType.POINTEROUT, data, event);
}
/**
* Triggers a 'pointercancel' event.
* @param {Object} data Pointer event data.
* @param {Event} event The event.
*/
cancel(data, event) {
this.fireEvent(PointerEventType.POINTERCANCEL, data, event);
}
/**
* Triggers a combination of 'pointerout' and 'pointerleave' events.
* @param {Object} data Pointer event data.
* @param {Event} event The event.
*/
leaveOut(data, event) {
this.out(data, event);
if (!this.contains_(data.target, data.relatedTarget)) {
this.leave(data, event);
}
}
/**
* Triggers a combination of 'pointerover' and 'pointerevents' events.
* @param {Object} data Pointer event data.
* @param {Event} event The event.
*/
enterOver(data, event) {
this.over(data, event);
if (!this.contains_(data.target, data.relatedTarget)) {
this.enter(data, event);
}
}
/**
* @private
* @param {Element} container The container element.
* @param {Element} contained The contained element.
* @return {boolean} Returns true if the container element
* contains the other element.
*/
contains_(container, contained) {
if (!container || !contained) {
return false;
}
return container.contains(contained);
}
// EVENT CREATION AND TRACKING
/**
* Creates a new Event of type `inType`, based on the information in
* `data`.
*
* @param {string} inType A string representing the type of event to create.
* @param {Object} data Pointer event data.
* @param {Event} event The event.
* @return {PointerEvent} A PointerEvent of type `inType`.
*/
makeEvent(inType, data, event) {
return new PointerEvent(inType, event, data);
}
/**
* Make and dispatch an event in one call.
* @param {string} inType A string representing the type of event.
* @param {Object} data Pointer event data.
* @param {Event} event The event.
*/
fireEvent(inType, data, event) {
const e = this.makeEvent(inType, data, event);
this.dispatchEvent(e);
}
/**
* Creates a pointer event from a native pointer event
* and dispatches this event.
* @param {Event} event A platform event with a target.
*/
fireNativeEvent(event) {
const e = this.makeEvent(event.type, event, event);
this.dispatchEvent(e);
}
/**
* Wrap a native mouse event into a pointer event.
* This proxy method is required for the legacy IE support.
* @param {string} eventType The pointer event type.
* @param {Event} event The event.
* @return {PointerEvent} The wrapped event.
*/
wrapMouseEvent(eventType, event) {
const pointerEvent = this.makeEvent(
eventType, prepareMouseEvent(event, this), event);
return pointerEvent;
}
/**
* @inheritDoc
*/
disposeInternal() {
this.unregister_();
super.disposeInternal();
}
}
export default PointerEventHandler;

View File

@@ -1,424 +0,0 @@
/**
* @module ol/pointer/TouchSource
*/
// Based on https://github.com/Polymer/PointerEvents
// Copyright (c) 2013 The Polymer Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import {remove} from '../array.js';
import EventSource from './EventSource.js';
import {POINTER_ID} from './MouseSource.js';
/**
* @type {number}
*/
const CLICK_COUNT_TIMEOUT = 200;
/**
* @type {string}
*/
const POINTER_TYPE = 'touch';
/**
* Handler for `touchstart`, triggers `pointerover`,
* `pointerenter` and `pointerdown` events.
*
* @this {TouchSource}
* @param {TouchEvent} inEvent The in event.
*/
function touchstart(inEvent) {
this.vacuumTouches_(inEvent);
this.setPrimaryTouch_(inEvent.changedTouches[0]);
this.dedupSynthMouse_(inEvent);
this.clickCount_++;
this.processTouches_(inEvent, this.overDown_);
}
/**
* Handler for `touchmove`.
*
* @this {TouchSource}
* @param {TouchEvent} inEvent The in event.
*/
function touchmove(inEvent) {
this.processTouches_(inEvent, this.moveOverOut_);
}
/**
* Handler for `touchend`, triggers `pointerup`,
* `pointerout` and `pointerleave` events.
*
* @this {TouchSource}
* @param {TouchEvent} inEvent The event.
*/
function touchend(inEvent) {
this.dedupSynthMouse_(inEvent);
this.processTouches_(inEvent, this.upOut_);
}
/**
* Handler for `touchcancel`, triggers `pointercancel`,
* `pointerout` and `pointerleave` events.
*
* @this {TouchSource}
* @param {TouchEvent} inEvent The in event.
*/
function touchcancel(inEvent) {
this.processTouches_(inEvent, this.cancelOut_);
}
class TouchSource extends EventSource {
/**
* @param {import("./PointerEventHandler.js").default} dispatcher The event handler.
* @param {import("./MouseSource.js").default} mouseSource Mouse source.
*/
constructor(dispatcher, mouseSource) {
const mapping = {
'touchstart': touchstart,
'touchmove': touchmove,
'touchend': touchend,
'touchcancel': touchcancel
};
super(dispatcher, mapping);
/**
* @const
* @type {!Object<string, Event|Object>}
*/
this.pointerMap = dispatcher.pointerMap;
/**
* @const
* @type {import("./MouseSource.js").default}
*/
this.mouseSource = mouseSource;
/**
* @private
* @type {number|undefined}
*/
this.firstTouchId_ = undefined;
/**
* @private
* @type {number}
*/
this.clickCount_ = 0;
/**
* @private
* @type {?}
*/
this.resetId_;
/**
* Mouse event timeout: This should be long enough to
* ignore compat mouse events made by touch.
* @private
* @type {number}
*/
this.dedupTimeout_ = 2500;
}
/**
* @private
* @param {Touch} inTouch The in touch.
* @return {boolean} True, if this is the primary touch.
*/
isPrimaryTouch_(inTouch) {
return this.firstTouchId_ === inTouch.identifier;
}
/**
* Set primary touch if there are no pointers, or the only pointer is the mouse.
* @param {Touch} inTouch The in touch.
* @private
*/
setPrimaryTouch_(inTouch) {
const count = Object.keys(this.pointerMap).length;
if (count === 0 || (count === 1 && POINTER_ID.toString() in this.pointerMap)) {
this.firstTouchId_ = inTouch.identifier;
this.cancelResetClickCount_();
}
}
/**
* @private
* @param {PointerEvent} inPointer The in pointer object.
*/
removePrimaryPointer_(inPointer) {
if (inPointer.isPrimary) {
this.firstTouchId_ = undefined;
this.resetClickCount_();
}
}
/**
* @private
*/
resetClickCount_() {
this.resetId_ = setTimeout(
this.resetClickCountHandler_.bind(this),
CLICK_COUNT_TIMEOUT);
}
/**
* @private
*/
resetClickCountHandler_() {
this.clickCount_ = 0;
this.resetId_ = undefined;
}
/**
* @private
*/
cancelResetClickCount_() {
if (this.resetId_ !== undefined) {
clearTimeout(this.resetId_);
}
}
/**
* @private
* @param {TouchEvent} browserEvent Browser event
* @param {Touch} inTouch Touch event
* @return {PointerEvent} A pointer object.
*/
touchToPointer_(browserEvent, inTouch) {
const e = this.dispatcher.cloneEvent(browserEvent, inTouch);
// Spec specifies that pointerId 1 is reserved for Mouse.
// Touch identifiers can start at 0.
// Add 2 to the touch identifier for compatibility.
e.pointerId = inTouch.identifier + 2;
// TODO: check if this is necessary?
//e.target = findTarget(e);
e.bubbles = true;
e.cancelable = true;
e.detail = this.clickCount_;
e.button = 0;
e.buttons = 1;
e.width = inTouch.radiusX || 0;
e.height = inTouch.radiusY || 0;
e.pressure = inTouch.force || 0.5;
e.isPrimary = this.isPrimaryTouch_(inTouch);
e.pointerType = POINTER_TYPE;
// make sure that the properties that are different for
// each `Touch` object are not copied from the BrowserEvent object
e.clientX = inTouch.clientX;
e.clientY = inTouch.clientY;
e.screenX = inTouch.screenX;
e.screenY = inTouch.screenY;
return e;
}
/**
* @private
* @param {TouchEvent} inEvent Touch event
* @param {function(TouchEvent, PointerEvent): void} inFunction In function.
*/
processTouches_(inEvent, inFunction) {
const touches = Array.prototype.slice.call(inEvent.changedTouches);
const count = touches.length;
function preventDefault() {
inEvent.preventDefault();
}
for (let i = 0; i < count; ++i) {
const pointer = this.touchToPointer_(inEvent, touches[i]);
// forward touch preventDefaults
pointer.preventDefault = preventDefault;
inFunction.call(this, inEvent, pointer);
}
}
/**
* @private
* @param {TouchList} touchList The touch list.
* @param {number} searchId Search identifier.
* @return {boolean} True, if the `Touch` with the given id is in the list.
*/
findTouch_(touchList, searchId) {
const l = touchList.length;
for (let i = 0; i < l; i++) {
const touch = touchList[i];
if (touch.identifier === searchId) {
return true;
}
}
return false;
}
/**
* 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
*
* @private
* @param {TouchEvent} inEvent The in event.
*/
vacuumTouches_(inEvent) {
const touchList = inEvent.touches;
// pointerMap.getCount() should be < touchList.length here,
// as the touchstart has not been processed yet.
const keys = Object.keys(this.pointerMap);
const count = keys.length;
if (count >= touchList.length) {
const d = [];
for (let i = 0; i < count; ++i) {
const key = Number(keys[i]);
const value = this.pointerMap[key];
// Never remove pointerId == 1, which is mouse.
// Touch identifiers are 2 smaller than their pointerId, which is the
// index in pointermap.
if (key != POINTER_ID && !this.findTouch_(touchList, key - 2)) {
d.push(value.out);
}
}
for (let i = 0; i < d.length; ++i) {
this.cancelOut_(inEvent, d[i]);
}
}
}
/**
* @private
* @param {TouchEvent} browserEvent The event.
* @param {PointerEvent} inPointer The in pointer object.
*/
overDown_(browserEvent, inPointer) {
this.pointerMap[inPointer.pointerId] = {
target: inPointer.target,
out: inPointer,
outTarget: inPointer.target
};
this.dispatcher.over(inPointer, browserEvent);
this.dispatcher.enter(inPointer, browserEvent);
this.dispatcher.down(inPointer, browserEvent);
}
/**
* @private
* @param {TouchEvent} browserEvent The event.
* @param {PointerEvent} inPointer The in pointer.
*/
moveOverOut_(browserEvent, inPointer) {
const event = inPointer;
const pointer = this.pointerMap[event.pointerId];
// a finger drifted off the screen, ignore it
if (!pointer) {
return;
}
const outEvent = pointer.out;
const outTarget = pointer.outTarget;
this.dispatcher.move(event, browserEvent);
if (outEvent && outTarget !== event.target) {
outEvent.relatedTarget = event.target;
/** @type {Object} */ (event).relatedTarget = outTarget;
// recover from retargeting by shadow
outEvent.target = outTarget;
if (event.target) {
this.dispatcher.leaveOut(outEvent, browserEvent);
this.dispatcher.enterOver(event, browserEvent);
} else {
// clean up case when finger leaves the screen
/** @type {Object} */ (event).target = outTarget;
/** @type {Object} */ (event).relatedTarget = null;
this.cancelOut_(browserEvent, event);
}
}
pointer.out = event;
pointer.outTarget = event.target;
}
/**
* @private
* @param {TouchEvent} browserEvent An event.
* @param {PointerEvent} inPointer The inPointer object.
*/
upOut_(browserEvent, inPointer) {
this.dispatcher.up(inPointer, browserEvent);
this.dispatcher.out(inPointer, browserEvent);
this.dispatcher.leave(inPointer, browserEvent);
this.cleanUpPointer_(inPointer);
}
/**
* @private
* @param {TouchEvent} browserEvent The event.
* @param {PointerEvent} inPointer The in pointer.
*/
cancelOut_(browserEvent, inPointer) {
this.dispatcher.cancel(inPointer, browserEvent);
this.dispatcher.out(inPointer, browserEvent);
this.dispatcher.leave(inPointer, browserEvent);
this.cleanUpPointer_(inPointer);
}
/**
* @private
* @param {PointerEvent} inPointer The inPointer object.
*/
cleanUpPointer_(inPointer) {
delete this.pointerMap[inPointer.pointerId];
this.removePrimaryPointer_(inPointer);
}
/**
* Prevent synth mouse events from creating pointer events.
*
* @private
* @param {TouchEvent} inEvent The in event.
*/
dedupSynthMouse_(inEvent) {
const lts = this.mouseSource.lastTouches;
const t = inEvent.changedTouches[0];
// only the primary finger will synth mouse events
if (this.isPrimaryTouch_(t)) {
// remember x/y of last touch
const lt = [t.clientX, t.clientY];
lts.push(lt);
setTimeout(function() {
// remove touch after timeout
remove(lts, lt);
}, this.dedupTimeout_);
}
}
}
export default TouchSource;