1094 lines
32 KiB
HTML
1094 lines
32 KiB
HTML
|
|
<!--
|
||
|
|
@license
|
||
|
|
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
||
|
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||
|
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||
|
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||
|
|
Code distributed by Google as part of the polymer project is also
|
||
|
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||
|
|
-->
|
||
|
|
<link rel="import" href="boot.html">
|
||
|
|
<link rel="import" href="async.html">
|
||
|
|
<link rel="import" href="debounce.html">
|
||
|
|
|
||
|
|
<script>
|
||
|
|
(function() {
|
||
|
|
|
||
|
|
'use strict';
|
||
|
|
|
||
|
|
// detect native touch action support
|
||
|
|
let HAS_NATIVE_TA = typeof document.head.style.touchAction === 'string';
|
||
|
|
let GESTURE_KEY = '__polymerGestures';
|
||
|
|
let HANDLED_OBJ = '__polymerGesturesHandled';
|
||
|
|
let TOUCH_ACTION = '__polymerGesturesTouchAction';
|
||
|
|
// radius for tap and track
|
||
|
|
let TAP_DISTANCE = 25;
|
||
|
|
let TRACK_DISTANCE = 5;
|
||
|
|
// number of last N track positions to keep
|
||
|
|
let TRACK_LENGTH = 2;
|
||
|
|
|
||
|
|
// Disabling "mouse" handlers for 2500ms is enough
|
||
|
|
let MOUSE_TIMEOUT = 2500;
|
||
|
|
let MOUSE_EVENTS = ['mousedown', 'mousemove', 'mouseup', 'click'];
|
||
|
|
// an array of bitmask values for mapping MouseEvent.which to MouseEvent.buttons
|
||
|
|
let MOUSE_WHICH_TO_BUTTONS = [0, 1, 4, 2];
|
||
|
|
let MOUSE_HAS_BUTTONS = (function() {
|
||
|
|
try {
|
||
|
|
return new MouseEvent('test', {buttons: 1}).buttons === 1;
|
||
|
|
} catch (e) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
})();
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param {string} name Possible mouse event name
|
||
|
|
* @return {boolean} true if mouse event, false if not
|
||
|
|
*/
|
||
|
|
function isMouseEvent(name) {
|
||
|
|
return MOUSE_EVENTS.indexOf(name) > -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* eslint no-empty: ["error", { "allowEmptyCatch": true }] */
|
||
|
|
// check for passive event listeners
|
||
|
|
let SUPPORTS_PASSIVE = false;
|
||
|
|
(function() {
|
||
|
|
try {
|
||
|
|
let opts = Object.defineProperty({}, 'passive', {get() {SUPPORTS_PASSIVE = true;}});
|
||
|
|
window.addEventListener('test', null, opts);
|
||
|
|
window.removeEventListener('test', null, opts);
|
||
|
|
} catch(e) {}
|
||
|
|
})();
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Generate settings for event listeners, dependant on `Polymer.passiveTouchGestures`
|
||
|
|
*
|
||
|
|
* @param {string} eventName Event name to determine if `{passive}` option is needed
|
||
|
|
* @return {{passive: boolean} | undefined} Options to use for addEventListener and removeEventListener
|
||
|
|
*/
|
||
|
|
function PASSIVE_TOUCH(eventName) {
|
||
|
|
if (isMouseEvent(eventName) || eventName === 'touchend') {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (HAS_NATIVE_TA && SUPPORTS_PASSIVE && Polymer.passiveTouchGestures) {
|
||
|
|
return {passive: true};
|
||
|
|
} else {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check for touch-only devices
|
||
|
|
let IS_TOUCH_ONLY = navigator.userAgent.match(/iP(?:[oa]d|hone)|Android/);
|
||
|
|
|
||
|
|
let GestureRecognizer = function(){}; // eslint-disable-line no-unused-vars
|
||
|
|
/** @type {function(): void} */
|
||
|
|
GestureRecognizer.prototype.reset;
|
||
|
|
/** @type {function(MouseEvent): void | undefined} */
|
||
|
|
GestureRecognizer.prototype.mousedown;
|
||
|
|
/** @type {(function(MouseEvent): void | undefined)} */
|
||
|
|
GestureRecognizer.prototype.mousemove;
|
||
|
|
/** @type {(function(MouseEvent): void | undefined)} */
|
||
|
|
GestureRecognizer.prototype.mouseup;
|
||
|
|
/** @type {(function(TouchEvent): void | undefined)} */
|
||
|
|
GestureRecognizer.prototype.touchstart;
|
||
|
|
/** @type {(function(TouchEvent): void | undefined)} */
|
||
|
|
GestureRecognizer.prototype.touchmove;
|
||
|
|
/** @type {(function(TouchEvent): void | undefined)} */
|
||
|
|
GestureRecognizer.prototype.touchend;
|
||
|
|
/** @type {(function(MouseEvent): void | undefined)} */
|
||
|
|
GestureRecognizer.prototype.click;
|
||
|
|
|
||
|
|
// keep track of any labels hit by the mouseCanceller
|
||
|
|
/** @type {!Array<!HTMLLabelElement>} */
|
||
|
|
const clickedLabels = [];
|
||
|
|
|
||
|
|
/** @type {!Object<boolean>} */
|
||
|
|
const labellable = {
|
||
|
|
'button': true,
|
||
|
|
'input': true,
|
||
|
|
'keygen': true,
|
||
|
|
'meter': true,
|
||
|
|
'output': true,
|
||
|
|
'textarea': true,
|
||
|
|
'progress': true,
|
||
|
|
'select': true
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param {HTMLElement} el Element to check labelling status
|
||
|
|
* @return {boolean} element can have labels
|
||
|
|
*/
|
||
|
|
function canBeLabelled(el) {
|
||
|
|
return labellable[el.localName] || false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param {HTMLElement} el Element that may be labelled.
|
||
|
|
* @return {!Array<!HTMLLabelElement>} Relevant label for `el`
|
||
|
|
*/
|
||
|
|
function matchingLabels(el) {
|
||
|
|
let labels = [...(/** @type {HTMLInputElement} */(el).labels || [])];
|
||
|
|
// IE doesn't have `labels` and Safari doesn't populate `labels`
|
||
|
|
// if element is in a shadowroot.
|
||
|
|
// In this instance, finding the non-ancestor labels is enough,
|
||
|
|
// as the mouseCancellor code will handle ancstor labels
|
||
|
|
if (!labels.length) {
|
||
|
|
labels = [];
|
||
|
|
let root = el.getRootNode();
|
||
|
|
// if there is an id on `el`, check for all labels with a matching `for` attribute
|
||
|
|
if (el.id) {
|
||
|
|
let matching = root.querySelectorAll(`label[for = ${el.id}]`);
|
||
|
|
for (let i = 0; i < matching.length; i++) {
|
||
|
|
labels.push(/** @type {!HTMLLabelElement} */(matching[i]));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return labels;
|
||
|
|
}
|
||
|
|
|
||
|
|
// touch will make synthetic mouse events
|
||
|
|
// `preventDefault` on touchend will cancel them,
|
||
|
|
// but this breaks `<input>` focus and link clicks
|
||
|
|
// disable mouse handlers for MOUSE_TIMEOUT ms after
|
||
|
|
// a touchend to ignore synthetic mouse events
|
||
|
|
let mouseCanceller = function(mouseEvent) {
|
||
|
|
// Check for sourceCapabilities, used to distinguish synthetic events
|
||
|
|
// if mouseEvent did not come from a device that fires touch events,
|
||
|
|
// it was made by a real mouse and should be counted
|
||
|
|
// http://wicg.github.io/InputDeviceCapabilities/#dom-inputdevicecapabilities-firestouchevents
|
||
|
|
let sc = mouseEvent.sourceCapabilities;
|
||
|
|
if (sc && !sc.firesTouchEvents) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
// skip synthetic mouse events
|
||
|
|
mouseEvent[HANDLED_OBJ] = {skip: true};
|
||
|
|
// disable "ghost clicks"
|
||
|
|
if (mouseEvent.type === 'click') {
|
||
|
|
let clickFromLabel = false;
|
||
|
|
let path = mouseEvent.composedPath && mouseEvent.composedPath();
|
||
|
|
if (path) {
|
||
|
|
for (let i = 0; i < path.length; i++) {
|
||
|
|
if (path[i].nodeType === Node.ELEMENT_NODE) {
|
||
|
|
if (path[i].localName === 'label') {
|
||
|
|
clickedLabels.push(path[i]);
|
||
|
|
} else if (canBeLabelled(path[i])) {
|
||
|
|
let ownerLabels = matchingLabels(path[i]);
|
||
|
|
// check if one of the clicked labels is labelling this element
|
||
|
|
for (let j = 0; j < ownerLabels.length; j++) {
|
||
|
|
clickFromLabel = clickFromLabel || clickedLabels.indexOf(ownerLabels[j]) > -1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (path[i] === POINTERSTATE.mouse.target) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// if one of the clicked labels was labelling the target element,
|
||
|
|
// this is not a ghost click
|
||
|
|
if (clickFromLabel) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
mouseEvent.preventDefault();
|
||
|
|
mouseEvent.stopPropagation();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param {boolean=} setup True to add, false to remove.
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
function setupTeardownMouseCanceller(setup) {
|
||
|
|
let events = IS_TOUCH_ONLY ? ['click'] : MOUSE_EVENTS;
|
||
|
|
for (let i = 0, en; i < events.length; i++) {
|
||
|
|
en = events[i];
|
||
|
|
if (setup) {
|
||
|
|
// reset clickLabels array
|
||
|
|
clickedLabels.length = 0;
|
||
|
|
document.addEventListener(en, mouseCanceller, true);
|
||
|
|
} else {
|
||
|
|
document.removeEventListener(en, mouseCanceller, true);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function ignoreMouse(e) {
|
||
|
|
if (!POINTERSTATE.mouse.mouseIgnoreJob) {
|
||
|
|
setupTeardownMouseCanceller(true);
|
||
|
|
}
|
||
|
|
let unset = function() {
|
||
|
|
setupTeardownMouseCanceller();
|
||
|
|
POINTERSTATE.mouse.target = null;
|
||
|
|
POINTERSTATE.mouse.mouseIgnoreJob = null;
|
||
|
|
};
|
||
|
|
POINTERSTATE.mouse.target = e.composedPath()[0];
|
||
|
|
POINTERSTATE.mouse.mouseIgnoreJob = Polymer.Debouncer.debounce(
|
||
|
|
POINTERSTATE.mouse.mouseIgnoreJob
|
||
|
|
, Polymer.Async.timeOut.after(MOUSE_TIMEOUT)
|
||
|
|
, unset);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param {MouseEvent} ev event to test for left mouse button down
|
||
|
|
* @return {boolean} has left mouse button down
|
||
|
|
*/
|
||
|
|
function hasLeftMouseButton(ev) {
|
||
|
|
let type = ev.type;
|
||
|
|
// exit early if the event is not a mouse event
|
||
|
|
if (!isMouseEvent(type)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
// ev.button is not reliable for mousemove (0 is overloaded as both left button and no buttons)
|
||
|
|
// instead we use ev.buttons (bitmask of buttons) or fall back to ev.which (deprecated, 0 for no buttons, 1 for left button)
|
||
|
|
if (type === 'mousemove') {
|
||
|
|
// allow undefined for testing events
|
||
|
|
let buttons = ev.buttons === undefined ? 1 : ev.buttons;
|
||
|
|
if ((ev instanceof window.MouseEvent) && !MOUSE_HAS_BUTTONS) {
|
||
|
|
buttons = MOUSE_WHICH_TO_BUTTONS[ev.which] || 0;
|
||
|
|
}
|
||
|
|
// buttons is a bitmask, check that the left button bit is set (1)
|
||
|
|
return Boolean(buttons & 1);
|
||
|
|
} else {
|
||
|
|
// allow undefined for testing events
|
||
|
|
let button = ev.button === undefined ? 0 : ev.button;
|
||
|
|
// ev.button is 0 in mousedown/mouseup/click for left button activation
|
||
|
|
return button === 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function isSyntheticClick(ev) {
|
||
|
|
if (ev.type === 'click') {
|
||
|
|
// ev.detail is 0 for HTMLElement.click in most browsers
|
||
|
|
if (ev.detail === 0) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
// in the worst case, check that the x/y position of the click is within
|
||
|
|
// the bounding box of the target of the event
|
||
|
|
// Thanks IE 10 >:(
|
||
|
|
let t = Gestures._findOriginalTarget(ev);
|
||
|
|
// make sure the target of the event is an element so we can use getBoundingClientRect,
|
||
|
|
// if not, just assume it is a synthetic click
|
||
|
|
if (!t.nodeType || /** @type {Element} */(t).nodeType !== Node.ELEMENT_NODE) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
let bcr = /** @type {Element} */(t).getBoundingClientRect();
|
||
|
|
// use page x/y to account for scrolling
|
||
|
|
let x = ev.pageX, y = ev.pageY;
|
||
|
|
// ev is a synthetic click if the position is outside the bounding box of the target
|
||
|
|
return !((x >= bcr.left && x <= bcr.right) && (y >= bcr.top && y <= bcr.bottom));
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
let POINTERSTATE = {
|
||
|
|
mouse: {
|
||
|
|
target: null,
|
||
|
|
mouseIgnoreJob: null
|
||
|
|
},
|
||
|
|
touch: {
|
||
|
|
x: 0,
|
||
|
|
y: 0,
|
||
|
|
id: -1,
|
||
|
|
scrollDecided: false
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
function firstTouchAction(ev) {
|
||
|
|
let ta = 'auto';
|
||
|
|
let path = ev.composedPath && ev.composedPath();
|
||
|
|
if (path) {
|
||
|
|
for (let i = 0, n; i < path.length; i++) {
|
||
|
|
n = path[i];
|
||
|
|
if (n[TOUCH_ACTION]) {
|
||
|
|
ta = n[TOUCH_ACTION];
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return ta;
|
||
|
|
}
|
||
|
|
|
||
|
|
function trackDocument(stateObj, movefn, upfn) {
|
||
|
|
stateObj.movefn = movefn;
|
||
|
|
stateObj.upfn = upfn;
|
||
|
|
document.addEventListener('mousemove', movefn);
|
||
|
|
document.addEventListener('mouseup', upfn);
|
||
|
|
}
|
||
|
|
|
||
|
|
function untrackDocument(stateObj) {
|
||
|
|
document.removeEventListener('mousemove', stateObj.movefn);
|
||
|
|
document.removeEventListener('mouseup', stateObj.upfn);
|
||
|
|
stateObj.movefn = null;
|
||
|
|
stateObj.upfn = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
// use a document-wide touchend listener to start the ghost-click prevention mechanism
|
||
|
|
// Use passive event listeners, if supported, to not affect scrolling performance
|
||
|
|
document.addEventListener('touchend', ignoreMouse, SUPPORTS_PASSIVE ? {passive: true} : false);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Module for adding listeners to a node for the following normalized
|
||
|
|
* cross-platform "gesture" events:
|
||
|
|
* - `down` - mouse or touch went down
|
||
|
|
* - `up` - mouse or touch went up
|
||
|
|
* - `tap` - mouse click or finger tap
|
||
|
|
* - `track` - mouse drag or touch move
|
||
|
|
*
|
||
|
|
* @namespace
|
||
|
|
* @memberof Polymer
|
||
|
|
* @summary Module for adding cross-platform gesture event listeners.
|
||
|
|
*/
|
||
|
|
const Gestures = {
|
||
|
|
gestures: {},
|
||
|
|
recognizers: [],
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Finds the element rendered on the screen at the provided coordinates.
|
||
|
|
*
|
||
|
|
* Similar to `document.elementFromPoint`, but pierces through
|
||
|
|
* shadow roots.
|
||
|
|
*
|
||
|
|
* @memberof Polymer.Gestures
|
||
|
|
* @param {number} x Horizontal pixel coordinate
|
||
|
|
* @param {number} y Vertical pixel coordinate
|
||
|
|
* @return {Element} Returns the deepest shadowRoot inclusive element
|
||
|
|
* found at the screen position given.
|
||
|
|
*/
|
||
|
|
deepTargetFind: function(x, y) {
|
||
|
|
let node = document.elementFromPoint(x, y);
|
||
|
|
let next = node;
|
||
|
|
// this code path is only taken when native ShadowDOM is used
|
||
|
|
// if there is a shadowroot, it may have a node at x/y
|
||
|
|
// if there is not a shadowroot, exit the loop
|
||
|
|
while (next && next.shadowRoot && !window.ShadyDOM) {
|
||
|
|
// if there is a node at x/y in the shadowroot, look deeper
|
||
|
|
let oldNext = next;
|
||
|
|
next = next.shadowRoot.elementFromPoint(x, y);
|
||
|
|
// on Safari, elementFromPoint may return the shadowRoot host
|
||
|
|
if (oldNext === next) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
if (next) {
|
||
|
|
node = next;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return node;
|
||
|
|
},
|
||
|
|
/**
|
||
|
|
* a cheaper check than ev.composedPath()[0];
|
||
|
|
*
|
||
|
|
* @private
|
||
|
|
* @param {Event} ev Event.
|
||
|
|
* @return {EventTarget} Returns the event target.
|
||
|
|
*/
|
||
|
|
_findOriginalTarget: function(ev) {
|
||
|
|
// shadowdom
|
||
|
|
if (ev.composedPath) {
|
||
|
|
const targets = /** @type {!Array<!EventTarget>} */(ev.composedPath());
|
||
|
|
// It shouldn't be, but sometimes targets is empty (window on Safari).
|
||
|
|
return targets.length > 0 ? targets[0] : ev.target;
|
||
|
|
}
|
||
|
|
// shadydom
|
||
|
|
return ev.target;
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @private
|
||
|
|
* @param {Event} ev Event.
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
_handleNative: function(ev) {
|
||
|
|
let handled;
|
||
|
|
let type = ev.type;
|
||
|
|
let node = ev.currentTarget;
|
||
|
|
let gobj = node[GESTURE_KEY];
|
||
|
|
if (!gobj) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
let gs = gobj[type];
|
||
|
|
if (!gs) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (!ev[HANDLED_OBJ]) {
|
||
|
|
ev[HANDLED_OBJ] = {};
|
||
|
|
if (type.slice(0, 5) === 'touch') {
|
||
|
|
ev = /** @type {TouchEvent} */(ev); // eslint-disable-line no-self-assign
|
||
|
|
let t = ev.changedTouches[0];
|
||
|
|
if (type === 'touchstart') {
|
||
|
|
// only handle the first finger
|
||
|
|
if (ev.touches.length === 1) {
|
||
|
|
POINTERSTATE.touch.id = t.identifier;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (POINTERSTATE.touch.id !== t.identifier) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (!HAS_NATIVE_TA) {
|
||
|
|
if (type === 'touchstart' || type === 'touchmove') {
|
||
|
|
Gestures._handleTouchAction(ev);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
handled = ev[HANDLED_OBJ];
|
||
|
|
// used to ignore synthetic mouse events
|
||
|
|
if (handled.skip) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
// reset recognizer state
|
||
|
|
for (let i = 0, r; i < Gestures.recognizers.length; i++) {
|
||
|
|
r = Gestures.recognizers[i];
|
||
|
|
if (gs[r.name] && !handled[r.name]) {
|
||
|
|
if (r.flow && r.flow.start.indexOf(ev.type) > -1 && r.reset) {
|
||
|
|
r.reset();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// enforce gesture recognizer order
|
||
|
|
for (let i = 0, r; i < Gestures.recognizers.length; i++) {
|
||
|
|
r = Gestures.recognizers[i];
|
||
|
|
if (gs[r.name] && !handled[r.name]) {
|
||
|
|
handled[r.name] = true;
|
||
|
|
r[type](ev);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @private
|
||
|
|
* @param {TouchEvent} ev Event.
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
_handleTouchAction: function(ev) {
|
||
|
|
let t = ev.changedTouches[0];
|
||
|
|
let type = ev.type;
|
||
|
|
if (type === 'touchstart') {
|
||
|
|
POINTERSTATE.touch.x = t.clientX;
|
||
|
|
POINTERSTATE.touch.y = t.clientY;
|
||
|
|
POINTERSTATE.touch.scrollDecided = false;
|
||
|
|
} else if (type === 'touchmove') {
|
||
|
|
if (POINTERSTATE.touch.scrollDecided) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
POINTERSTATE.touch.scrollDecided = true;
|
||
|
|
let ta = firstTouchAction(ev);
|
||
|
|
let prevent = false;
|
||
|
|
let dx = Math.abs(POINTERSTATE.touch.x - t.clientX);
|
||
|
|
let dy = Math.abs(POINTERSTATE.touch.y - t.clientY);
|
||
|
|
if (!ev.cancelable) {
|
||
|
|
// scrolling is happening
|
||
|
|
} else if (ta === 'none') {
|
||
|
|
prevent = true;
|
||
|
|
} else if (ta === 'pan-x') {
|
||
|
|
prevent = dy > dx;
|
||
|
|
} else if (ta === 'pan-y') {
|
||
|
|
prevent = dx > dy;
|
||
|
|
}
|
||
|
|
if (prevent) {
|
||
|
|
ev.preventDefault();
|
||
|
|
} else {
|
||
|
|
Gestures.prevent('track');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Adds an event listener to a node for the given gesture type.
|
||
|
|
*
|
||
|
|
* @memberof Polymer.Gestures
|
||
|
|
* @param {!Node} node Node to add listener on
|
||
|
|
* @param {string} evType Gesture type: `down`, `up`, `track`, or `tap`
|
||
|
|
* @param {!function(!Event):void} handler Event listener function to call
|
||
|
|
* @return {boolean} Returns true if a gesture event listener was added.
|
||
|
|
* @this {Gestures}
|
||
|
|
*/
|
||
|
|
addListener: function(node, evType, handler) {
|
||
|
|
if (this.gestures[evType]) {
|
||
|
|
this._add(node, evType, handler);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Removes an event listener from a node for the given gesture type.
|
||
|
|
*
|
||
|
|
* @memberof Polymer.Gestures
|
||
|
|
* @param {!Node} node Node to remove listener from
|
||
|
|
* @param {string} evType Gesture type: `down`, `up`, `track`, or `tap`
|
||
|
|
* @param {!function(!Event):void} handler Event listener function previously passed to
|
||
|
|
* `addListener`.
|
||
|
|
* @return {boolean} Returns true if a gesture event listener was removed.
|
||
|
|
* @this {Gestures}
|
||
|
|
*/
|
||
|
|
removeListener: function(node, evType, handler) {
|
||
|
|
if (this.gestures[evType]) {
|
||
|
|
this._remove(node, evType, handler);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* automate the event listeners for the native events
|
||
|
|
*
|
||
|
|
* @private
|
||
|
|
* @param {!HTMLElement} node Node on which to add the event.
|
||
|
|
* @param {string} evType Event type to add.
|
||
|
|
* @param {function(!Event)} handler Event handler function.
|
||
|
|
* @return {void}
|
||
|
|
* @this {Gestures}
|
||
|
|
*/
|
||
|
|
_add: function(node, evType, handler) {
|
||
|
|
let recognizer = this.gestures[evType];
|
||
|
|
let deps = recognizer.deps;
|
||
|
|
let name = recognizer.name;
|
||
|
|
let gobj = node[GESTURE_KEY];
|
||
|
|
if (!gobj) {
|
||
|
|
node[GESTURE_KEY] = gobj = {};
|
||
|
|
}
|
||
|
|
for (let i = 0, dep, gd; i < deps.length; i++) {
|
||
|
|
dep = deps[i];
|
||
|
|
// don't add mouse handlers on iOS because they cause gray selection overlays
|
||
|
|
if (IS_TOUCH_ONLY && isMouseEvent(dep) && dep !== 'click') {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
gd = gobj[dep];
|
||
|
|
if (!gd) {
|
||
|
|
gobj[dep] = gd = {_count: 0};
|
||
|
|
}
|
||
|
|
if (gd._count === 0) {
|
||
|
|
node.addEventListener(dep, this._handleNative, PASSIVE_TOUCH(dep));
|
||
|
|
}
|
||
|
|
gd[name] = (gd[name] || 0) + 1;
|
||
|
|
gd._count = (gd._count || 0) + 1;
|
||
|
|
}
|
||
|
|
node.addEventListener(evType, handler);
|
||
|
|
if (recognizer.touchAction) {
|
||
|
|
this.setTouchAction(node, recognizer.touchAction);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* automate event listener removal for native events
|
||
|
|
*
|
||
|
|
* @private
|
||
|
|
* @param {!HTMLElement} node Node on which to remove the event.
|
||
|
|
* @param {string} evType Event type to remove.
|
||
|
|
* @param {function(Event?)} handler Event handler function.
|
||
|
|
* @return {void}
|
||
|
|
* @this {Gestures}
|
||
|
|
*/
|
||
|
|
_remove: function(node, evType, handler) {
|
||
|
|
let recognizer = this.gestures[evType];
|
||
|
|
let deps = recognizer.deps;
|
||
|
|
let name = recognizer.name;
|
||
|
|
let gobj = node[GESTURE_KEY];
|
||
|
|
if (gobj) {
|
||
|
|
for (let i = 0, dep, gd; i < deps.length; i++) {
|
||
|
|
dep = deps[i];
|
||
|
|
gd = gobj[dep];
|
||
|
|
if (gd && gd[name]) {
|
||
|
|
gd[name] = (gd[name] || 1) - 1;
|
||
|
|
gd._count = (gd._count || 1) - 1;
|
||
|
|
if (gd._count === 0) {
|
||
|
|
node.removeEventListener(dep, this._handleNative, PASSIVE_TOUCH(dep));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
node.removeEventListener(evType, handler);
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Registers a new gesture event recognizer for adding new custom
|
||
|
|
* gesture event types.
|
||
|
|
*
|
||
|
|
* @memberof Polymer.Gestures
|
||
|
|
* @param {!GestureRecognizer} recog Gesture recognizer descriptor
|
||
|
|
* @return {void}
|
||
|
|
* @this {Gestures}
|
||
|
|
*/
|
||
|
|
register: function(recog) {
|
||
|
|
this.recognizers.push(recog);
|
||
|
|
for (let i = 0; i < recog.emits.length; i++) {
|
||
|
|
this.gestures[recog.emits[i]] = recog;
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @private
|
||
|
|
* @param {string} evName Event name.
|
||
|
|
* @return {Object} Returns the gesture for the given event name.
|
||
|
|
* @this {Gestures}
|
||
|
|
*/
|
||
|
|
_findRecognizerByEvent: function(evName) {
|
||
|
|
for (let i = 0, r; i < this.recognizers.length; i++) {
|
||
|
|
r = this.recognizers[i];
|
||
|
|
for (let j = 0, n; j < r.emits.length; j++) {
|
||
|
|
n = r.emits[j];
|
||
|
|
if (n === evName) {
|
||
|
|
return r;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Sets scrolling direction on node.
|
||
|
|
*
|
||
|
|
* This value is checked on first move, thus it should be called prior to
|
||
|
|
* adding event listeners.
|
||
|
|
*
|
||
|
|
* @memberof Polymer.Gestures
|
||
|
|
* @param {!Element} node Node to set touch action setting on
|
||
|
|
* @param {string} value Touch action value
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
setTouchAction: function(node, value) {
|
||
|
|
if (HAS_NATIVE_TA) {
|
||
|
|
// NOTE: add touchAction async so that events can be added in
|
||
|
|
// custom element constructors. Otherwise we run afoul of custom
|
||
|
|
// elements restriction against settings attributes (style) in the
|
||
|
|
// constructor.
|
||
|
|
Polymer.Async.microTask.run(() => {
|
||
|
|
node.style.touchAction = value;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
node[TOUCH_ACTION] = value;
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Dispatches an event on the `target` element of `type` with the given
|
||
|
|
* `detail`.
|
||
|
|
* @private
|
||
|
|
* @param {!EventTarget} target The element on which to fire an event.
|
||
|
|
* @param {string} type The type of event to fire.
|
||
|
|
* @param {!Object=} detail The detail object to populate on the event.
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
_fire: function(target, type, detail) {
|
||
|
|
let ev = new Event(type, { bubbles: true, cancelable: true, composed: true });
|
||
|
|
ev.detail = detail;
|
||
|
|
target.dispatchEvent(ev);
|
||
|
|
// forward `preventDefault` in a clean way
|
||
|
|
if (ev.defaultPrevented) {
|
||
|
|
let preventer = detail.preventer || detail.sourceEvent;
|
||
|
|
if (preventer && preventer.preventDefault) {
|
||
|
|
preventer.preventDefault();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Prevents the dispatch and default action of the given event name.
|
||
|
|
*
|
||
|
|
* @memberof Polymer.Gestures
|
||
|
|
* @param {string} evName Event name.
|
||
|
|
* @return {void}
|
||
|
|
* @this {Gestures}
|
||
|
|
*/
|
||
|
|
prevent: function(evName) {
|
||
|
|
let recognizer = this._findRecognizerByEvent(evName);
|
||
|
|
if (recognizer.info) {
|
||
|
|
recognizer.info.prevent = true;
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Reset the 2500ms timeout on processing mouse input after detecting touch input.
|
||
|
|
*
|
||
|
|
* Touch inputs create synthesized mouse inputs anywhere from 0 to 2000ms after the touch.
|
||
|
|
* This method should only be called during testing with simulated touch inputs.
|
||
|
|
* Calling this method in production may cause duplicate taps or other Gestures.
|
||
|
|
*
|
||
|
|
* @memberof Polymer.Gestures
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
resetMouseCanceller: function() {
|
||
|
|
if (POINTERSTATE.mouse.mouseIgnoreJob) {
|
||
|
|
POINTERSTATE.mouse.mouseIgnoreJob.flush();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
/* eslint-disable valid-jsdoc */
|
||
|
|
|
||
|
|
Gestures.register({
|
||
|
|
name: 'downup',
|
||
|
|
deps: ['mousedown', 'touchstart', 'touchend'],
|
||
|
|
flow: {
|
||
|
|
start: ['mousedown', 'touchstart'],
|
||
|
|
end: ['mouseup', 'touchend']
|
||
|
|
},
|
||
|
|
emits: ['down', 'up'],
|
||
|
|
|
||
|
|
info: {
|
||
|
|
movefn: null,
|
||
|
|
upfn: null
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @this {GestureRecognizer}
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
reset: function() {
|
||
|
|
untrackDocument(this.info);
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @this {GestureRecognizer}
|
||
|
|
* @param {MouseEvent} e
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
mousedown: function(e) {
|
||
|
|
if (!hasLeftMouseButton(e)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
let t = Gestures._findOriginalTarget(e);
|
||
|
|
let self = this;
|
||
|
|
let movefn = function movefn(e) {
|
||
|
|
if (!hasLeftMouseButton(e)) {
|
||
|
|
self._fire('up', t, e);
|
||
|
|
untrackDocument(self.info);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
let upfn = function upfn(e) {
|
||
|
|
if (hasLeftMouseButton(e)) {
|
||
|
|
self._fire('up', t, e);
|
||
|
|
}
|
||
|
|
untrackDocument(self.info);
|
||
|
|
};
|
||
|
|
trackDocument(this.info, movefn, upfn);
|
||
|
|
this._fire('down', t, e);
|
||
|
|
},
|
||
|
|
/**
|
||
|
|
* @this {GestureRecognizer}
|
||
|
|
* @param {TouchEvent} e
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
touchstart: function(e) {
|
||
|
|
this._fire('down', Gestures._findOriginalTarget(e), e.changedTouches[0], e);
|
||
|
|
},
|
||
|
|
/**
|
||
|
|
* @this {GestureRecognizer}
|
||
|
|
* @param {TouchEvent} e
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
touchend: function(e) {
|
||
|
|
this._fire('up', Gestures._findOriginalTarget(e), e.changedTouches[0], e);
|
||
|
|
},
|
||
|
|
/**
|
||
|
|
* @param {string} type
|
||
|
|
* @param {!EventTarget} target
|
||
|
|
* @param {Event} event
|
||
|
|
* @param {Function} preventer
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
_fire: function(type, target, event, preventer) {
|
||
|
|
Gestures._fire(target, type, {
|
||
|
|
x: event.clientX,
|
||
|
|
y: event.clientY,
|
||
|
|
sourceEvent: event,
|
||
|
|
preventer: preventer,
|
||
|
|
prevent: function(e) {
|
||
|
|
return Gestures.prevent(e);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
Gestures.register({
|
||
|
|
name: 'track',
|
||
|
|
touchAction: 'none',
|
||
|
|
deps: ['mousedown', 'touchstart', 'touchmove', 'touchend'],
|
||
|
|
flow: {
|
||
|
|
start: ['mousedown', 'touchstart'],
|
||
|
|
end: ['mouseup', 'touchend']
|
||
|
|
},
|
||
|
|
emits: ['track'],
|
||
|
|
|
||
|
|
info: {
|
||
|
|
x: 0,
|
||
|
|
y: 0,
|
||
|
|
state: 'start',
|
||
|
|
started: false,
|
||
|
|
moves: [],
|
||
|
|
/** @this {GestureRecognizer} */
|
||
|
|
addMove: function(move) {
|
||
|
|
if (this.moves.length > TRACK_LENGTH) {
|
||
|
|
this.moves.shift();
|
||
|
|
}
|
||
|
|
this.moves.push(move);
|
||
|
|
},
|
||
|
|
movefn: null,
|
||
|
|
upfn: null,
|
||
|
|
prevent: false
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @this {GestureRecognizer}
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
reset: function() {
|
||
|
|
this.info.state = 'start';
|
||
|
|
this.info.started = false;
|
||
|
|
this.info.moves = [];
|
||
|
|
this.info.x = 0;
|
||
|
|
this.info.y = 0;
|
||
|
|
this.info.prevent = false;
|
||
|
|
untrackDocument(this.info);
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @this {GestureRecognizer}
|
||
|
|
* @param {number} x
|
||
|
|
* @param {number} y
|
||
|
|
* @return {boolean}
|
||
|
|
*/
|
||
|
|
hasMovedEnough: function(x, y) {
|
||
|
|
if (this.info.prevent) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (this.info.started) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
let dx = Math.abs(this.info.x - x);
|
||
|
|
let dy = Math.abs(this.info.y - y);
|
||
|
|
return (dx >= TRACK_DISTANCE || dy >= TRACK_DISTANCE);
|
||
|
|
},
|
||
|
|
/**
|
||
|
|
* @this {GestureRecognizer}
|
||
|
|
* @param {MouseEvent} e
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
mousedown: function(e) {
|
||
|
|
if (!hasLeftMouseButton(e)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
let t = Gestures._findOriginalTarget(e);
|
||
|
|
let self = this;
|
||
|
|
let movefn = function movefn(e) {
|
||
|
|
let x = e.clientX, y = e.clientY;
|
||
|
|
if (self.hasMovedEnough(x, y)) {
|
||
|
|
// first move is 'start', subsequent moves are 'move', mouseup is 'end'
|
||
|
|
self.info.state = self.info.started ? (e.type === 'mouseup' ? 'end' : 'track') : 'start';
|
||
|
|
if (self.info.state === 'start') {
|
||
|
|
// if and only if tracking, always prevent tap
|
||
|
|
Gestures.prevent('tap');
|
||
|
|
}
|
||
|
|
self.info.addMove({x: x, y: y});
|
||
|
|
if (!hasLeftMouseButton(e)) {
|
||
|
|
// always _fire "end"
|
||
|
|
self.info.state = 'end';
|
||
|
|
untrackDocument(self.info);
|
||
|
|
}
|
||
|
|
self._fire(t, e);
|
||
|
|
self.info.started = true;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
let upfn = function upfn(e) {
|
||
|
|
if (self.info.started) {
|
||
|
|
movefn(e);
|
||
|
|
}
|
||
|
|
|
||
|
|
// remove the temporary listeners
|
||
|
|
untrackDocument(self.info);
|
||
|
|
};
|
||
|
|
// add temporary document listeners as mouse retargets
|
||
|
|
trackDocument(this.info, movefn, upfn);
|
||
|
|
this.info.x = e.clientX;
|
||
|
|
this.info.y = e.clientY;
|
||
|
|
},
|
||
|
|
/**
|
||
|
|
* @this {GestureRecognizer}
|
||
|
|
* @param {TouchEvent} e
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
touchstart: function(e) {
|
||
|
|
let ct = e.changedTouches[0];
|
||
|
|
this.info.x = ct.clientX;
|
||
|
|
this.info.y = ct.clientY;
|
||
|
|
},
|
||
|
|
/**
|
||
|
|
* @this {GestureRecognizer}
|
||
|
|
* @param {TouchEvent} e
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
touchmove: function(e) {
|
||
|
|
let t = Gestures._findOriginalTarget(e);
|
||
|
|
let ct = e.changedTouches[0];
|
||
|
|
let x = ct.clientX, y = ct.clientY;
|
||
|
|
if (this.hasMovedEnough(x, y)) {
|
||
|
|
if (this.info.state === 'start') {
|
||
|
|
// if and only if tracking, always prevent tap
|
||
|
|
Gestures.prevent('tap');
|
||
|
|
}
|
||
|
|
this.info.addMove({x: x, y: y});
|
||
|
|
this._fire(t, ct);
|
||
|
|
this.info.state = 'track';
|
||
|
|
this.info.started = true;
|
||
|
|
}
|
||
|
|
},
|
||
|
|
/**
|
||
|
|
* @this {GestureRecognizer}
|
||
|
|
* @param {TouchEvent} e
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
touchend: function(e) {
|
||
|
|
let t = Gestures._findOriginalTarget(e);
|
||
|
|
let ct = e.changedTouches[0];
|
||
|
|
// only trackend if track was started and not aborted
|
||
|
|
if (this.info.started) {
|
||
|
|
// reset started state on up
|
||
|
|
this.info.state = 'end';
|
||
|
|
this.info.addMove({x: ct.clientX, y: ct.clientY});
|
||
|
|
this._fire(t, ct, e);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @this {GestureRecognizer}
|
||
|
|
* @param {!EventTarget} target
|
||
|
|
* @param {Touch} touch
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
_fire: function(target, touch) {
|
||
|
|
let secondlast = this.info.moves[this.info.moves.length - 2];
|
||
|
|
let lastmove = this.info.moves[this.info.moves.length - 1];
|
||
|
|
let dx = lastmove.x - this.info.x;
|
||
|
|
let dy = lastmove.y - this.info.y;
|
||
|
|
let ddx, ddy = 0;
|
||
|
|
if (secondlast) {
|
||
|
|
ddx = lastmove.x - secondlast.x;
|
||
|
|
ddy = lastmove.y - secondlast.y;
|
||
|
|
}
|
||
|
|
Gestures._fire(target, 'track', {
|
||
|
|
state: this.info.state,
|
||
|
|
x: touch.clientX,
|
||
|
|
y: touch.clientY,
|
||
|
|
dx: dx,
|
||
|
|
dy: dy,
|
||
|
|
ddx: ddx,
|
||
|
|
ddy: ddy,
|
||
|
|
sourceEvent: touch,
|
||
|
|
hover: function() {
|
||
|
|
return Gestures.deepTargetFind(touch.clientX, touch.clientY);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
});
|
||
|
|
|
||
|
|
Gestures.register({
|
||
|
|
name: 'tap',
|
||
|
|
deps: ['mousedown', 'click', 'touchstart', 'touchend'],
|
||
|
|
flow: {
|
||
|
|
start: ['mousedown', 'touchstart'],
|
||
|
|
end: ['click', 'touchend']
|
||
|
|
},
|
||
|
|
emits: ['tap'],
|
||
|
|
info: {
|
||
|
|
x: NaN,
|
||
|
|
y: NaN,
|
||
|
|
prevent: false
|
||
|
|
},
|
||
|
|
/**
|
||
|
|
* @this {GestureRecognizer}
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
reset: function() {
|
||
|
|
this.info.x = NaN;
|
||
|
|
this.info.y = NaN;
|
||
|
|
this.info.prevent = false;
|
||
|
|
},
|
||
|
|
/**
|
||
|
|
* @this {GestureRecognizer}
|
||
|
|
* @param {MouseEvent} e
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
save: function(e) {
|
||
|
|
this.info.x = e.clientX;
|
||
|
|
this.info.y = e.clientY;
|
||
|
|
},
|
||
|
|
/**
|
||
|
|
* @this {GestureRecognizer}
|
||
|
|
* @param {MouseEvent} e
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
mousedown: function(e) {
|
||
|
|
if (hasLeftMouseButton(e)) {
|
||
|
|
this.save(e);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
/**
|
||
|
|
* @this {GestureRecognizer}
|
||
|
|
* @param {MouseEvent} e
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
click: function(e) {
|
||
|
|
if (hasLeftMouseButton(e)) {
|
||
|
|
this.forward(e);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
/**
|
||
|
|
* @this {GestureRecognizer}
|
||
|
|
* @param {TouchEvent} e
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
touchstart: function(e) {
|
||
|
|
this.save(e.changedTouches[0], e);
|
||
|
|
},
|
||
|
|
/**
|
||
|
|
* @this {GestureRecognizer}
|
||
|
|
* @param {TouchEvent} e
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
touchend: function(e) {
|
||
|
|
this.forward(e.changedTouches[0], e);
|
||
|
|
},
|
||
|
|
/**
|
||
|
|
* @this {GestureRecognizer}
|
||
|
|
* @param {Event | Touch} e
|
||
|
|
* @param {Event=} preventer
|
||
|
|
* @return {void}
|
||
|
|
*/
|
||
|
|
forward: function(e, preventer) {
|
||
|
|
let dx = Math.abs(e.clientX - this.info.x);
|
||
|
|
let dy = Math.abs(e.clientY - this.info.y);
|
||
|
|
// find original target from `preventer` for TouchEvents, or `e` for MouseEvents
|
||
|
|
let t = Gestures._findOriginalTarget(/** @type {Event} */(preventer || e));
|
||
|
|
if (!t || t.disabled) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
// dx,dy can be NaN if `click` has been simulated and there was no `down` for `start`
|
||
|
|
if (isNaN(dx) || isNaN(dy) || (dx <= TAP_DISTANCE && dy <= TAP_DISTANCE) || isSyntheticClick(e)) {
|
||
|
|
// prevent taps from being generated if an event has canceled them
|
||
|
|
if (!this.info.prevent) {
|
||
|
|
Gestures._fire(t, 'tap', {
|
||
|
|
x: e.clientX,
|
||
|
|
y: e.clientY,
|
||
|
|
sourceEvent: e,
|
||
|
|
preventer: preventer
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
/* eslint-enable valid-jsdoc */
|
||
|
|
|
||
|
|
/** @deprecated */
|
||
|
|
Gestures.findOriginalTarget = Gestures._findOriginalTarget;
|
||
|
|
|
||
|
|
/** @deprecated */
|
||
|
|
Gestures.add = Gestures.addListener;
|
||
|
|
|
||
|
|
/** @deprecated */
|
||
|
|
Gestures.remove = Gestures.removeListener;
|
||
|
|
|
||
|
|
Polymer.Gestures = Gestures;
|
||
|
|
|
||
|
|
})();
|
||
|
|
</script>
|