summaryrefslogtreecommitdiffstats
path: root/chromium/third_party/catapult/third_party/polymer2/bower_components/polymer/lib/utils/gestures.html
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/catapult/third_party/polymer2/bower_components/polymer/lib/utils/gestures.html')
-rw-r--r--chromium/third_party/catapult/third_party/polymer2/bower_components/polymer/lib/utils/gestures.html1093
1 files changed, 1093 insertions, 0 deletions
diff --git a/chromium/third_party/catapult/third_party/polymer2/bower_components/polymer/lib/utils/gestures.html b/chromium/third_party/catapult/third_party/polymer2/bower_components/polymer/lib/utils/gestures.html
new file mode 100644
index 00000000000..2a05daa462e
--- /dev/null
+++ b/chromium/third_party/catapult/third_party/polymer2/bower_components/polymer/lib/utils/gestures.html
@@ -0,0 +1,1093 @@
+<!--
+@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>