| /** | 
|  * Utilities for mouse or touch events. | 
|  */ | 
|   | 
| import Eventful from './Eventful'; | 
| import env from './env'; | 
| import { ZRRawEvent } from './types'; | 
| import {isCanvasEl, transformCoordWithViewport} from './dom'; | 
|   | 
| const MOUSE_EVENT_REG = /^(?:mouse|pointer|contextmenu|drag|drop)|click/; | 
| const _calcOut: number[] = []; | 
| const firefoxNotSupportOffsetXY = env.browser.firefox | 
|     // use offsetX/offsetY for Firefox >= 39 | 
|     // PENDING: consider Firefox for Android and Firefox OS? >= 43 | 
|     && +(env.browser.version as string).split('.')[0] < 39; | 
|   | 
| type FirefoxMouseEvent = { | 
|     layerX: number | 
|     layerY: number | 
| } | 
|   | 
|   | 
| /** | 
|  * Get the `zrX` and `zrY`, which are relative to the top-left of | 
|  * the input `el`. | 
|  * CSS transform (2D & 3D) is supported. | 
|  * | 
|  * The strategy to fetch the coords: | 
|  * + If `calculate` is not set as `true`, users of this method should | 
|  * ensure that `el` is the same or the same size & location as `e.target`. | 
|  * Otherwise the result coords are probably not expected. Because we | 
|  * firstly try to get coords from e.offsetX/e.offsetY. | 
|  * + If `calculate` is set as `true`, the input `el` can be any element | 
|  * and we force to calculate the coords based on `el`. | 
|  * + The input `el` should be positionable (not position:static). | 
|  * | 
|  * The force `calculate` can be used in case like: | 
|  * When mousemove event triggered on ec tooltip, `e.target` is not `el`(zr painter.dom). | 
|  * | 
|  * @param  el DOM element. | 
|  * @param  e Mouse event or touch event. | 
|  * @param  out Get `out.zrX` and `out.zrY` as the result. | 
|  * @param  calculate Whether to force calculate | 
|  *        the coordinates but not use ones provided by browser. | 
|  */ | 
| export function clientToLocal( | 
|     el: HTMLElement, | 
|     e: ZRRawEvent | FirefoxMouseEvent | Touch, | 
|     out: {zrX?: number, zrY?: number}, | 
|     calculate?: boolean | 
| ) { | 
|     out = out || {}; | 
|   | 
|     // According to the W3C Working Draft, offsetX and offsetY should be relative | 
|     // to the padding edge of the target element. The only browser using this convention | 
|     // is IE. Webkit uses the border edge, Opera uses the content edge, and FireFox does | 
|     // not support the properties. | 
|     // (see http://www.jacklmoore.com/notes/mouse-position/) | 
|     // In zr painter.dom, padding edge equals to border edge. | 
|     if (calculate) { | 
|         calculateZrXY(el, e as ZRRawEvent, out); | 
|     } | 
|     // Caution: In FireFox, layerX/layerY Mouse position relative to the closest positioned | 
|     // ancestor element, so we should make sure el is positioned (e.g., not position:static). | 
|     // BTW1, Webkit don't return the same results as FF in non-simple cases (like add | 
|     // zoom-factor, overflow / opacity layers, transforms ...) | 
|     // BTW2, (ev.offsetY || ev.pageY - $(ev.target).offset().top) is not correct in preserve-3d. | 
|     // <https://bugs.jquery.com/ticket/8523#comment:14> | 
|     // BTW3, In ff, offsetX/offsetY is always 0. | 
|     else if (firefoxNotSupportOffsetXY | 
|         && (e as FirefoxMouseEvent).layerX != null | 
|         && (e as FirefoxMouseEvent).layerX !== (e as MouseEvent).offsetX | 
|     ) { | 
|         out.zrX = (e as FirefoxMouseEvent).layerX; | 
|         out.zrY = (e as FirefoxMouseEvent).layerY; | 
|     } | 
|     // For IE6+, chrome, safari, opera, firefox >= 39 | 
|     else if ((e as MouseEvent).offsetX != null) { | 
|         out.zrX = (e as MouseEvent).offsetX; | 
|         out.zrY = (e as MouseEvent).offsetY; | 
|     } | 
|     // For some other device, e.g., IOS safari. | 
|     else { | 
|         calculateZrXY(el, e as ZRRawEvent, out); | 
|     } | 
|   | 
|     return out; | 
| } | 
|   | 
| function calculateZrXY( | 
|     el: HTMLElement, | 
|     e: ZRRawEvent, | 
|     out: {zrX?: number, zrY?: number} | 
| ) { | 
|     // BlackBerry 5, iOS 3 (original iPhone) don't have getBoundingRect. | 
|     if (env.domSupported && el.getBoundingClientRect) { | 
|         const ex = (e as MouseEvent).clientX; | 
|         const ey = (e as MouseEvent).clientY; | 
|   | 
|         if (isCanvasEl(el)) { | 
|             // Original approach, which do not support CSS transform. | 
|             // marker can not be locationed in a canvas container | 
|             // (getBoundingClientRect is always 0). We do not support | 
|             // that input a pre-created canvas to zr while using css | 
|             // transform in iOS. | 
|             const box = el.getBoundingClientRect(); | 
|             out.zrX = ex - box.left; | 
|             out.zrY = ey - box.top; | 
|             return; | 
|         } | 
|         else { | 
|             if (transformCoordWithViewport(_calcOut, el, ex, ey)) { | 
|                 out.zrX = _calcOut[0]; | 
|                 out.zrY = _calcOut[1]; | 
|                 return; | 
|             } | 
|         } | 
|     } | 
|     out.zrX = out.zrY = 0; | 
| } | 
|   | 
| /** | 
|  * Find native event compat for legency IE. | 
|  * Should be called at the begining of a native event listener. | 
|  * | 
|  * @param e Mouse event or touch event or pointer event. | 
|  *        For lagency IE, we use `window.event` is used. | 
|  * @return The native event. | 
|  */ | 
| export function getNativeEvent(e: ZRRawEvent): ZRRawEvent { | 
|     return e | 
|         || (window.event as any);   // For IE | 
| } | 
|   | 
| /** | 
|  * Normalize the coordinates of the input event. | 
|  * | 
|  * Get the `e.zrX` and `e.zrY`, which are relative to the top-left of | 
|  * the input `el`. | 
|  * Get `e.zrDelta` if using mouse wheel. | 
|  * Get `e.which`, see the comment inside this function. | 
|  * | 
|  * Do not calculate repeatly if `zrX` and `zrY` already exist. | 
|  * | 
|  * Notice: see comments in `clientToLocal`. check the relationship | 
|  * between the result coords and the parameters `el` and `calculate`. | 
|  * | 
|  * @param el DOM element. | 
|  * @param e See `getNativeEvent`. | 
|  * @param calculate Whether to force calculate | 
|  *        the coordinates but not use ones provided by browser. | 
|  * @return The normalized native UIEvent. | 
|  */ | 
| export function normalizeEvent( | 
|     el: HTMLElement, | 
|     e: ZRRawEvent, | 
|     calculate?: boolean | 
| ) { | 
|   | 
|     e = getNativeEvent(e); | 
|   | 
|     if (e.zrX != null) { | 
|         return e; | 
|     } | 
|   | 
|     const eventType = e.type; | 
|     const isTouch = eventType && eventType.indexOf('touch') >= 0; | 
|   | 
|     if (!isTouch) { | 
|         clientToLocal(el, e, e, calculate); | 
|         const wheelDelta = getWheelDeltaMayPolyfill(e); | 
|         // FIXME: IE8- has "wheelDeta" in event "mousewheel" but hat different value (120 times) | 
|         // with Chrome and Safari. It's not correct for zrender event but we left it as it was. | 
|         e.zrDelta = wheelDelta ? wheelDelta / 120 : -(e.detail || 0) / 3; | 
|     } | 
|     else { | 
|         const touch = eventType !== 'touchend' | 
|             ? (<TouchEvent>e).targetTouches[0] | 
|             : (<TouchEvent>e).changedTouches[0]; | 
|         touch && clientToLocal(el, touch, e, calculate); | 
|     } | 
|   | 
|     // Add which for click: 1 === left; 2 === middle; 3 === right; otherwise: 0; | 
|     // See jQuery: https://github.com/jquery/jquery/blob/master/src/event.js | 
|     // If e.which has been defined, it may be readonly, | 
|     // see: https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/which | 
|     const button = (<MouseEvent>e).button; | 
|     if (e.which == null && button !== undefined && MOUSE_EVENT_REG.test(e.type)) { | 
|         (e as any).which = (button & 1 ? 1 : (button & 2 ? 3 : (button & 4 ? 2 : 0))); | 
|     } | 
|     // [Caution]: `e.which` from browser is not always reliable. For example, | 
|     // when press left button and `mousemove (pointermove)` in Edge, the `e.which` | 
|     // is 65536 and the `e.button` is -1. But the `mouseup (pointerup)` and | 
|     // `mousedown (pointerdown)` is the same as Chrome does. | 
|   | 
|     return e; | 
| } | 
|   | 
| // TODO: also provide prop "deltaX" "deltaY" in zrender "mousewheel" event. | 
| function getWheelDeltaMayPolyfill(e: ZRRawEvent): number { | 
|     // Although event "wheel" do not has the prop "wheelDelta" in spec, | 
|     // agent like Chrome and Safari still provide "wheelDelta" like | 
|     // event "mousewheel" did (perhaps for backward compat). | 
|     // Since zrender has been using "wheelDeta" in zrender event "mousewheel". | 
|     // we currently do not break it. | 
|     // But event "wheel" in firefox do not has "wheelDelta", so we calculate | 
|     // "wheelDeta" from "deltaX", "deltaY" (which is the props in spec). | 
|     const rawWheelDelta = (e as any).wheelDelta; | 
|     // Theroetically `e.wheelDelta` won't be 0 unless some day it has been deprecated | 
|     // by agent like Chrome or Safari. So we also calculate it if rawWheelDelta is 0. | 
|     if (rawWheelDelta) { | 
|         return rawWheelDelta; | 
|     } | 
|   | 
|     const deltaX = (e as any).deltaX; | 
|     const deltaY = (e as any).deltaY; | 
|     if (deltaX == null || deltaY == null) { | 
|         return rawWheelDelta; | 
|     } | 
|   | 
|     // Test in Chrome and Safari (MacOS): | 
|     // The sign is corrent. | 
|     // The abs value is 99% corrent (inconsist case only like 62~63, 125~126 ...) | 
|     const delta = deltaY !== 0 ? Math.abs(deltaY) : Math.abs(deltaX); | 
|     const sign = deltaY > 0 ? -1 | 
|         : deltaY < 0 ? 1 | 
|         : deltaX > 0 ? -1 | 
|         : 1; | 
|     return 3 * delta * sign; | 
| } | 
|   | 
|   | 
| type AddEventListenerParams = Parameters<typeof HTMLElement.prototype.addEventListener> | 
| type RemoveEventListenerParams = Parameters<typeof HTMLElement.prototype.removeEventListener> | 
| /** | 
|  * @param  el | 
|  * @param  name | 
|  * @param  handler | 
|  * @param  opt If boolean, means `opt.capture` | 
|  * @param  opt.capture | 
|  * @param  opt.passive | 
|  */ | 
| export function addEventListener( | 
|     el: HTMLElement | HTMLDocument, | 
|     name: AddEventListenerParams[0], | 
|     handler: AddEventListenerParams[1], | 
|     opt?: AddEventListenerParams[2] | 
| ) { | 
|     // Reproduct the console warning: | 
|     // [Violation] Added non-passive event listener to a scroll-blocking <some> event. | 
|     // Consider marking event handler as 'passive' to make the page more responsive. | 
|     // Just set console log level: verbose in chrome dev tool. | 
|     // then the warning log will be printed when addEventListener called. | 
|     // See https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md | 
|     // We have not yet found a neat way to using passive. Because in zrender the dom event | 
|     // listener delegate all of the upper events of element. Some of those events need | 
|     // to prevent default. For example, the feature `preventDefaultMouseMove` of echarts. | 
|     // Before passive can be adopted, these issues should be considered: | 
|     // (1) Whether and how a zrender user specifies an event listener passive. And by default, | 
|     // passive or not. | 
|     // (2) How to tread that some zrender event listener is passive, and some is not. If | 
|     // we use other way but not preventDefault of mousewheel and touchmove, browser | 
|     // compatibility should be handled. | 
|   | 
|     // const opts = (env.passiveSupported && name === 'mousewheel') | 
|     //     ? {passive: true} | 
|     //     // By default, the third param of el.addEventListener is `capture: false`. | 
|     //     : void 0; | 
|     // el.addEventListener(name, handler /* , opts */); | 
|     el.addEventListener(name, handler, opt); | 
| } | 
|   | 
| /** | 
|  * Parameter are the same as `addEventListener`. | 
|  * | 
|  * Notice that if a listener is registered twice, one with capture and one without, | 
|  * remove each one separately. Removal of a capturing listener does not affect a | 
|  * non-capturing version of the same listener, and vice versa. | 
|  */ | 
| export function removeEventListener( | 
|     el: HTMLElement | HTMLDocument, | 
|     name: RemoveEventListenerParams[0], | 
|     handler: RemoveEventListenerParams[1], | 
|     opt: RemoveEventListenerParams[2] | 
| ) { | 
|     el.removeEventListener(name, handler, opt); | 
| } | 
|   | 
| /** | 
|  * preventDefault and stopPropagation. | 
|  * Notice: do not use this method in zrender. It can only be | 
|  * used by upper applications if necessary. | 
|  * | 
|  * @param {Event} e A mouse or touch event. | 
|  */ | 
| export const stop = function (e: MouseEvent | TouchEvent | PointerEvent) { | 
|     e.preventDefault(); | 
|     e.stopPropagation(); | 
|     e.cancelBubble = true; | 
| }; | 
|   | 
| /** | 
|  * This method only works for mouseup and mousedown. The functionality is restricted | 
|  * for fault tolerance, See the `e.which` compatibility above. | 
|  * | 
|  * params can be MouseEvent or ElementEvent | 
|  */ | 
| export function isMiddleOrRightButtonOnMouseUpDown(e: { which: number }) { | 
|     return e.which === 2 || e.which === 3; | 
| } | 
|   | 
| // For backward compatibility | 
| export {Eventful as Dispatcher}; |