forked from RaspberryPiFoundation/blockly
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbrowser_events.ts
More file actions
253 lines (233 loc) · 7.39 KB
/
browser_events.ts
File metadata and controls
253 lines (233 loc) · 7.39 KB
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
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* Browser event handling.
*
* @namespace Blockly.browserEvents
*/
import * as goog from '../closure/goog/goog.js';
goog.declareModuleId('Blockly.browserEvents');
import * as Touch from './touch.js';
import * as deprecation from './utils/deprecation.js';
import * as userAgent from './utils/useragent.js';
/**
* Blockly opaque event data used to unbind events when using
* `bind` and `conditionalBind`.
*
* @alias Blockly.browserEvents.Data
*/
export type Data = [EventTarget, string, (e: Event) => void][];
/**
* The multiplier for scroll wheel deltas using the line delta mode.
* See https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/deltaMode
* for more information on deltaMode.
*/
const LINE_MODE_MULTIPLIER = 40;
/**
* The multiplier for scroll wheel deltas using the page delta mode.
* See https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/deltaMode
* for more information on deltaMode.
*/
const PAGE_MODE_MULTIPLIER = 125;
/**
* Bind an event handler that can be ignored if it is not part of the active
* touch stream.
* Use this for events that either start or continue a multi-part gesture (e.g.
* mousedown or mousemove, which may be part of a drag or click).
*
* @param node Node upon which to listen.
* @param name Event name to listen to (e.g. 'mousedown').
* @param thisObject The value of 'this' in the function.
* @param func Function to call when event is triggered.
* @param opt_noCaptureIdentifier True if triggering on this event should not
* block execution of other event handlers on this touch or other
* simultaneous touches. False by default.
* @param opt_noPreventDefault No-op, deprecated and will be removed in v10.
* @returns Opaque data that can be passed to unbindEvent_.
* @alias Blockly.browserEvents.conditionalBind
*/
export function conditionalBind(
node: EventTarget, name: string, thisObject: Object|null, func: Function,
opt_noCaptureIdentifier?: boolean, opt_noPreventDefault?: boolean): Data {
if (opt_noPreventDefault !== undefined) {
deprecation.warn(
'The opt_noPreventDefault argument of conditionalBind', 'version 9',
'version 10');
}
/**
*
* @param e
*/
function wrapFunc(e: Event) {
const captureIdentifier = !opt_noCaptureIdentifier;
if (!(captureIdentifier && !Touch.shouldHandleEvent(e))) {
if (thisObject) {
func.call(thisObject, e);
} else {
func(e);
}
}
}
const bindData: Data = [];
if (name in Touch.TOUCH_MAP) {
for (let i = 0; i < Touch.TOUCH_MAP[name].length; i++) {
const type = Touch.TOUCH_MAP[name][i];
node.addEventListener(type, wrapFunc, false);
bindData.push([node, type, wrapFunc]);
}
} else {
node.addEventListener(name, wrapFunc, false);
bindData.push([node, name, wrapFunc]);
}
return bindData;
}
/**
* Bind an event handler that should be called regardless of whether it is part
* of the active touch stream.
* Use this for events that are not part of a multi-part gesture (e.g.
* mouseover for tooltips).
*
* @param node Node upon which to listen.
* @param name Event name to listen to (e.g. 'mousedown').
* @param thisObject The value of 'this' in the function.
* @param func Function to call when event is triggered.
* @returns Opaque data that can be passed to unbindEvent_.
* @alias Blockly.browserEvents.bind
*/
export function bind(
node: EventTarget, name: string, thisObject: Object|null,
func: Function): Data {
/**
*
* @param e
*/
function wrapFunc(e: Event) {
if (thisObject) {
func.call(thisObject, e);
} else {
func(e);
}
}
const bindData: Data = [];
if (name in Touch.TOUCH_MAP) {
for (let i = 0; i < Touch.TOUCH_MAP[name].length; i++) {
const type = Touch.TOUCH_MAP[name][i];
node.addEventListener(type, wrapFunc, false);
bindData.push([node, type, wrapFunc]);
}
} else {
node.addEventListener(name, wrapFunc, false);
bindData.push([node, name, wrapFunc]);
}
return bindData;
}
/**
* Unbind one or more events event from a function call.
*
* @param bindData Opaque data from bindEvent_.
* This list is emptied during the course of calling this function.
* @returns The function call.
* @alias Blockly.browserEvents.unbind
*/
export function unbind(bindData: Data): (e: Event) => void {
// Accessing an element of the last property of the array is unsafe if the
// bindData is an empty array. But that should never happen because developers
// should only pass Data from bind or conditionalBind.
const callback = bindData[bindData.length - 1][2];
while (bindData.length) {
const bindDatum = bindData.pop();
const node = bindDatum![0];
const name = bindDatum![1];
const func = bindDatum![2];
node.removeEventListener(name, func, false);
}
return callback;
}
/**
* Returns true if this event is targeting a text input widget?
*
* @param e An event.
* @returns True if text input.
* @alias Blockly.browserEvents.isTargetInput
*/
export function isTargetInput(e: Event): boolean {
if (e.target instanceof HTMLElement) {
if (e.target.isContentEditable ||
e.target.getAttribute('data-is-text-input') === 'true') {
return true;
}
if (e.target instanceof HTMLInputElement) {
const target = e.target;
return target.type === 'text' || target.type === 'number' ||
target.type === 'email' || target.type === 'password' ||
target.type === 'search' || target.type === 'tel' ||
target.type === 'url';
}
if (e.target instanceof HTMLTextAreaElement) {
return true;
}
}
return false;
}
/**
* Returns true this event is a right-click.
*
* @param e Mouse event.
* @returns True if right-click.
* @alias Blockly.browserEvents.isRightButton
*/
export function isRightButton(e: MouseEvent): boolean {
if (e.ctrlKey && userAgent.MAC) {
// Control-clicking on Mac OS X is treated as a right-click.
// WebKit on Mac OS X fails to change button to 2 (but Gecko does).
return true;
}
return e.button === 2;
}
/**
* Returns the converted coordinates of the given mouse event.
* The origin (0,0) is the top-left corner of the Blockly SVG.
*
* @param e Mouse event.
* @param svg SVG element.
* @param matrix Inverted screen CTM to use.
* @returns Object with .x and .y properties.
* @alias Blockly.browserEvents.mouseToSvg
*/
export function mouseToSvg(
e: MouseEvent, svg: SVGSVGElement, matrix: SVGMatrix|null): SVGPoint {
const svgPoint = svg.createSVGPoint();
svgPoint.x = e.clientX;
svgPoint.y = e.clientY;
if (!matrix) {
matrix = svg.getScreenCTM()!.inverse();
}
return svgPoint.matrixTransform(matrix);
}
/**
* Returns the scroll delta of a mouse event in pixel units.
*
* @param e Mouse event.
* @returns Scroll delta object with .x and .y properties.
* @alias Blockly.browserEvents.getScrollDeltaPixels
*/
export function getScrollDeltaPixels(e: WheelEvent): {x: number, y: number} {
switch (e.deltaMode) {
case 0x00: // Pixel mode.
default:
return {x: e.deltaX, y: e.deltaY};
case 0x01: // Line mode.
return {
x: e.deltaX * LINE_MODE_MULTIPLIER,
y: e.deltaY * LINE_MODE_MULTIPLIER,
};
case 0x02: // Page mode.
return {
x: e.deltaX * PAGE_MODE_MULTIPLIER,
y: e.deltaY * PAGE_MODE_MULTIPLIER,
};
}
}