main
js 361 lines 9.74 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 defaultPointerId,
16 defaultPointerSize,
17 defaultBrowserChromeSize,
18 } from './constants';
19 import * as domEvents from './domEvents';
20 import {hasPointerEvent, platform} from './domEnvironment';
21 import * as touchStore from './touchStore';
22
23 /**
24 * Converts a PointerEvent payload to a Touch
25 */
26 function createTouch(target, payload) {
27 const {
28 height = defaultPointerSize,
29 pageX,
30 pageY,
31 pointerId,
32 pressure = 1,
33 twist = 0,
34 width = defaultPointerSize,
35 x = 0,
36 y = 0,
37 } = payload;
38
39 return {
40 clientX: x,
41 clientY: y,
42 force: pressure,
43 identifier: pointerId,
44 pageX: pageX || x,
45 pageY: pageY || y,
46 radiusX: width / 2,
47 radiusY: height / 2,
48 rotationAngle: twist,
49 target,
50 screenX: x,
51 screenY: y + defaultBrowserChromeSize,
52 };
53 }
54
55 /**
56 * Converts a PointerEvent to a TouchEvent
57 */
58 function createTouchEventPayload(target, touch, payload) {
59 const {
60 altKey = false,
61 ctrlKey = false,
62 metaKey = false,
63 preventDefault,
64 shiftKey = false,
65 timeStamp,
66 } = payload;
67
68 return {
69 altKey,
70 changedTouches: [touch],
71 ctrlKey,
72 metaKey,
73 preventDefault,
74 shiftKey,
75 targetTouches: touchStore.getTargetTouches(target),
76 timeStamp,
77 touches: touchStore.getTouches(),
78 };
79 }
80
81 function getPointerType(payload) {
82 let pointerType = 'mouse';
83 if (payload != null && payload.pointerType != null) {
84 pointerType = payload.pointerType;
85 }
86 return pointerType;
87 }
88
89 /**
90 * Pointer events sequences.
91 *
92 * Creates representative browser event sequences for high-level gestures based on pointers.
93 * This allows unit tests to be written in terms of simple pointer interactions while testing
94 * that the responses to those interactions account for the complex sequence of events that
95 * browsers produce as a result.
96 *
97 * Every time a new pointer touches the surface a 'touchstart' event should be dispatched.
98 * - 'changedTouches' contains the new touch.
99 * - 'targetTouches' contains all the active pointers for the target.
100 * - 'touches' contains all the active pointers on the surface.
101 *
102 * Every time an existing pointer moves a 'touchmove' event should be dispatched.
103 * - 'changedTouches' contains the updated touch.
104 *
105 * Every time an existing pointer leaves the surface a 'touchend' event should be dispatched.
106 * - 'changedTouches' contains the released touch.
107 * - 'targetTouches' contains any of the remaining active pointers for the target.
108 */
109
110 export function contextmenu(
111 target,
112 defaultPayload,
113 {pointerType = 'mouse', modified} = {},
114 ) {
115 const dispatch = arg => target.dispatchEvent(arg);
116
117 const payload = {
118 pointerId: defaultPointerId,
119 pointerType,
120 ...defaultPayload,
121 };
122
123 const preventDefault = payload.preventDefault;
124
125 if (pointerType === 'touch') {
126 if (hasPointerEvent()) {
127 dispatch(
128 domEvents.pointerdown({
129 ...payload,
130 button: buttonType.primary,
131 buttons: buttonsType.primary,
132 }),
133 );
134 }
135 const touch = createTouch(target, payload);
136 touchStore.addTouch(touch);
137 const touchEventPayload = createTouchEventPayload(target, touch, payload);
138 dispatch(domEvents.touchstart(touchEventPayload));
139 dispatch(
140 domEvents.contextmenu({
141 button: buttonType.primary,
142 buttons: buttonsType.none,
143 preventDefault,
144 }),
145 );
146 touchStore.removeTouch(touch);
147 } else if (pointerType === 'mouse') {
148 if (modified === true) {
149 const button = buttonType.primary;
150 const buttons = buttonsType.primary;
151 const ctrlKey = true;
152 if (hasPointerEvent()) {
153 dispatch(
154 domEvents.pointerdown({button, buttons, ctrlKey, pointerType}),
155 );
156 }
157 dispatch(domEvents.mousedown({button, buttons, ctrlKey}));
158 if (platform.get() === 'mac') {
159 dispatch(
160 domEvents.contextmenu({button, buttons, ctrlKey, preventDefault}),
161 );
162 }
163 } else {
164 const button = buttonType.secondary;
165 const buttons = buttonsType.secondary;
166 if (hasPointerEvent()) {
167 dispatch(domEvents.pointerdown({button, buttons, pointerType}));
168 }
169 dispatch(domEvents.mousedown({button, buttons}));
170 dispatch(domEvents.contextmenu({button, buttons, preventDefault}));
171 }
172 }
173 }
174
175 export function pointercancel(target, defaultPayload) {
176 const dispatchEvent = arg => target.dispatchEvent(arg);
177 const pointerType = getPointerType(defaultPayload);
178
179 const payload = {
180 pointerId: defaultPointerId,
181 pointerType,
182 ...defaultPayload,
183 };
184
185 if (hasPointerEvent()) {
186 dispatchEvent(domEvents.pointercancel(payload));
187 } else {
188 if (pointerType === 'mouse') {
189 dispatchEvent(domEvents.dragstart(payload));
190 } else {
191 const touch = createTouch(target, payload);
192 touchStore.removeTouch(touch);
193 const touchEventPayload = createTouchEventPayload(target, touch, payload);
194 dispatchEvent(domEvents.touchcancel(touchEventPayload));
195 }
196 }
197 }
198
199 export function pointerdown(target, defaultPayload) {
200 const dispatch = arg => target.dispatchEvent(arg);
201 const pointerType = getPointerType(defaultPayload);
202
203 const payload = {
204 button: buttonType.primary,
205 buttons: buttonsType.primary,
206 pointerId: defaultPointerId,
207 pointerType,
208 ...defaultPayload,
209 };
210
211 if (pointerType === 'mouse') {
212 if (hasPointerEvent()) {
213 dispatch(domEvents.pointerover(payload));
214 dispatch(domEvents.pointerenter(payload));
215 }
216 dispatch(domEvents.mouseover(payload));
217 dispatch(domEvents.mouseenter(payload));
218 if (hasPointerEvent()) {
219 dispatch(domEvents.pointerdown(payload));
220 }
221 dispatch(domEvents.mousedown(payload));
222 if (document.activeElement !== target) {
223 dispatch(domEvents.focus());
224 }
225 } else {
226 if (hasPointerEvent()) {
227 dispatch(domEvents.pointerover(payload));
228 dispatch(domEvents.pointerenter(payload));
229 dispatch(domEvents.pointerdown(payload));
230 }
231 const touch = createTouch(target, payload);
232 touchStore.addTouch(touch);
233 const touchEventPayload = createTouchEventPayload(target, touch, payload);
234 dispatch(domEvents.touchstart(touchEventPayload));
235 if (hasPointerEvent()) {
236 dispatch(domEvents.gotpointercapture(payload));
237 }
238 }
239 }
240
241 export function pointerenter(target, defaultPayload) {
242 const dispatch = arg => target.dispatchEvent(arg);
243
244 const payload = {
245 pointerId: defaultPointerId,
246 ...defaultPayload,
247 };
248
249 if (hasPointerEvent()) {
250 dispatch(domEvents.pointerover(payload));
251 dispatch(domEvents.pointerenter(payload));
252 }
253 dispatch(domEvents.mouseover(payload));
254 dispatch(domEvents.mouseenter(payload));
255 }
256
257 export function pointerexit(target, defaultPayload) {
258 const dispatch = arg => target.dispatchEvent(arg);
259
260 const payload = {
261 pointerId: defaultPointerId,
262 ...defaultPayload,
263 };
264
265 if (hasPointerEvent()) {
266 dispatch(domEvents.pointerout(payload));
267 dispatch(domEvents.pointerleave(payload));
268 }
269 dispatch(domEvents.mouseout(payload));
270 dispatch(domEvents.mouseleave(payload));
271 }
272
273 export function pointerhover(target, defaultPayload) {
274 const dispatch = arg => target.dispatchEvent(arg);
275
276 const payload = {
277 pointerId: defaultPointerId,
278 ...defaultPayload,
279 };
280
281 if (hasPointerEvent()) {
282 dispatch(domEvents.pointermove(payload));
283 }
284 dispatch(domEvents.mousemove(payload));
285 }
286
287 export function pointermove(target, defaultPayload) {
288 const dispatch = arg => target.dispatchEvent(arg);
289 const pointerType = getPointerType(defaultPayload);
290
291 const payload = {
292 pointerId: defaultPointerId,
293 pointerType,
294 ...defaultPayload,
295 };
296
297 if (hasPointerEvent()) {
298 dispatch(
299 domEvents.pointermove({
300 pressure: pointerType === 'touch' ? 1 : 0.5,
301 ...payload,
302 }),
303 );
304 } else {
305 if (pointerType === 'mouse') {
306 dispatch(domEvents.mousemove(payload));
307 } else {
308 const touch = createTouch(target, payload);
309 touchStore.updateTouch(touch);
310 const touchEventPayload = createTouchEventPayload(target, touch, payload);
311 dispatch(domEvents.touchmove(touchEventPayload));
312 }
313 }
314 }
315
316 export function pointerup(target, defaultPayload) {
317 const dispatch = arg => target.dispatchEvent(arg);
318 const pointerType = getPointerType(defaultPayload);
319
320 const payload = {
321 pointerId: defaultPointerId,
322 pointerType,
323 ...defaultPayload,
324 };
325
326 if (pointerType === 'mouse') {
327 if (hasPointerEvent()) {
328 dispatch(domEvents.pointerup(payload));
329 }
330 dispatch(domEvents.mouseup(payload));
331 dispatch(domEvents.click(payload));
332 } else {
333 if (hasPointerEvent()) {
334 dispatch(domEvents.pointerup(payload));
335 dispatch(domEvents.lostpointercapture(payload));
336 dispatch(domEvents.pointerout(payload));
337 dispatch(domEvents.pointerleave(payload));
338 }
339 const touch = createTouch(target, payload);
340 touchStore.removeTouch(touch);
341 const touchEventPayload = createTouchEventPayload(target, touch, payload);
342 dispatch(domEvents.touchend(touchEventPayload));
343 dispatch(domEvents.mouseover(payload));
344 dispatch(domEvents.mousemove(payload));
345 dispatch(domEvents.mousedown(payload));
346 if (document.activeElement !== target) {
347 dispatch(domEvents.focus());
348 }
349 dispatch(domEvents.mouseup(payload));
350 dispatch(domEvents.click(payload));
351 }
352 }
353
354 /**
355 * This function should be called after each test to ensure the touchStore is cleared
356 * in cases where the mock pointers weren't released before the test completed
357 * (e.g., a test failed or ran a partial gesture).
358 */
359 export function resetActivePointers() {
360 touchStore.clear();
361 }