[react-events] Focus/FocusWithin responders with fallbacks (#16343)
Separate the PointerEvent and fallback implementations. Fix the unit tests to cover both PointerEvent and non-PointerEvent environments. Fix the focus-visible related callbacks to get called when keys other than "Tab" are used.
Nicolas Gallagher committed
Aug 12, 2019 at 10:10 UTC
107521a9526a5066d7ad7bc8e2e73224b2f5de24
6 files changed
+405
-392
packages/react-events/src/dom/Focus.js
+57
-50
@@ -33,6 +33,7 @@ type FocusState = {
33
isFocused: boolean,
34
isFocusVisible: boolean,
35
pointerType: PointerType,
36
+ isEmulatingMouseEvents: boolean,
37
};
38
39
type FocusProps = {
@@ -66,25 +67,12 @@ const isMac =
67
68
const targetEventTypes = ['focus', 'blur'];
69
69
-const rootEventTypes = [
70
- 'keydown',
71
- 'keyup',
72
- 'pointermove',
73
- 'pointerdown',
74
- 'pointerup',
75
-];
76
-
77
-// If PointerEvents is not supported (e.g., Safari), also listen to touch and mouse events.
78
-if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
79
- rootEventTypes.push(
80
- 'mousemove',
81
- 'mousedown',
82
- 'mouseup',
83
- 'touchmove',
84
- 'touchstart',
85
- 'touchend',
86
- );
87
-}
70
+const hasPointerEvents =
71
+ typeof window !== 'undefined' && window.PointerEvent != null;
72
+
73
+const rootEventTypes = hasPointerEvents
74
+ ? ['keydown', 'keyup', 'pointermove', 'pointerdown', 'pointerup']
75
+ : ['keydown', 'keyup', 'mousedown', 'touchmove', 'touchstart', 'touchend'];
76
77
function isFunction(obj): boolean {
78
return typeof obj === 'function';
@@ -110,13 +98,7 @@ function handleRootPointerEvent(
98
state: FocusState,
99
callback: boolean => void,
100
): void {
113
- const {type, target} = event;
114
- // Ignore a Safari quirks where 'mousemove' is dispatched on the 'html'
115
- // element when the window blurs.
116
- if (type === 'mousemove' && target.nodeName === 'HTML') {
117
- return;
118
- }
119
-
101
+ const {type} = event;
102
isGlobalFocusVisible = false;
103
104
// Focus should stop being visible if a pointer is used on the element
@@ -124,7 +106,7 @@ function handleRootPointerEvent(
106
const focusTarget = state.focusTarget;
107
if (
108
focusTarget !== null &&
127
- context.isTargetWithinNode(event.target, focusTarget) &&
109
+ context.isTargetWithinResponderScope(focusTarget) &&
110
(type === 'mousedown' || type === 'touchstart' || type === 'pointerdown')
111
) {
112
callback(false);
@@ -140,13 +122,6 @@ function handleRootEvent(
122
const {type} = event;
123
124
switch (type) {
143
- case 'mousemove':
144
- case 'mousedown':
145
- case 'mouseup': {
146
- state.pointerType = 'mouse';
147
- handleRootPointerEvent(event, context, state, callback);
148
- break;
149
- }
125
case 'pointermove':
126
case 'pointerdown':
127
case 'pointerup': {
@@ -156,27 +131,45 @@ function handleRootEvent(
131
handleRootPointerEvent(event, context, state, callback);
132
break;
133
}
134
+
135
+ case 'keydown':
136
+ case 'keyup': {
137
+ const nativeEvent = event.nativeEvent;
138
+ const focusTarget = state.focusTarget;
139
+ const {key, metaKey, altKey, ctrlKey} = (nativeEvent: any);
140
+ const validKey =
141
+ key === 'Enter' ||
142
+ key === ' ' ||
143
+ (key === 'Tab' && !(metaKey || (!isMac && altKey) || ctrlKey));
144
+
145
+ if (validKey) {
146
+ state.pointerType = 'keyboard';
147
+ isGlobalFocusVisible = true;
148
+ if (
149
+ focusTarget !== null &&
150
+ context.isTargetWithinResponderScope(focusTarget)
151
+ ) {
152
+ callback(true);
153
+ }
154
+ }
155
+ break;
156
+ }
157
+
158
+ // fallbacks for no PointerEvent support
159
case 'touchmove':
160
case 'touchstart':
161
case 'touchend': {
162
state.pointerType = 'touch';
163
+ state.isEmulatingMouseEvents = true;
164
handleRootPointerEvent(event, context, state, callback);
165
break;
166
}
166
-
167
- case 'keydown':
168
- case 'keyup': {
169
- const nativeEvent = event.nativeEvent;
170
- if (
171
- nativeEvent.key === 'Tab' &&
172
- !(
173
- nativeEvent.metaKey ||
174
- (!isMac && nativeEvent.altKey) ||
175
- nativeEvent.ctrlKey
176
- )
177
- ) {
178
- state.pointerType = 'keyboard';
179
- isGlobalFocusVisible = true;
167
+ case 'mousedown': {
168
+ if (!state.isEmulatingMouseEvents) {
169
+ state.pointerType = 'mouse';
170
+ handleRootPointerEvent(event, context, state, callback);
171
+ } else {
172
+ state.isEmulatingMouseEvents = false;
173
}
174
break;
175
}
@@ -271,6 +264,7 @@ const focusResponderImpl = {
264
getInitialState(): FocusState {
265
return {
266
focusTarget: null,
267
+ isEmulatingMouseEvents: false,
268
isFocused: false,
269
isFocusVisible: false,
270
pointerType: '',
@@ -303,6 +297,7 @@ const focusResponderImpl = {
297
state.isFocusVisible = isGlobalFocusVisible;
298
dispatchFocusEvents(context, props, state);
299
}
300
+ state.isEmulatingMouseEvents = false;
301
break;
302
}
303
case 'blur': {
@@ -311,6 +306,17 @@ const focusResponderImpl = {
306
state.isFocusVisible = isGlobalFocusVisible;
307
state.isFocused = false;
308
}
309
+ // This covers situations where focus is lost to another document in
310
+ // the same window (e.g., iframes). Any action that restores focus to
311
+ // the document (e.g., touch or click) first causes 'focus' to be
312
+ // dispatched, which means the 'pointerType' we provide is stale
313
+ // (it reflects the *previous* pointer). We cannot determine the
314
+ // 'pointerType' in this case, so a blur with no
315
+ // relatedTarget is used as a signal to reset the 'pointerType'.
316
+ if (event.nativeEvent.relatedTarget == null) {
317
+ state.pointerType = '';
318
+ }
319
+ state.isEmulatingMouseEvents = false;
320
break;
321
}
322
}
@@ -322,7 +328,7 @@ const focusResponderImpl = {
328
state: FocusState,
329
): void {
330
handleRootEvent(event, context, state, isFocusVisible => {
325
- if (state.isFocusVisible !== isFocusVisible) {
331
+ if (state.isFocused && state.isFocusVisible !== isFocusVisible) {
332
state.isFocusVisible = isFocusVisible;
333
dispatchFocusVisibleChangeEvent(context, props, isFocusVisible);
334
}
@@ -402,6 +408,7 @@ const focusWithinResponderImpl = {
408
getInitialState(): FocusState {
409
return {
410
focusTarget: null,
411
+ isEmulatingMouseEvents: false,
412
isFocused: false,
413
isFocusVisible: false,
414
pointerType: '',
@@ -460,7 +467,7 @@ const focusWithinResponderImpl = {
467
state: FocusState,
468
): void {
469
handleRootEvent(event, context, state, isFocusVisible => {
463
- if (state.isFocusVisible !== isFocusVisible) {
470
+ if (state.isFocused && state.isFocusVisible !== isFocusVisible) {
471
state.isFocusVisible = isFocusVisible;
472
dispatchFocusWithinVisibleChangeEvent(
473
context,
packages/react-events/src/dom/__tests__/ContextMenu-test.internal.js
+13
-45
@@ -9,7 +9,13 @@
9
10
'use strict';
11
12
-import {createEvent, platform, setPointerEvent} from '../test-utils';
12
+import {
13
+ dispatchLongPressContextMenu,
14
+ dispatchRightClickContextMenu,
15
+ dispatchModifiedClickContextMenu,
16
+ platform,
17
+ setPointerEvent,
18
+} from '../test-utils';
19
20
let React;
21
let ReactFeatureFlags;
@@ -27,44 +33,6 @@ function initializeModules(hasPointerEvents) {
33
.useContextMenuResponder;
34
}
35
30
-function dispatchContextMenuEvents(ref, options) {
31
- const preventDefault = options.preventDefault || function() {};
32
- const variant = (options.variant: 'mouse' | 'touch' | 'modified');
33
- const dispatchEvent = arg => ref.current.dispatchEvent(arg);
34
-
35
- if (variant === 'mouse') {
36
- // right-click
37
- dispatchEvent(
38
- createEvent('pointerdown', {pointerType: 'mouse', button: 2}),
39
- );
40
- dispatchEvent(createEvent('mousedown', {button: 2}));
41
- dispatchEvent(createEvent('contextmenu', {button: 2, preventDefault}));
42
- } else if (variant === 'modified') {
43
- // left-click + ctrl
44
- dispatchEvent(
45
- createEvent('pointerdown', {pointerType: 'mouse', button: 0}),
46
- );
47
- dispatchEvent(createEvent('mousedown', {button: 0}));
48
- if (platform.get() === 'mac') {
49
- dispatchEvent(
50
- createEvent('contextmenu', {button: 0, ctrlKey: true, preventDefault}),
51
- );
52
- }
53
- } else if (variant === 'touch') {
54
- // long-press
55
- dispatchEvent(
56
- createEvent('pointerdown', {pointerType: 'touch', button: 0}),
57
- );
58
- dispatchEvent(
59
- createEvent('touchstart', {
60
- changedTouches: [],
61
- targetTouches: [],
62
- }),
63
- );
64
- dispatchEvent(createEvent('contextmenu', {button: 0, preventDefault}));
65
- }
66
-}
67
-
36
const forcePointerEvents = true;
37
const table = [[forcePointerEvents], [!forcePointerEvents]];
38
@@ -94,7 +62,7 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
62
};
63
ReactDOM.render(<Component />, container);
64
97
- dispatchContextMenuEvents(ref, {variant: 'mouse', preventDefault});
65
+ dispatchRightClickContextMenu(ref.current, {preventDefault});
66
expect(preventDefault).toHaveBeenCalledTimes(1);
67
expect(onContextMenu).toHaveBeenCalledTimes(1);
68
expect(onContextMenu).toHaveBeenCalledWith(
@@ -112,7 +80,7 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
80
};
81
ReactDOM.render(<Component />, container);
82
115
- dispatchContextMenuEvents(ref, {variant: 'touch', preventDefault});
83
+ dispatchLongPressContextMenu(ref.current, {preventDefault});
84
expect(preventDefault).toHaveBeenCalledTimes(1);
85
expect(onContextMenu).toHaveBeenCalledTimes(1);
86
expect(onContextMenu).toHaveBeenCalledWith(
@@ -132,7 +100,7 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
100
};
101
ReactDOM.render(<Component />, container);
102
135
- dispatchContextMenuEvents(ref, 'mouse');
103
+ dispatchRightClickContextMenu(ref.current);
104
expect(onContextMenu).toHaveBeenCalledTimes(0);
105
});
106
@@ -149,7 +117,7 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
117
};
118
ReactDOM.render(<Component />, container);
119
152
- dispatchContextMenuEvents(ref, {variant: 'mouse', preventDefault});
120
+ dispatchRightClickContextMenu(ref.current, {preventDefault});
121
expect(preventDefault).toHaveBeenCalledTimes(0);
122
expect(onContextMenu).toHaveBeenCalledTimes(1);
123
});
@@ -174,7 +142,7 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
142
};
143
ReactDOM.render(<Component />, container);
144
177
- dispatchContextMenuEvents(ref, {variant: 'modified'});
145
+ dispatchModifiedClickContextMenu(ref.current);
146
expect(onContextMenu).toHaveBeenCalledTimes(1);
147
expect(onContextMenu).toHaveBeenCalledWith(
148
expect.objectContaining({pointerType: 'mouse', type: 'contextmenu'}),
@@ -201,7 +169,7 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
169
};
170
ReactDOM.render(<Component />, container);
171
204
- dispatchContextMenuEvents(ref, {variant: 'modified'});
172
+ dispatchModifiedClickContextMenu(ref.current);
173
expect(onContextMenu).toHaveBeenCalledTimes(0);
174
});
175
});
packages/react-events/src/dom/__tests__/Focus-test.internal.js
+85
-127
@@ -9,47 +9,41 @@
9
10
'use strict';
11
12
+import {
13
+ blur,
14
+ focus,
15
+ keydown,
16
+ setPointerEvent,
17
+ platform,
18
+ dispatchPointerPressDown,
19
+ dispatchPointerPressRelease,
20
+} from '../test-utils';
21
+
22
let React;
23
let ReactFeatureFlags;
24
let ReactDOM;
25
let FocusResponder;
26
let useFocusResponder;
27
18
-const createEvent = (type, data) => {
19
- const event = document.createEvent('CustomEvent');
20
- event.initCustomEvent(type, true, true);
21
- if (data != null) {
22
- Object.entries(data).forEach(([key, value]) => {
23
- event[key] = value;
24
- });
25
- }
26
- return event;
27
-};
28
-
29
-const createKeyboardEvent = (type, data) => {
30
- return new KeyboardEvent(type, {
31
- bubbles: true,
32
- cancelable: true,
33
- ...data,
34
- });
35
-};
36
-
37
-const modulesInit = () => {
28
+function initializeModules(hasPointerEvents) {
29
+ setPointerEvent(hasPointerEvents);
30
+ jest.resetModules();
31
ReactFeatureFlags = require('shared/ReactFeatureFlags');
32
ReactFeatureFlags.enableFlareAPI = true;
33
React = require('react');
34
ReactDOM = require('react-dom');
35
FocusResponder = require('react-events/focus').FocusResponder;
36
useFocusResponder = require('react-events/focus').useFocusResponder;
44
-};
37
+}
38
+
39
+const forcePointerEvents = true;
40
+const table = [[forcePointerEvents], [!forcePointerEvents]];
41
46
-describe('Focus event responder', () => {
42
+describe.each(table)('Focus responder', hasPointerEvents => {
43
let container;
44
45
beforeEach(() => {
50
- jest.resetModules();
51
- modulesInit();
52
-
46
+ initializeModules(hasPointerEvents);
47
container = document.createElement('div');
48
document.body.appendChild(container);
49
});
@@ -78,9 +72,10 @@ describe('Focus event responder', () => {
72
ReactDOM.render(<Component />, container);
73
});
74
81
- it('prevents custom events being dispatched', () => {
82
- ref.current.dispatchEvent(createEvent('focus'));
83
- ref.current.dispatchEvent(createEvent('blur'));
75
+ it('does not call callbacks', () => {
76
+ const dispatch = arg => ref.current.dispatchEvent(arg);
77
+ dispatch(focus());
78
+ dispatch(blur());
79
expect(onFocus).not.toBeCalled();
80
expect(onBlur).not.toBeCalled();
81
});
@@ -102,8 +97,9 @@ describe('Focus event responder', () => {
97
});
98
99
it('is called after "blur" event', () => {
105
- ref.current.dispatchEvent(createEvent('focus'));
106
- ref.current.dispatchEvent(createEvent('blur'));
100
+ const dispatch = arg => ref.current.dispatchEvent(arg);
101
+ dispatch(focus());
102
+ dispatch(blur());
103
expect(onBlur).toHaveBeenCalledTimes(1);
104
});
105
});
@@ -131,83 +127,52 @@ describe('Focus event responder', () => {
127
beforeEach(componentInit);
128
129
it('is called after "focus" event', () => {
134
- ref.current.dispatchEvent(createEvent('focus'));
130
+ ref.current.dispatchEvent(focus());
131
expect(onFocus).toHaveBeenCalledTimes(1);
132
});
133
134
it('is not called if descendants of target receive focus', () => {
139
- const target = innerRef.current;
140
- target.dispatchEvent(createEvent('focus'));
135
+ innerRef.current.dispatchEvent(focus());
136
expect(onFocus).not.toBeCalled();
137
});
138
144
- it('is called with the correct pointerType using pointer events', () => {
145
- // Pointer mouse
146
- ref.current.dispatchEvent(
147
- createEvent('pointerdown', {
148
- pointerType: 'mouse',
149
- }),
150
- );
151
- ref.current.dispatchEvent(createEvent('focus'));
139
+ it('is called with the correct pointerType: mouse', () => {
140
+ const target = ref.current;
141
+ dispatchPointerPressDown(target, {pointerType: 'mouse'});
142
+ dispatchPointerPressRelease(target, {pointerType: 'mouse'});
143
expect(onFocus).toHaveBeenCalledTimes(1);
144
expect(onFocus).toHaveBeenCalledWith(
145
expect.objectContaining({pointerType: 'mouse'}),
146
);
156
- ref.current.dispatchEvent(createEvent('blur'));
157
-
158
- // Pointer touch
159
- ref.current.dispatchEvent(
160
- createEvent('pointerdown', {
161
- pointerType: 'touch',
162
- }),
163
- );
164
- ref.current.dispatchEvent(createEvent('focus'));
165
- expect(onFocus).toHaveBeenCalledTimes(2);
166
- expect(onFocus).toHaveBeenCalledWith(
167
- expect.objectContaining({pointerType: 'touch'}),
168
- );
169
- ref.current.dispatchEvent(createEvent('blur'));
170
-
171
- // Pointer pen
172
- ref.current.dispatchEvent(
173
- createEvent('pointerdown', {
174
- pointerType: 'pen',
175
- }),
176
- );
177
- ref.current.dispatchEvent(createEvent('focus'));
178
- expect(onFocus).toHaveBeenCalledTimes(3);
179
- expect(onFocus).toHaveBeenCalledWith(
180
- expect.objectContaining({pointerType: 'pen'}),
181
- );
147
});
148
184
- it('is called with the correct pointerType without pointer events', () => {
185
- // Mouse
186
- ref.current.dispatchEvent(createEvent('mousedown'));
187
- ref.current.dispatchEvent(createEvent('focus'));
149
+ it('is called with the correct pointerType: touch', () => {
150
+ const target = ref.current;
151
+ dispatchPointerPressDown(target, {pointerType: 'touch'});
152
+ dispatchPointerPressRelease(target, {pointerType: 'touch'});
153
expect(onFocus).toHaveBeenCalledTimes(1);
189
- expect(onFocus).toHaveBeenCalledWith(
190
- expect.objectContaining({pointerType: 'mouse'}),
191
- );
192
- ref.current.dispatchEvent(createEvent('blur'));
193
-
194
- // Touch
195
- ref.current.dispatchEvent(createEvent('touchstart'));
196
- ref.current.dispatchEvent(createEvent('focus'));
197
- expect(onFocus).toHaveBeenCalledTimes(2);
154
expect(onFocus).toHaveBeenCalledWith(
155
expect.objectContaining({pointerType: 'touch'}),
156
);
157
});
158
159
+ if (hasPointerEvents) {
160
+ it('is called with the correct pointerType: pen', () => {
161
+ const target = ref.current;
162
+ dispatchPointerPressDown(target, {pointerType: 'pen'});
163
+ dispatchPointerPressRelease(target, {pointerType: 'pen'});
164
+ expect(onFocus).toHaveBeenCalledTimes(1);
165
+ expect(onFocus).toHaveBeenCalledWith(
166
+ expect.objectContaining({pointerType: 'pen'}),
167
+ );
168
+ });
169
+ }
170
+
171
it('is called with the correct pointerType using a keyboard', () => {
172
+ const target = ref.current;
173
// Keyboard tab
205
- ref.current.dispatchEvent(
206
- createEvent('keydown', {
207
- key: 'Tab',
208
- }),
209
- );
210
- ref.current.dispatchEvent(createEvent('focus'));
174
+ target.dispatchEvent(keydown({key: 'Tab'}));
175
+ target.dispatchEvent(focus());
176
expect(onFocus).toHaveBeenCalledTimes(1);
177
expect(onFocus).toHaveBeenCalledWith(
178
expect.objectContaining({pointerType: 'keyboard'}),
@@ -215,19 +180,14 @@ describe('Focus event responder', () => {
180
});
181
182
it('is called with the correct pointerType using Tab+altKey on Mac', () => {
183
+ platform.set('mac');
184
jest.resetModules();
219
- const platformGetter = jest.spyOn(global.navigator, 'platform', 'get');
220
- platformGetter.mockReturnValue('MacIntel');
221
- modulesInit();
185
+ initializeModules();
186
componentInit();
187
+ const target = ref.current;
188
224
- ref.current.dispatchEvent(
225
- createEvent('keydown', {
226
- key: 'Tab',
227
- altKey: true,
228
- }),
229
- );
230
- ref.current.dispatchEvent(createEvent('focus'));
189
+ target.dispatchEvent(keydown({key: 'Tab', altKey: true}));
190
+ target.dispatchEvent(focus());
191
expect(onFocus).toHaveBeenCalledTimes(1);
192
expect(onFocus).toHaveBeenCalledWith(
193
expect.objectContaining({
@@ -235,7 +195,7 @@ describe('Focus event responder', () => {
195
}),
196
);
197
238
- platformGetter.mockClear();
198
+ platform.clear();
199
});
200
});
201
@@ -260,18 +220,20 @@ describe('Focus event responder', () => {
220
});
221
222
it('is called after "blur" and "focus" events', () => {
263
- ref.current.dispatchEvent(createEvent('focus'));
223
+ const target = ref.current;
224
+ target.dispatchEvent(focus());
225
expect(onFocusChange).toHaveBeenCalledTimes(1);
226
expect(onFocusChange).toHaveBeenCalledWith(true);
266
- ref.current.dispatchEvent(createEvent('blur'));
227
+ target.dispatchEvent(blur());
228
expect(onFocusChange).toHaveBeenCalledTimes(2);
229
expect(onFocusChange).toHaveBeenCalledWith(false);
230
});
231
232
it('is not called after "blur" and "focus" events on descendants', () => {
272
- innerRef.current.dispatchEvent(createEvent('focus'));
233
+ const target = innerRef.current;
234
+ target.dispatchEvent(focus());
235
expect(onFocusChange).toHaveBeenCalledTimes(0);
274
- innerRef.current.dispatchEvent(createEvent('blur'));
236
+ target.dispatchEvent(blur());
237
expect(onFocusChange).toHaveBeenCalledTimes(0);
238
});
239
});
@@ -297,52 +259,48 @@ describe('Focus event responder', () => {
259
});
260
261
it('is called after "focus" and "blur" if keyboard navigation is active', () => {
262
+ const target = ref.current;
263
// use keyboard first
301
- container.dispatchEvent(createKeyboardEvent('keydown', {key: 'Tab'}));
302
- ref.current.dispatchEvent(createEvent('focus'));
264
+ container.dispatchEvent(keydown({key: 'Tab'}));
265
+ target.dispatchEvent(focus());
266
expect(onFocusVisibleChange).toHaveBeenCalledTimes(1);
267
expect(onFocusVisibleChange).toHaveBeenCalledWith(true);
305
- ref.current.dispatchEvent(
306
- createEvent('blur', {relatedTarget: container}),
307
- );
268
+ target.dispatchEvent(blur({relatedTarget: container}));
269
expect(onFocusVisibleChange).toHaveBeenCalledTimes(2);
270
expect(onFocusVisibleChange).toHaveBeenCalledWith(false);
271
});
272
273
it('is called if non-keyboard event is dispatched on target previously focused with keyboard', () => {
274
+ const target = ref.current;
275
// use keyboard first
314
- container.dispatchEvent(createKeyboardEvent('keydown', {key: 'Tab'}));
315
- ref.current.dispatchEvent(createEvent('focus'));
276
+ container.dispatchEvent(keydown({key: 'Tab'}));
277
+ target.dispatchEvent(focus());
278
expect(onFocusVisibleChange).toHaveBeenCalledTimes(1);
279
expect(onFocusVisibleChange).toHaveBeenCalledWith(true);
280
// then use pointer on the target, focus should no longer be visible
319
- ref.current.dispatchEvent(createEvent('pointerdown'));
281
+ dispatchPointerPressDown(target);
282
expect(onFocusVisibleChange).toHaveBeenCalledTimes(2);
283
expect(onFocusVisibleChange).toHaveBeenCalledWith(false);
284
// onFocusVisibleChange should not be called again
323
- ref.current.dispatchEvent(
324
- createEvent('blur', {relatedTarget: container}),
325
- );
285
+ target.dispatchEvent(blur({relatedTarget: container}));
286
expect(onFocusVisibleChange).toHaveBeenCalledTimes(2);
287
});
288
289
it('is not called after "focus" and "blur" events without keyboard', () => {
330
- ref.current.dispatchEvent(createEvent('pointerdown'));
331
- ref.current.dispatchEvent(createEvent('focus'));
332
- container.dispatchEvent(createEvent('pointerdown'));
333
- ref.current.dispatchEvent(
334
- createEvent('blur', {relatedTarget: container}),
335
- );
290
+ const target = ref.current;
291
+ dispatchPointerPressDown(target);
292
+ dispatchPointerPressRelease(target);
293
+ dispatchPointerPressDown(container);
294
+ target.dispatchEvent(blur({relatedTarget: container}));
295
expect(onFocusVisibleChange).toHaveBeenCalledTimes(0);
296
});
297
298
it('is not called after "blur" and "focus" events on descendants', () => {
340
- container.dispatchEvent(createKeyboardEvent('keydown', {key: 'Tab'}));
341
- innerRef.current.dispatchEvent(createEvent('focus'));
299
+ const target = innerRef.current;
300
+ container.dispatchEvent(keydown({key: 'Tab'}));
301
+ target.dispatchEvent(focus());
302
expect(onFocusVisibleChange).toHaveBeenCalledTimes(0);
343
- innerRef.current.dispatchEvent(
344
- createEvent('blur', {relatedTarget: container}),
345
- );
303
+ target.dispatchEvent(blur({relatedTarget: container}));
304
expect(onFocusVisibleChange).toHaveBeenCalledTimes(0);
305
});
306
});
@@ -380,10 +338,10 @@ describe('Focus event responder', () => {
338
339
ReactDOM.render(<Outer />, container);
340
383
- outerRef.current.dispatchEvent(createEvent('focus'));
384
- outerRef.current.dispatchEvent(createEvent('blur'));
385
- innerRef.current.dispatchEvent(createEvent('focus'));
386
- innerRef.current.dispatchEvent(createEvent('blur'));
341
+ outerRef.current.dispatchEvent(focus());
342
+ outerRef.current.dispatchEvent(blur());
343
+ innerRef.current.dispatchEvent(focus());
344
+ innerRef.current.dispatchEvent(blur());
345
expect(events).toEqual([
346
'outer: onFocus',
347
'outer: onFocusChange',
packages/react-events/src/dom/__tests__/FocusWithin-test.internal.js
+74
-102
@@ -9,32 +9,24 @@
9
10
'use strict';
11
12
+import {
13
+ blur,
14
+ focus,
15
+ keydown,
16
+ setPointerEvent,
17
+ dispatchPointerPressDown,
18
+ dispatchPointerPressRelease,
19
+} from '../test-utils';
20
+
21
let React;
22
let ReactFeatureFlags;
23
let ReactDOM;
24
let FocusWithinResponder;
25
let useFocusWithinResponder;
26
18
-const createEvent = (type, data) => {
19
- const event = document.createEvent('CustomEvent');
20
- event.initCustomEvent(type, true, true);
21
- if (data != null) {
22
- Object.entries(data).forEach(([key, value]) => {
23
- event[key] = value;
24
- });
25
- }
26
- return event;
27
-};
28
-
29
-const createKeyboardEvent = (type, data) => {
30
- return new KeyboardEvent(type, {
31
- bubbles: true,
32
- cancelable: true,
33
- ...data,
34
- });
35
-};
36
-
37
-const modulesInit = () => {
27
+const initializeModules = hasPointerEvents => {
28
+ setPointerEvent(hasPointerEvents);
29
+ jest.resetModules();
30
ReactFeatureFlags = require('shared/ReactFeatureFlags');
31
ReactFeatureFlags.enableFlareAPI = true;
32
React = require('react');
@@ -44,13 +36,14 @@ const modulesInit = () => {
36
.useFocusWithinResponder;
37
};
38
47
-describe('FocusWithin event responder', () => {
39
+const forcePointerEvents = true;
40
+const table = [[forcePointerEvents], [!forcePointerEvents]];
41
+
42
+describe.each(table)('FocusWithin responder', hasPointerEvents => {
43
let container;
44
45
beforeEach(() => {
51
- jest.resetModules();
52
- modulesInit();
53
-
46
+ initializeModules();
47
container = document.createElement('div');
48
document.body.appendChild(container);
49
});
@@ -80,8 +73,9 @@ describe('FocusWithin event responder', () => {
73
});
74
75
it('prevents custom events being dispatched', () => {
83
- ref.current.dispatchEvent(createEvent('focus'));
84
- ref.current.dispatchEvent(createEvent('blur'));
76
+ const target = ref.current;
77
+ target.dispatchEvent(focus());
78
+ target.dispatchEvent(blur());
79
expect(onFocusWithinChange).not.toBeCalled();
80
expect(onFocusWithinVisibleChange).not.toBeCalled();
81
});
@@ -110,49 +104,42 @@ describe('FocusWithin event responder', () => {
104
});
105
106
it('is called after "blur" and "focus" events on focus target', () => {
113
- ref.current.dispatchEvent(createEvent('focus'));
107
+ const target = ref.current;
108
+ target.dispatchEvent(focus());
109
expect(onFocusWithinChange).toHaveBeenCalledTimes(1);
110
expect(onFocusWithinChange).toHaveBeenCalledWith(true);
116
- ref.current.dispatchEvent(
117
- createEvent('blur', {relatedTarget: container}),
118
- );
111
+ target.dispatchEvent(blur({relatedTarget: container}));
112
expect(onFocusWithinChange).toHaveBeenCalledTimes(2);
113
expect(onFocusWithinChange).toHaveBeenCalledWith(false);
114
});
115
116
it('is called after "blur" and "focus" events on descendants', () => {
124
- innerRef.current.dispatchEvent(createEvent('focus'));
117
+ const target = innerRef.current;
118
+ target.dispatchEvent(focus());
119
expect(onFocusWithinChange).toHaveBeenCalledTimes(1);
120
expect(onFocusWithinChange).toHaveBeenCalledWith(true);
127
- innerRef.current.dispatchEvent(
128
- createEvent('blur', {relatedTarget: container}),
129
- );
121
+ target.dispatchEvent(blur({relatedTarget: container}));
122
expect(onFocusWithinChange).toHaveBeenCalledTimes(2);
123
expect(onFocusWithinChange).toHaveBeenCalledWith(false);
124
});
125
126
it('is only called once when focus moves within and outside the subtree', () => {
127
+ const target = ref.current;
128
+ const innerTarget1 = innerRef.current;
129
+ const innerTarget2 = innerRef2.current;
130
// focus shifts into subtree
136
- innerRef.current.dispatchEvent(createEvent('focus'));
131
+ innerTarget1.dispatchEvent(focus());
132
expect(onFocusWithinChange).toHaveBeenCalledTimes(1);
133
expect(onFocusWithinChange).toHaveBeenCalledWith(true);
134
// focus moves around subtree
140
- innerRef.current.dispatchEvent(
141
- createEvent('blur', {relatedTarget: innerRef2.current}),
142
- );
143
- innerRef2.current.dispatchEvent(createEvent('focus'));
144
- innerRef2.current.dispatchEvent(
145
- createEvent('blur', {relatedTarget: ref.current}),
146
- );
147
- ref.current.dispatchEvent(createEvent('focus'));
148
- ref.current.dispatchEvent(
149
- createEvent('blur', {relatedTarget: innerRef.current}),
150
- );
135
+ innerTarget1.dispatchEvent(blur({relatedTarget: innerTarget2}));
136
+ innerTarget2.dispatchEvent(focus());
137
+ innerTarget2.dispatchEvent(blur({relatedTarget: target}));
138
+ target.dispatchEvent(focus());
139
+ target.dispatchEvent(blur({relatedTarget: innerTarget1}));
140
expect(onFocusWithinChange).toHaveBeenCalledTimes(1);
141
// focus shifts outside subtree
153
- innerRef.current.dispatchEvent(
154
- createEvent('blur', {relatedTarget: container}),
155
- );
142
+ innerTarget1.dispatchEvent(blur({relatedTarget: container}));
143
expect(onFocusWithinChange).toHaveBeenCalledTimes(2);
144
expect(onFocusWithinChange).toHaveBeenCalledWith(false);
145
});
@@ -181,102 +168,87 @@ describe('FocusWithin event responder', () => {
168
});
169
170
it('is called after "focus" and "blur" on focus target if keyboard was used', () => {
171
+ const target = ref.current;
172
// use keyboard first
185
- container.dispatchEvent(createKeyboardEvent('keydown', {key: 'Tab'}));
186
- ref.current.dispatchEvent(createEvent('focus'));
173
+ container.dispatchEvent(keydown({key: 'Tab'}));
174
+ target.dispatchEvent(focus());
175
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1);
176
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true);
189
- ref.current.dispatchEvent(
190
- createEvent('blur', {relatedTarget: container}),
191
- );
177
+ target.dispatchEvent(blur({relatedTarget: container}));
178
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
179
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
180
});
181
182
it('is called after "focus" and "blur" on descendants if keyboard was used', () => {
183
+ const innerTarget = innerRef.current;
184
// use keyboard first
198
- container.dispatchEvent(createKeyboardEvent('keydown', {key: 'Tab'}));
199
- innerRef.current.dispatchEvent(createEvent('focus'));
185
+ container.dispatchEvent(keydown({key: 'Tab'}));
186
+ innerTarget.dispatchEvent(focus());
187
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1);
188
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true);
202
- innerRef.current.dispatchEvent(
203
- createEvent('blur', {relatedTarget: container}),
204
- );
189
+ innerTarget.dispatchEvent(blur({relatedTarget: container}));
190
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
191
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
192
});
193
194
it('is called if non-keyboard event is dispatched on target previously focused with keyboard', () => {
195
+ const target = ref.current;
196
+ const innerTarget1 = innerRef.current;
197
+ const innerTarget2 = innerRef2.current;
198
// use keyboard first
211
- ref.current.dispatchEvent(createEvent('focus'));
212
- ref.current.dispatchEvent(createKeyboardEvent('keydown', {key: 'Tab'}));
213
- ref.current.dispatchEvent(
214
- createEvent('blur', {relatedTarget: innerRef.current}),
215
- );
216
- innerRef.current.dispatchEvent(createEvent('focus'));
199
+ target.dispatchEvent(focus());
200
+ target.dispatchEvent(keydown({key: 'Tab'}));
201
+ target.dispatchEvent(blur({relatedTarget: innerTarget1}));
202
+ innerTarget1.dispatchEvent(focus());
203
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1);
204
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true);
205
// then use pointer on the next target, focus should no longer be visible
220
- innerRef2.current.dispatchEvent(createEvent('pointerdown'));
221
- innerRef.current.dispatchEvent(
222
- createEvent('blur', {relatedTarget: innerRef2.current}),
223
- );
224
- innerRef2.current.dispatchEvent(createEvent('focus'));
206
+ dispatchPointerPressDown(innerTarget2);
207
+ innerTarget1.dispatchEvent(blur({relatedTarget: innerTarget2}));
208
+ innerTarget2.dispatchEvent(focus());
209
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
210
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
211
// then use keyboard again
228
- innerRef2.current.dispatchEvent(
229
- createKeyboardEvent('keydown', {key: 'Tab', shiftKey: true}),
230
- );
231
- innerRef2.current.dispatchEvent(
232
- createEvent('blur', {relatedTarget: innerRef.current}),
233
- );
234
- innerRef.current.dispatchEvent(createEvent('focus'));
212
+ innerTarget2.dispatchEvent(keydown({key: 'Tab', shiftKey: true}));
213
+ innerTarget2.dispatchEvent(blur({relatedTarget: innerTarget1}));
214
+ innerTarget1.dispatchEvent(focus());
215
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(3);
216
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true);
217
// then use pointer on the target, focus should no longer be visible
238
- innerRef.current.dispatchEvent(createEvent('pointerdown'));
218
+ dispatchPointerPressDown(innerTarget1);
219
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(4);
220
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
221
// onFocusVisibleChange should not be called again
242
- innerRef.current.dispatchEvent(
243
- createEvent('blur', {relatedTarget: container}),
244
- );
222
+ innerTarget1.dispatchEvent(blur({relatedTarget: container}));
223
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(4);
224
});
225
226
it('is not called after "focus" and "blur" events without keyboard', () => {
249
- innerRef.current.dispatchEvent(createEvent('pointerdown'));
250
- innerRef.current.dispatchEvent(createEvent('focus'));
251
- container.dispatchEvent(createEvent('pointerdown'));
252
- innerRef.current.dispatchEvent(
253
- createEvent('blur', {relatedTarget: container}),
254
- );
227
+ const innerTarget = innerRef.current;
228
+ dispatchPointerPressDown(innerTarget);
229
+ dispatchPointerPressRelease(innerTarget);
230
+ innerTarget.dispatchEvent(blur({relatedTarget: container}));
231
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(0);
232
});
233
234
it('is only called once when focus moves within and outside the subtree', () => {
235
+ const target = ref.current;
236
+ const innerTarget1 = innerRef.current;
237
+ const innerTarget2 = innerRef2.current;
238
+
239
// focus shifts into subtree
260
- innerRef.current.dispatchEvent(createEvent('focus'));
240
+ innerTarget1.dispatchEvent(focus());
241
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1);
242
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true);
243
// focus moves around subtree
264
- innerRef.current.dispatchEvent(
265
- createEvent('blur', {relatedTarget: innerRef2.current}),
266
- );
267
- innerRef2.current.dispatchEvent(createEvent('focus'));
268
- innerRef2.current.dispatchEvent(
269
- createEvent('blur', {relatedTarget: ref.current}),
270
- );
271
- ref.current.dispatchEvent(createEvent('focus'));
272
- ref.current.dispatchEvent(
273
- createEvent('blur', {relatedTarget: innerRef.current}),
274
- );
244
+ innerTarget1.dispatchEvent(blur({relatedTarget: innerTarget2}));
245
+ innerTarget2.dispatchEvent(focus());
246
+ innerTarget2.dispatchEvent(blur({relatedTarget: target}));
247
+ target.dispatchEvent(focus());
248
+ target.dispatchEvent(blur({relatedTarget: innerTarget1}));
249
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1);
250
// focus shifts outside subtree
277
- innerRef.current.dispatchEvent(
278
- createEvent('blur', {relatedTarget: container}),
279
- );
251
+ innerTarget1.dispatchEvent(blur({relatedTarget: container}));
252
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
253
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
254
});
packages/react-events/src/dom/__tests__/Hover-test.internal.js
+52
-33
@@ -76,9 +76,10 @@ describe.each(table)('Hover responder', hasPointerEvents => {
76
ReactDOM.render(<Component />, container);
77
});
78
79
- it('prevents custom events being dispatched', () => {
80
- dispatchPointerHoverEnter(ref);
81
- dispatchPointerHoverExit(ref);
79
+ it('does not call callbacks', () => {
80
+ const target = ref.current;
81
+ dispatchPointerHoverEnter(target);
82
+ dispatchPointerHoverExit(target);
83
expect(onHoverChange).not.toBeCalled();
84
expect(onHoverStart).not.toBeCalled();
85
expect(onHoverMove).not.toBeCalled();
@@ -102,18 +103,21 @@ describe.each(table)('Hover responder', hasPointerEvents => {
103
});
104
105
it('is called for mouse pointers', () => {
105
- dispatchPointerHoverEnter(ref);
106
+ const target = ref.current;
107
+ dispatchPointerHoverEnter(target);
108
expect(onHoverStart).toHaveBeenCalledTimes(1);
109
});
110
111
it('is not called for touch pointers', () => {
110
- dispatchTouchTap(ref);
112
+ const target = ref.current;
113
+ dispatchTouchTap(target);
114
expect(onHoverStart).not.toBeCalled();
115
});
116
117
it('is called if a mouse pointer is used after a touch pointer', () => {
115
- dispatchTouchTap(ref);
116
- dispatchPointerHoverEnter(ref);
118
+ const target = ref.current;
119
+ dispatchTouchTap(target);
120
+ dispatchPointerHoverEnter(target);
121
expect(onHoverStart).toHaveBeenCalledTimes(1);
122
});
123
});
@@ -134,16 +138,18 @@ describe.each(table)('Hover responder', hasPointerEvents => {
138
});
139
140
it('is called for mouse pointers', () => {
137
- dispatchPointerHoverEnter(ref);
141
+ const target = ref.current;
142
+ dispatchPointerHoverEnter(target);
143
expect(onHoverChange).toHaveBeenCalledTimes(1);
144
expect(onHoverChange).toHaveBeenCalledWith(true);
140
- dispatchPointerHoverExit(ref);
145
+ dispatchPointerHoverExit(target);
146
expect(onHoverChange).toHaveBeenCalledTimes(2);
147
expect(onHoverChange).toHaveBeenCalledWith(false);
148
});
149
150
it('is not called for touch pointers', () => {
146
- dispatchTouchTap(ref);
151
+ const target = ref.current;
152
+ dispatchTouchTap(target);
153
expect(onHoverChange).not.toBeCalled();
154
});
155
});
@@ -164,28 +170,31 @@ describe.each(table)('Hover responder', hasPointerEvents => {
170
});
171
172
it('is called for mouse pointers', () => {
167
- dispatchPointerHoverEnter(ref);
168
- dispatchPointerHoverExit(ref);
173
+ const target = ref.current;
174
+ dispatchPointerHoverEnter(target);
175
+ dispatchPointerHoverExit(target);
176
expect(onHoverEnd).toHaveBeenCalledTimes(1);
177
});
178
179
if (hasPointerEvents) {
180
it('is called once for cancelled mouse pointers', () => {
174
- dispatchPointerHoverEnter(ref);
175
- dispatchPointerCancel(ref);
181
+ const target = ref.current;
182
+ dispatchPointerHoverEnter(target);
183
+ dispatchPointerCancel(target);
184
expect(onHoverEnd).toHaveBeenCalledTimes(1);
185
186
// only called once if cancel follows exit
187
onHoverEnd.mockReset();
180
- dispatchPointerHoverEnter(ref);
181
- dispatchPointerHoverExit(ref);
182
- dispatchPointerCancel(ref);
188
+ dispatchPointerHoverEnter(target);
189
+ dispatchPointerHoverExit(target);
190
+ dispatchPointerCancel(target);
191
expect(onHoverEnd).toHaveBeenCalledTimes(1);
192
});
193
}
194
195
it('is not called for touch pointers', () => {
188
- dispatchTouchTap(ref);
196
+ const target = ref.current;
197
+ dispatchTouchTap(target);
198
expect(onHoverEnd).not.toBeCalled();
199
});
200
});
@@ -201,8 +210,10 @@ describe.each(table)('Hover responder', hasPointerEvents => {
210
return <div ref={ref} listeners={listener} />;
211
};
212
ReactDOM.render(<Component />, container);
204
- dispatchPointerHoverEnter(ref);
205
- dispatchPointerHoverMove(ref, {from: {x: 0, y: 0}, to: {x: 1, y: 1}});
213
+
214
+ const target = ref.current;
215
+ dispatchPointerHoverEnter(target);
216
+ dispatchPointerHoverMove(target, {from: {x: 0, y: 0}, to: {x: 1, y: 1}});
217
expect(onHoverMove).toHaveBeenCalledTimes(2);
218
expect(onHoverMove).toHaveBeenCalledWith(
219
expect.objectContaining({type: 'hovermove'}),
@@ -242,12 +253,15 @@ describe.each(table)('Hover responder', hasPointerEvents => {
253
};
254
ReactDOM.render(<Outer />, container);
255
245
- dispatchPointerHoverEnter(outerRef, {relatedTarget: container});
246
- dispatchPointerHoverExit(outerRef, {relatedTarget: innerRef.current});
247
- dispatchPointerHoverEnter(innerRef, {relatedTarget: outerRef.current});
248
- dispatchPointerHoverExit(innerRef, {relatedTarget: outerRef.current});
249
- dispatchPointerHoverEnter(outerRef, {relatedTarget: innerRef.current});
250
- dispatchPointerHoverExit(outerRef, {relatedTarget: container});
256
+ const innerTarget = innerRef.current;
257
+ const outerTarget = outerRef.current;
258
+
259
+ dispatchPointerHoverEnter(outerTarget, {relatedTarget: container});
260
+ dispatchPointerHoverExit(outerTarget, {relatedTarget: innerTarget});
261
+ dispatchPointerHoverEnter(innerTarget, {relatedTarget: outerTarget});
262
+ dispatchPointerHoverExit(innerTarget, {relatedTarget: outerTarget});
263
+ dispatchPointerHoverEnter(outerTarget, {relatedTarget: innerTarget});
264
+ dispatchPointerHoverExit(outerTarget, {relatedTarget: container});
265
266
expect(events).toEqual([
267
'outer: onHoverStart',
@@ -300,9 +314,14 @@ describe.each(table)('Hover responder', hasPointerEvents => {
314
};
315
ReactDOM.render(<Component />, container);
316
303
- dispatchPointerHoverEnter(ref, {x: 10, y: 10});
304
- dispatchPointerHoverMove(ref, {from: {x: 10, y: 10}, to: {x: 20, y: 20}});
305
- dispatchPointerHoverExit(ref, {x: 20, y: 20});
317
+ const target = ref.current;
318
+
319
+ dispatchPointerHoverEnter(target, {x: 10, y: 10});
320
+ dispatchPointerHoverMove(target, {
321
+ from: {x: 10, y: 10},
322
+ to: {x: 20, y: 20},
323
+ });
324
+ dispatchPointerHoverExit(target, {x: 20, y: 20});
325
326
expect(eventLog).toEqual([
327
{
@@ -312,7 +331,7 @@ describe.each(table)('Hover responder', hasPointerEvents => {
331
pageY: 10,
332
clientX: 10,
333
clientY: 10,
315
- target: ref.current,
334
+ target,
335
timeStamp: timeStamps[0],
336
type: 'hoverstart',
337
pointerType: 'mouse',
@@ -324,7 +343,7 @@ describe.each(table)('Hover responder', hasPointerEvents => {
343
pageY: 10,
344
clientX: 10,
345
clientY: 10,
327
- target: ref.current,
346
+ target,
347
timeStamp: timeStamps[1],
348
type: 'hovermove',
349
pointerType: 'mouse',
@@ -336,7 +355,7 @@ describe.each(table)('Hover responder', hasPointerEvents => {
355
pageY: 20,
356
clientX: 20,
357
clientY: 20,
339
- target: ref.current,
358
+ target,
359
timeStamp: timeStamps[2],
360
type: 'hovermove',
361
pointerType: 'mouse',
@@ -348,7 +367,7 @@ describe.each(table)('Hover responder', hasPointerEvents => {
367
pageY: 20,
368
clientX: 20,
369
clientY: 20,
351
- target: ref.current,
370
+ target,
371
timeStamp: timeStamps[3],
372
type: 'hoverend',
373
pointerType: 'mouse',
packages/react-events/src/dom/test-utils.js
+124
-35
@@ -95,6 +95,10 @@ function click(data) {
95
return createEvent('click', data);
96
}
97
98
+function contextmenu(data) {
99
+ return createEvent('contextmenu', data);
100
+}
101
+
102
function dragstart(data) {
103
return createEvent('dragstart', data);
104
}
@@ -107,6 +111,14 @@ function gotpointercapture(data) {
111
return createEvent('gotpointercapture', data);
112
}
113
114
+function keydown(data) {
115
+ return createKeyboardEvent('keydown', data);
116
+}
117
+
118
+function keyup(data) {
119
+ return createKeyboardEvent('keyup', data);
120
+}
121
+
122
function lostpointercapture(data) {
123
return createEvent('lostpointercapture', data);
124
}
@@ -191,8 +203,52 @@ function touchstart(data, id) {
203
* Dispatch high-level event sequences
204
*/
205
194
-function dispatchPointerHoverEnter(ref, {relatedTarget, x, y} = {}) {
195
- const dispatch = arg => ref.current.dispatchEvent(arg);
206
+function emptyFunction() {}
207
+
208
+function dispatchLongPressContextMenu(
209
+ target,
210
+ {preventDefault = emptyFunction} = {},
211
+) {
212
+ const dispatch = arg => target.dispatchEvent(arg);
213
+ const button = 0;
214
+ if (hasPointerEvent()) {
215
+ dispatch(pointerdown({button, pointerType: 'touch'}));
216
+ }
217
+ dispatch(touchstart());
218
+ dispatch(contextmenu({button, preventDefault}));
219
+}
220
+
221
+function dispatchRightClickContextMenu(
222
+ target,
223
+ {preventDefault = emptyFunction} = {},
224
+) {
225
+ const dispatch = arg => target.dispatchEvent(arg);
226
+ const button = 2;
227
+ if (hasPointerEvent()) {
228
+ dispatch(pointerdown({button, pointerType: 'mouse'}));
229
+ }
230
+ dispatch(mousedown({button}));
231
+ dispatch(contextmenu({button, preventDefault}));
232
+}
233
+
234
+function dispatchModifiedClickContextMenu(
235
+ target,
236
+ {preventDefault = emptyFunction} = {},
237
+) {
238
+ const dispatch = arg => target.dispatchEvent(arg);
239
+ const button = 0;
240
+ const ctrlKey = true;
241
+ if (hasPointerEvent()) {
242
+ dispatch(pointerdown({button, ctrlKey, pointerType: 'mouse'}));
243
+ }
244
+ dispatch(mousedown({button, ctrlKey}));
245
+ if (platform.get() === 'mac') {
246
+ dispatch(contextmenu({button, ctrlKey, preventDefault}));
247
+ }
248
+}
249
+
250
+function dispatchPointerHoverEnter(target, {relatedTarget, x, y} = {}) {
251
+ const dispatch = arg => target.dispatchEvent(arg);
252
const button = -1;
253
const pointerType = 'mouse';
254
const event = {
@@ -211,8 +267,8 @@ function dispatchPointerHoverEnter(ref, {relatedTarget, x, y} = {}) {
267
dispatch(mouseover(event));
268
}
269
214
-function dispatchPointerHoverMove(ref, {from, to} = {}) {
215
- const dispatch = arg => ref.current.dispatchEvent(arg);
270
+function dispatchPointerHoverMove(target, {from, to} = {}) {
271
+ const dispatch = arg => target.dispatchEvent(arg);
272
const button = -1;
273
const pointerId = 1;
274
const pointerType = 'mouse';
@@ -233,8 +289,8 @@ function dispatchPointerHoverMove(ref, {from, to} = {}) {
289
dispatchMove({x: to.x, y: to.y});
290
}
291
236
-function dispatchPointerHoverExit(ref, {relatedTarget, x, y} = {}) {
237
- const dispatch = arg => ref.current.dispatchEvent(arg);
292
+function dispatchPointerHoverExit(target, {relatedTarget, x, y} = {}) {
293
+ const dispatch = arg => target.dispatchEvent(arg);
294
const button = -1;
295
const pointerType = 'mouse';
296
const event = {
@@ -253,51 +309,82 @@ function dispatchPointerHoverExit(ref, {relatedTarget, x, y} = {}) {
309
dispatch(mouseleave(event));
310
}
311
256
-function dispatchPointerCancel(ref, options) {
257
- const dispatchEvent = arg => ref.current.dispatchEvent(arg);
312
+function dispatchPointerCancel(target, options) {
313
+ const dispatchEvent = arg => target.dispatchEvent(arg);
314
dispatchEvent(pointercancel({pointerType: 'mouse'}));
315
dispatchEvent(dragstart({pointerType: 'mouse'}));
316
}
317
262
-function dispatchPointerPressDown(ref, {button = 0, pointerType = 'mouse'}) {
263
- const dispatch = arg => ref.current.dispatchEvent(arg);
318
+function dispatchPointerPressDown(
319
+ target,
320
+ {button = 0, pointerType = 'mouse'} = {},
321
+) {
322
+ const dispatch = arg => target.dispatchEvent(arg);
323
const pointerId = 1;
265
- if (hasPointerEvent()) {
266
- dispatch(pointerover({pointerId, pointerType, button}));
267
- dispatch(pointerenter({pointerId, pointerType, button}));
268
- dispatch(pointerdown({pointerId, pointerType, button}));
269
- }
270
- dispatch(touchstart(null, pointerId));
271
- if (hasPointerEvent()) {
272
- dispatch(gotpointercapture({pointerId, pointerType, button}));
324
+ if (pointerType !== 'mouse') {
325
+ if (hasPointerEvent()) {
326
+ dispatch(pointerover({button, pointerId, pointerType}));
327
+ dispatch(pointerenter({button, pointerId, pointerType}));
328
+ dispatch(pointerdown({button, pointerId, pointerType}));
329
+ }
330
+ dispatch(touchstart(null, pointerId));
331
+ if (hasPointerEvent()) {
332
+ dispatch(gotpointercapture({button, pointerId, pointerType}));
333
+ }
334
+ } else {
335
+ if (hasPointerEvent()) {
336
+ dispatch(pointerdown({button, pointerId, pointerType}));
337
+ }
338
+ dispatch(mousedown({button}));
339
+ if (document.activeElement !== target) {
340
+ dispatch(focus({button}));
341
+ }
342
}
343
}
344
276
-function dispatchPointerPressRelease(ref, {button = 0, pointerType = 'mouse'}) {
277
- const dispatch = arg => ref.current.dispatchEvent(arg);
345
+function dispatchPointerPressRelease(
346
+ target,
347
+ {button = 0, pointerType = 'mouse'} = {},
348
+) {
349
+ const dispatch = arg => target.dispatchEvent(arg);
350
const pointerId = 1;
279
- if (hasPointerEvent()) {
280
- dispatch(pointerup({pointerId, pointerType, button}));
281
- dispatch(lostpointercapture({pointerId, pointerType, button}));
282
- dispatch(pointerout({pointerId, pointerType, button}));
283
- dispatch(pointerleave({pointerId, pointerType, button}));
351
+ if (pointerType !== 'mouse') {
352
+ if (hasPointerEvent()) {
353
+ dispatch(pointerup({button, pointerId, pointerType}));
354
+ dispatch(lostpointercapture({button, pointerId, pointerType}));
355
+ dispatch(pointerout({button, pointerId, pointerType}));
356
+ dispatch(pointerleave({button, pointerId, pointerType}));
357
+ }
358
+ dispatch(touchend(null, pointerId));
359
+ dispatch(mouseover({button}));
360
+ dispatch(mousemove({button}));
361
+ dispatch(mousedown({button}));
362
+ if (document.activeElement !== target) {
363
+ dispatch(focus({button}));
364
+ }
365
+ dispatch(mouseup({button}));
366
+ dispatch(click({button}));
367
+ } else {
368
+ if (hasPointerEvent()) {
369
+ dispatch(pointerup({button, pointerId, pointerType}));
370
+ }
371
+ dispatch(mouseup({button}));
372
+ dispatch(click({button}));
373
}
285
- dispatch(touchend(null, pointerId));
286
- dispatch(mouseover({button}));
287
- dispatch(mousemove({button}));
288
- dispatch(mousedown({button}));
289
- dispatch(focus({button}));
290
- dispatch(mouseup({button}));
291
- dispatch(click({button}));
374
}
375
294
-function dispatchTouchTap(ref) {
295
- dispatchPointerPressDown(ref, {pointerType: 'touch'});
296
- dispatchPointerPressRelease(ref, {pointerType: 'touch'});
376
+function dispatchTouchTap(target) {
377
+ dispatchPointerPressDown(target, {pointerType: 'touch'});
378
+ dispatchPointerPressRelease(target, {pointerType: 'touch'});
379
}
380
381
module.exports = {
382
+ blur,
383
+ focus,
384
createEvent,
385
+ dispatchLongPressContextMenu,
386
+ dispatchRightClickContextMenu,
387
+ dispatchModifiedClickContextMenu,
388
dispatchPointerCancel,
389
dispatchPointerHoverEnter,
390
dispatchPointerHoverExit,
@@ -305,6 +392,8 @@ module.exports = {
392
dispatchPointerPressDown,
393
dispatchPointerPressRelease,
394
dispatchTouchTap,
395
+ keydown,
396
+ keyup,
397
platform,
398
hasPointerEvent,
399
setPointerEvent,