-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
Copy pathevent.ts
320 lines (294 loc) · 11.9 KB
/
event.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
/**
* 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
}
type DispatchedEvent = {
dispatchedOffsetX: number
dispatchedOffsetY: 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 | DispatchedEvent,
out: {zrX?: number, zrY?: number},
calculate?: boolean
) {
out = out || {};
if ((e as DispatchedEvent).dispatchedOffsetX) {
out.zrX = (e as DispatchedEvent).dispatchedOffsetX;
out.zrY = (e as DispatchedEvent).dispatchedOffsetY;
}
// 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.
else 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};