Modern Event System: use focusin/focusout for onFocus/onBlur (#19186)
Dominic Gannaway committed
Jul 16, 2020 at 18:12 UTC
a59f899a1d021b231f8bd830436708294d9022c0
11 files changed
+45
-44
packages/react-dom/src/__tests__/ReactDOMInput-test.js
+5
@@ -183,6 +183,7 @@ describe('ReactDOMInput', () => {
183
// bypass the lazy event attachment system so we won't actually test this.
184
dispatchEventOnNode(instance.a, 'input');
185
dispatchEventOnNode(instance.a, 'blur');
186
+ dispatchEventOnNode(instance.a, 'focusout');
187
188
expect(instance.a.value).toBe('giraffe');
189
expect(instance.switchedFocus).toBe(true);
@@ -684,6 +685,7 @@ describe('ReactDOMInput', () => {
685
expect(node.hasAttribute('value')).toBe(false);
686
} else {
687
dispatchEventOnNode(node, 'blur');
688
+ dispatchEventOnNode(node, 'focusout');
689
690
expect(node.value).toBe('0.0');
691
expect(node.getAttribute('value')).toBe('0.0');
@@ -1771,6 +1773,7 @@ describe('ReactDOMInput', () => {
1773
// be the only way to remove focus in JSDOM
1774
node.blur();
1775
dispatchEventOnNode(node, 'blur');
1776
+ dispatchEventOnNode(node, 'focusout');
1777
1778
if (disableInputAttributeSyncing) {
1779
expect(node.value).toBe('2');
@@ -1796,6 +1799,7 @@ describe('ReactDOMInput', () => {
1799
// be the only way to remove focus in JSDOM
1800
node.blur();
1801
dispatchEventOnNode(node, 'blur');
1802
+ dispatchEventOnNode(node, 'focusout');
1803
1804
expect(node.getAttribute('value')).toBe('1');
1805
});
@@ -1815,6 +1819,7 @@ describe('ReactDOMInput', () => {
1819
// be the only way to remove focus in JSDOM
1820
node.blur();
1821
dispatchEventOnNode(node, 'blur');
1822
+ dispatchEventOnNode(node, 'focusout');
1823
1824
expect(node.getAttribute('value')).toBe('1');
1825
});
packages/react-dom/src/events/DOMEventProperties.js
+2
-2
@@ -41,7 +41,6 @@ const eventPriorities = new Map();
41
42
// prettier-ignore
43
const discreteEventPairsForSimpleEventPlugin = [
44
- DOMTopLevelEventTypes.TOP_BLUR, 'blur',
44
DOMTopLevelEventTypes.TOP_CANCEL, 'cancel',
45
DOMTopLevelEventTypes.TOP_CLICK, 'click',
46
DOMTopLevelEventTypes.TOP_CLOSE, 'close',
@@ -53,7 +52,8 @@ const discreteEventPairsForSimpleEventPlugin = [
52
DOMTopLevelEventTypes.TOP_DRAG_END, 'dragEnd',
53
DOMTopLevelEventTypes.TOP_DRAG_START, 'dragStart',
54
DOMTopLevelEventTypes.TOP_DROP, 'drop',
56
- DOMTopLevelEventTypes.TOP_FOCUS, 'focus',
55
+ DOMTopLevelEventTypes.TOP_FOCUS_IN, 'focus',
56
+ DOMTopLevelEventTypes.TOP_FOCUS_OUT, 'blur',
57
DOMTopLevelEventTypes.TOP_INPUT, 'input',
58
DOMTopLevelEventTypes.TOP_INVALID, 'invalid',
59
DOMTopLevelEventTypes.TOP_KEY_DOWN, 'keyDown',
packages/react-dom/src/events/DOMModernPluginEventSystem.js
-4
@@ -37,12 +37,10 @@ import {
37
38
import getEventTarget from './getEventTarget';
39
import {
40
- TOP_FOCUS,
40
TOP_LOAD,
41
TOP_ABORT,
42
TOP_CANCEL,
43
TOP_INVALID,
45
- TOP_BLUR,
44
TOP_SCROLL,
45
TOP_CLOSE,
46
TOP_RESET,
@@ -215,8 +213,6 @@ function extractEvents(
213
}
214
215
export const capturePhaseEvents: Set<DOMTopLevelEventType> = new Set([
218
- TOP_FOCUS,
219
- TOP_BLUR,
216
TOP_SCROLL,
217
TOP_LOAD,
218
TOP_ABORT,
packages/react-dom/src/events/DOMTopLevelEventTypes.js
+3
-2
@@ -32,7 +32,6 @@ export const TOP_ANIMATION_ITERATION = unsafeCastStringToDOMTopLevelType(
32
export const TOP_ANIMATION_START = unsafeCastStringToDOMTopLevelType(
33
getVendorPrefixedEventName('animationstart'),
34
);
35
-export const TOP_BLUR = unsafeCastStringToDOMTopLevelType('blur');
35
export const TOP_CAN_PLAY = unsafeCastStringToDOMTopLevelType('canplay');
36
export const TOP_CAN_PLAY_THROUGH = unsafeCastStringToDOMTopLevelType(
37
'canplaythrough',
@@ -72,7 +71,6 @@ export const TOP_EMPTIED = unsafeCastStringToDOMTopLevelType('emptied');
71
export const TOP_ENCRYPTED = unsafeCastStringToDOMTopLevelType('encrypted');
72
export const TOP_ENDED = unsafeCastStringToDOMTopLevelType('ended');
73
export const TOP_ERROR = unsafeCastStringToDOMTopLevelType('error');
75
-export const TOP_FOCUS = unsafeCastStringToDOMTopLevelType('focus');
74
export const TOP_GOT_POINTER_CAPTURE = unsafeCastStringToDOMTopLevelType(
75
'gotpointercapture',
76
);
@@ -152,6 +150,9 @@ export const TOP_WHEEL = unsafeCastStringToDOMTopLevelType('wheel');
150
export const TOP_AFTER_BLUR = unsafeCastStringToDOMTopLevelType('afterblur');
151
export const TOP_BEFORE_BLUR = unsafeCastStringToDOMTopLevelType('beforeblur');
152
153
+export const TOP_FOCUS_IN = unsafeCastStringToDOMTopLevelType('focusin');
154
+export const TOP_FOCUS_OUT = unsafeCastStringToDOMTopLevelType('focusout');
155
+
156
// List of events that need to be individually attached to media elements.
157
// Note that events in this list will *not* be listened to at the top level
158
// unless they're explicitly listed in `ReactBrowserEventEmitter.listenTo`.
packages/react-dom/src/events/ReactDOMEventReplaying.js
+7
-7
@@ -131,8 +131,8 @@ import {
131
TOP_POINTER_OUT,
132
TOP_GOT_POINTER_CAPTURE,
133
TOP_LOST_POINTER_CAPTURE,
134
- TOP_FOCUS,
135
- TOP_BLUR,
134
+ TOP_FOCUS_IN,
135
+ TOP_FOCUS_OUT,
136
} from './DOMTopLevelEventTypes';
137
import {IS_REPLAYED, PLUGIN_EVENT_SYSTEM} from './EventSystemFlags';
138
import {
@@ -216,10 +216,10 @@ const discreteReplayableEvents = [
216
];
217
218
const continuousReplayableEvents = [
219
- TOP_FOCUS,
220
- TOP_BLUR,
219
TOP_DRAG_ENTER,
220
TOP_DRAG_LEAVE,
221
+ TOP_FOCUS_IN,
222
+ TOP_FOCUS_OUT,
223
TOP_MOUSE_OVER,
224
TOP_MOUSE_OUT,
225
TOP_POINTER_OVER,
@@ -362,8 +362,8 @@ export function clearIfContinuousEvent(
362
nativeEvent: AnyNativeEvent,
363
): void {
364
switch (topLevelType) {
365
- case TOP_FOCUS:
366
- case TOP_BLUR:
365
+ case TOP_FOCUS_IN:
366
+ case TOP_FOCUS_OUT:
367
queuedFocus = null;
368
break;
369
case TOP_DRAG_ENTER:
@@ -443,7 +443,7 @@ export function queueIfContinuousEvent(
443
// moved from outside the window (no target) onto the target once it hydrates.
444
// Instead of mutating we could clone the event.
445
switch (topLevelType) {
446
- case TOP_FOCUS: {
446
+ case TOP_FOCUS_IN: {
447
const focusEvent = ((nativeEvent: any): FocusEvent);
448
queuedFocus = accumulateOrCreateContinuousQueuedReplayableEvent(
449
queuedFocus,
packages/react-dom/src/events/__tests__/DOMModernPluginEventSystem-test.internal.js
+3
-3
@@ -888,9 +888,9 @@ describe('DOMModernPluginEventSystem', () => {
888
expect(onFocus).toHaveBeenCalledTimes(3);
889
expect(onFocusCapture).toHaveBeenCalledTimes(3);
890
expect(log[2]).toEqual(['capture', buttonElement]);
891
- expect(log[3]).toEqual(['bubble', buttonElement]);
892
- expect(log[4]).toEqual(['capture', divElement]);
893
- expect(log[5]).toEqual(['bubble', divElement]);
891
+ expect(log[3]).toEqual(['capture', divElement]);
892
+ expect(log[4]).toEqual(['bubble', divElement]);
893
+ expect(log[5]).toEqual(['bubble', buttonElement]);
894
});
895
896
it('handle propagation of focus events between portals', () => {
packages/react-dom/src/events/plugins/ModernBeforeInputEventPlugin.js
+5
-5
@@ -11,7 +11,7 @@ import {canUseDOM} from 'shared/ExecutionEnvironment';
11
12
import {registerTwoPhaseEvent} from '../EventRegistry';
13
import {
14
- TOP_BLUR,
14
+ TOP_FOCUS_OUT,
15
TOP_COMPOSITION_START,
16
TOP_COMPOSITION_END,
17
TOP_COMPOSITION_UPDATE,
@@ -66,24 +66,24 @@ function registerEvents() {
66
TOP_PASTE,
67
]);
68
registerTwoPhaseEvent('onCompositionEnd', [
69
- TOP_BLUR,
69
TOP_COMPOSITION_END,
70
+ TOP_FOCUS_OUT,
71
TOP_KEY_DOWN,
72
TOP_KEY_PRESS,
73
TOP_KEY_UP,
74
TOP_MOUSE_DOWN,
75
]);
76
registerTwoPhaseEvent('onCompositionStart', [
77
- TOP_BLUR,
77
TOP_COMPOSITION_START,
78
+ TOP_FOCUS_OUT,
79
TOP_KEY_DOWN,
80
TOP_KEY_PRESS,
81
TOP_KEY_UP,
82
TOP_MOUSE_DOWN,
83
]);
84
registerTwoPhaseEvent('onCompositionUpdate', [
85
- TOP_BLUR,
85
TOP_COMPOSITION_UPDATE,
86
+ TOP_FOCUS_OUT,
87
TOP_KEY_DOWN,
88
TOP_KEY_PRESS,
89
TOP_KEY_UP,
@@ -154,7 +154,7 @@ function isFallbackCompositionEnd(topLevelType, nativeEvent) {
154
return nativeEvent.keyCode !== START_KEYCODE;
155
case TOP_KEY_PRESS:
156
case TOP_MOUSE_DOWN:
157
- case TOP_BLUR:
157
+ case TOP_FOCUS_OUT:
158
// Events are not possible without cancelling IME.
159
return true;
160
default:
packages/react-dom/src/events/plugins/ModernChangeEventPlugin.js
+7
-7
@@ -17,10 +17,10 @@ import isTextInputElement from '../isTextInputElement';
17
import {canUseDOM} from 'shared/ExecutionEnvironment';
18
19
import {
20
- TOP_BLUR,
20
+ TOP_FOCUS_OUT,
21
TOP_CHANGE,
22
TOP_CLICK,
23
- TOP_FOCUS,
23
+ TOP_FOCUS_IN,
24
TOP_INPUT,
25
TOP_KEY_DOWN,
26
TOP_KEY_UP,
@@ -42,10 +42,10 @@ import {
42
43
function registerEvents() {
44
registerTwoPhaseEvent('onChange', [
45
- TOP_BLUR,
45
TOP_CHANGE,
46
TOP_CLICK,
48
- TOP_FOCUS,
47
+ TOP_FOCUS_IN,
48
+ TOP_FOCUS_OUT,
49
TOP_INPUT,
50
TOP_KEY_DOWN,
51
TOP_KEY_UP,
@@ -172,7 +172,7 @@ function handlePropertyChange(nativeEvent) {
172
}
173
174
function handleEventsForInputEventPolyfill(topLevelType, target, targetInst) {
175
- if (topLevelType === TOP_FOCUS) {
175
+ if (topLevelType === TOP_FOCUS_IN) {
176
// In IE9, propertychange fires for most input events but is buggy and
177
// doesn't fire when text is deleted, but conveniently, selectionchange
178
// appears to fire in all of the remaining cases so we catch those and
@@ -185,7 +185,7 @@ function handleEventsForInputEventPolyfill(topLevelType, target, targetInst) {
185
// missed a blur event somehow.
186
stopWatchingForValueChange();
187
startWatchingForValueChange(target, targetInst);
188
- } else if (topLevelType === TOP_BLUR) {
188
+ } else if (topLevelType === TOP_FOCUS_OUT) {
189
stopWatchingForValueChange();
190
}
191
}
@@ -304,7 +304,7 @@ function extractEvents(
304
}
305
306
// When blurring, set the value attribute for number inputs
307
- if (topLevelType === TOP_BLUR) {
307
+ if (topLevelType === TOP_FOCUS_OUT) {
308
handleControlledInputBlur(((targetNode: any): HTMLInputElement));
309
}
310
}
packages/react-dom/src/events/plugins/ModernSelectEventPlugin.js
+6
-6
@@ -12,10 +12,10 @@ import shallowEqual from 'shared/shallowEqual';
12
13
import {registerTwoPhaseEvent} from '../EventRegistry';
14
import {
15
- TOP_BLUR,
15
+ TOP_FOCUS_OUT,
16
TOP_CONTEXT_MENU,
17
TOP_DRAG_END,
18
- TOP_FOCUS,
18
+ TOP_FOCUS_IN,
19
TOP_KEY_DOWN,
20
TOP_KEY_UP,
21
TOP_MOUSE_DOWN,
@@ -35,10 +35,10 @@ const skipSelectionChangeEvent =
35
canUseDOM && 'documentMode' in document && document.documentMode <= 11;
36
37
const rootTargetDependencies = [
38
- TOP_BLUR,
38
+ TOP_FOCUS_OUT,
39
TOP_CONTEXT_MENU,
40
TOP_DRAG_END,
41
- TOP_FOCUS,
41
+ TOP_FOCUS_IN,
42
TOP_KEY_DOWN,
43
TOP_KEY_UP,
44
TOP_MOUSE_DOWN,
@@ -186,7 +186,7 @@ function extractEvents(
186
187
switch (topLevelType) {
188
// Track the input node that has focus.
189
- case TOP_FOCUS:
189
+ case TOP_FOCUS_IN:
190
if (
191
isTextInputElement(targetNode) ||
192
targetNode.contentEditable === 'true'
@@ -196,7 +196,7 @@ function extractEvents(
196
lastSelection = null;
197
}
198
break;
199
- case TOP_BLUR:
199
+ case TOP_FOCUS_OUT:
200
activeElement = null;
201
activeElementInst = null;
202
lastSelection = null;
packages/react-dom/src/events/plugins/ModernSimpleEventPlugin.js
+2
-2
@@ -68,8 +68,8 @@ function extractEvents(
68
case DOMTopLevelEventTypes.TOP_KEY_UP:
69
EventConstructor = SyntheticKeyboardEvent;
70
break;
71
- case DOMTopLevelEventTypes.TOP_BLUR:
72
- case DOMTopLevelEventTypes.TOP_FOCUS:
71
+ case DOMTopLevelEventTypes.TOP_FOCUS_IN:
72
+ case DOMTopLevelEventTypes.TOP_FOCUS_OUT:
73
case DOMTopLevelEventTypes.TOP_BEFORE_BLUR:
74
case DOMTopLevelEventTypes.TOP_AFTER_BLUR:
75
EventConstructor = SyntheticFocusEvent;
packages/react-interactions/events/src/dom/create-event-handle/Focus.js
+5
-6
@@ -126,7 +126,6 @@ function handleGlobalFocusVisibleEvent(
126
}
127
128
const passiveObject = {passive: true};
129
-const passiveObjectWithPriority = {passive: true, priority: 0};
129
130
function handleFocusVisibleTargetEvent(
131
type: string,
@@ -243,8 +242,8 @@ export function useFocus(
242
): void {
243
// Setup controlled state for this useFocus hook
244
const stateRef = useRef({isFocused: false, isFocusVisible: false});
246
- const focusHandle = useEvent('focusin', passiveObjectWithPriority);
247
- const blurHandle = useEvent('focusout', passiveObjectWithPriority);
245
+ const focusHandle = useEvent('focusin', passiveObject);
246
+ const blurHandle = useEvent('focusout', passiveObject);
247
const focusVisibleHandles = useFocusVisibleInputHandles();
248
249
useEffect(() => {
@@ -334,8 +333,8 @@ export function useFocusWithin(
333
const stateRef = useRef<null | {isFocused: boolean, isFocusVisible: boolean}>(
334
{isFocused: false, isFocusVisible: false},
335
);
337
- const focusHandle = useEvent('focusin', passiveObjectWithPriority);
338
- const blurHandle = useEvent('focusout', passiveObjectWithPriority);
336
+ const focusHandle = useEvent('focusin', passiveObject);
337
+ const blurHandle = useEvent('focusout', passiveObject);
338
const afterBlurHandle = useEvent('afterblur', passiveObject);
339
const beforeBlurHandle = useEvent('beforeblur', passiveObject);
340
const focusVisibleHandles = useFocusVisibleInputHandles();
@@ -397,7 +396,7 @@ export function useFocusWithin(
396
if (disabled) {
397
return;
398
}
400
- const {relatedTarget} = (event.nativeEvent: any);
399
+ const {relatedTarget} = (event: any);
400
401
if (
402
state.isFocused &&