UNPKG

webdash-readme-preview

Version:
1,094 lines (1,037 loc) 33.2 kB
<!-- @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>