main
js 442 lines 9.04 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @emails react-core
8 */
9
10 'use strict';
11
12 import {
13 buttonType,
14 buttonsType,
15 defaultPointerSize,
16 defaultBrowserChromeSize,
17 } from './constants';
18
19 /**
20 * Native event object mocks for higher-level events.
21 *
22 * 1. Each event type defines the exact object that it accepts. This ensures
23 * that no arbitrary properties can be assigned to events, and the properties
24 * that don't exist on specific event types (e.g., 'pointerType') are not added
25 * to the respective native event.
26 *
27 * 2. Properties that cannot be relied on due to inconsistent browser support (e.g., 'x' and 'y') are not
28 * added to the native event. Others that shouldn't be arbitrarily customized (e.g., 'screenX')
29 * are automatically inferred from associated values.
30 *
31 * 3. PointerEvent and TouchEvent fields are normalized (e.g., 'rotationAngle' -> 'twist')
32 */
33
34 function emptyFunction() {}
35
36 function createEvent(type, data = {}) {
37 const event = document.createEvent('CustomEvent');
38 event.initCustomEvent(type, true, true);
39 if (data != null) {
40 Object.keys(data).forEach(key => {
41 const value = data[key];
42 if (key === 'timeStamp' && !value) {
43 return;
44 }
45 Object.defineProperty(event, key, {value});
46 });
47 }
48 return event;
49 }
50
51 function createGetModifierState(keyArg, data) {
52 if (keyArg === 'Alt') {
53 return data.altKey || false;
54 }
55 if (keyArg === 'Control') {
56 return data.ctrlKey || false;
57 }
58 if (keyArg === 'Meta') {
59 return data.metaKey || false;
60 }
61 if (keyArg === 'Shift') {
62 return data.shiftKey || false;
63 }
64 }
65
66 function createPointerEvent(
67 type,
68 {
69 altKey = false,
70 button = buttonType.none,
71 buttons = buttonsType.none,
72 ctrlKey = false,
73 detail = 1,
74 height,
75 metaKey = false,
76 movementX = 0,
77 movementY = 0,
78 offsetX = 0,
79 offsetY = 0,
80 pageX,
81 pageY,
82 pointerId,
83 pressure = 0,
84 preventDefault = emptyFunction,
85 pointerType = 'mouse',
86 screenX,
87 screenY,
88 shiftKey = false,
89 tangentialPressure = 0,
90 tiltX = 0,
91 tiltY = 0,
92 timeStamp,
93 twist = 0,
94 width,
95 x = 0,
96 y = 0,
97 } = {},
98 ) {
99 const modifierState = {altKey, ctrlKey, metaKey, shiftKey};
100 const isMouse = pointerType === 'mouse';
101
102 return createEvent(type, {
103 altKey,
104 button,
105 buttons,
106 clientX: x,
107 clientY: y,
108 ctrlKey,
109 detail,
110 getModifierState(keyArg) {
111 return createGetModifierState(keyArg, modifierState);
112 },
113 height: isMouse ? 1 : height != null ? height : defaultPointerSize,
114 metaKey,
115 movementX,
116 movementY,
117 offsetX,
118 offsetY,
119 pageX: pageX || x,
120 pageY: pageY || y,
121 pointerId,
122 pointerType,
123 pressure,
124 preventDefault,
125 releasePointerCapture: emptyFunction,
126 screenX: screenX === 0 ? screenX : x,
127 screenY: screenY === 0 ? screenY : y + defaultBrowserChromeSize,
128 setPointerCapture: emptyFunction,
129 shiftKey,
130 tangentialPressure,
131 tiltX,
132 tiltY,
133 timeStamp,
134 twist,
135 width: isMouse ? 1 : width != null ? width : defaultPointerSize,
136 });
137 }
138
139 function createKeyboardEvent(
140 type,
141 {
142 altKey = false,
143 ctrlKey = false,
144 isComposing = false,
145 key = '',
146 metaKey = false,
147 preventDefault = emptyFunction,
148 shiftKey = false,
149 } = {},
150 ) {
151 const modifierState = {altKey, ctrlKey, metaKey, shiftKey};
152
153 return createEvent(type, {
154 altKey,
155 ctrlKey,
156 getModifierState(keyArg) {
157 return createGetModifierState(keyArg, modifierState);
158 },
159 isComposing,
160 key,
161 metaKey,
162 preventDefault,
163 shiftKey,
164 });
165 }
166
167 function createMouseEvent(
168 type,
169 {
170 altKey = false,
171 button = buttonType.none,
172 buttons = buttonsType.none,
173 ctrlKey = false,
174 detail = 1,
175 metaKey = false,
176 movementX = 0,
177 movementY = 0,
178 offsetX = 0,
179 offsetY = 0,
180 pageX,
181 pageY,
182 preventDefault = emptyFunction,
183 screenX,
184 screenY,
185 shiftKey = false,
186 timeStamp,
187 x = 0,
188 y = 0,
189 } = {},
190 ) {
191 const modifierState = {altKey, ctrlKey, metaKey, shiftKey};
192
193 return createEvent(type, {
194 altKey,
195 button,
196 buttons,
197 clientX: x,
198 clientY: y,
199 ctrlKey,
200 detail,
201 getModifierState(keyArg) {
202 return createGetModifierState(keyArg, modifierState);
203 },
204 metaKey,
205 movementX,
206 movementY,
207 offsetX,
208 offsetY,
209 pageX: pageX || x,
210 pageY: pageY || y,
211 preventDefault,
212 screenX: screenX === 0 ? screenX : x,
213 screenY: screenY === 0 ? screenY : y + defaultBrowserChromeSize,
214 shiftKey,
215 timeStamp,
216 });
217 }
218
219 function createTouchEvent(type, payload) {
220 return createEvent(type, {
221 ...payload,
222 detail: 0,
223 sourceCapabilities: {
224 firesTouchEvents: true,
225 },
226 });
227 }
228
229 /**
230 * Mock event objects
231 */
232
233 export function blur({relatedTarget} = {}) {
234 return new FocusEvent('blur', {relatedTarget});
235 }
236
237 export function focusOut({relatedTarget} = {}) {
238 return new FocusEvent('focusout', {relatedTarget, bubbles: true});
239 }
240
241 export function click(payload) {
242 return createMouseEvent('click', {
243 button: buttonType.primary,
244 ...payload,
245 });
246 }
247
248 export function contextmenu(payload) {
249 return createMouseEvent('contextmenu', {
250 ...payload,
251 detail: 0,
252 });
253 }
254
255 export function dragstart(payload) {
256 return createMouseEvent('dragstart', {
257 ...payload,
258 detail: 0,
259 });
260 }
261
262 export function focus({relatedTarget} = {}) {
263 return new FocusEvent('focus', {relatedTarget});
264 }
265
266 export function focusIn({relatedTarget} = {}) {
267 return new FocusEvent('focusin', {relatedTarget, bubbles: true});
268 }
269
270 export function scroll() {
271 return createEvent('scroll');
272 }
273
274 export function virtualclick(payload) {
275 return createMouseEvent('click', {
276 button: 0,
277 ...payload,
278 buttons: 0,
279 detail: 0,
280 height: 1,
281 pageX: 0,
282 pageY: 0,
283 pressure: 0,
284 screenX: 0,
285 screenY: 0,
286 width: 1,
287 x: 0,
288 y: 0,
289 });
290 }
291
292 /**
293 * Key events
294 */
295
296 export function keydown(payload) {
297 return createKeyboardEvent('keydown', payload);
298 }
299
300 export function keyup(payload) {
301 return createKeyboardEvent('keyup', payload);
302 }
303
304 /**
305 * Pointer events
306 */
307
308 export function gotpointercapture(payload) {
309 return createPointerEvent('gotpointercapture', payload);
310 }
311
312 export function lostpointercapture(payload) {
313 return createPointerEvent('lostpointercapture', payload);
314 }
315
316 export function pointercancel(payload) {
317 return createPointerEvent('pointercancel', {
318 ...payload,
319 buttons: 0,
320 detail: 0,
321 height: 1,
322 pageX: 0,
323 pageY: 0,
324 pressure: 0,
325 screenX: 0,
326 screenY: 0,
327 width: 1,
328 x: 0,
329 y: 0,
330 });
331 }
332
333 export function pointerdown(payload) {
334 const isTouch = payload != null && payload.pointerType === 'touch';
335 return createPointerEvent('pointerdown', {
336 button: buttonType.primary,
337 buttons: buttonsType.primary,
338 pressure: isTouch ? 1 : 0.5,
339 ...payload,
340 });
341 }
342
343 export function pointerenter(payload) {
344 return createPointerEvent('pointerenter', payload);
345 }
346
347 export function pointerleave(payload) {
348 return createPointerEvent('pointerleave', payload);
349 }
350
351 export function pointermove(payload) {
352 return createPointerEvent('pointermove', {
353 ...payload,
354 button: buttonType.none,
355 });
356 }
357
358 export function pointerout(payload) {
359 return createPointerEvent('pointerout', payload);
360 }
361
362 export function pointerover(payload) {
363 return createPointerEvent('pointerover', payload);
364 }
365
366 export function pointerup(payload) {
367 return createPointerEvent('pointerup', {
368 button: buttonType.primary,
369 ...payload,
370 buttons: buttonsType.none,
371 pressure: 0,
372 });
373 }
374
375 /**
376 * Mouse events
377 */
378
379 export function mousedown(payload) {
380 // The value of 'button' and 'buttons' for 'mousedown' must not be none.
381 const button =
382 payload == null || payload.button === buttonType.none
383 ? buttonType.primary
384 : payload.button;
385 const buttons =
386 payload == null || payload.buttons === buttonsType.none
387 ? buttonsType.primary
388 : payload.buttons;
389 return createMouseEvent('mousedown', {
390 ...payload,
391 button,
392 buttons,
393 });
394 }
395
396 export function mouseenter(payload) {
397 return createMouseEvent('mouseenter', payload);
398 }
399
400 export function mouseleave(payload) {
401 return createMouseEvent('mouseleave', payload);
402 }
403
404 export function mousemove(payload) {
405 return createMouseEvent('mousemove', payload);
406 }
407
408 export function mouseout(payload) {
409 return createMouseEvent('mouseout', payload);
410 }
411
412 export function mouseover(payload) {
413 return createMouseEvent('mouseover', payload);
414 }
415
416 export function mouseup(payload) {
417 return createMouseEvent('mouseup', {
418 button: buttonType.primary,
419 ...payload,
420 buttons: buttonsType.none,
421 });
422 }
423
424 /**
425 * Touch events
426 */
427
428 export function touchcancel(payload) {
429 return createTouchEvent('touchcancel', payload);
430 }
431
432 export function touchend(payload) {
433 return createTouchEvent('touchend', payload);
434 }
435
436 export function touchmove(payload) {
437 return createTouchEvent('touchmove', payload);
438 }
439
440 export function touchstart(payload) {
441 return createTouchEvent('touchstart', payload);
442 }