Skip to content

Commit a196777

Browse files
committed
Modern Event System: use focusin/focusout for onFocus/onBlur
Fix test now we use capture phase Cleanup Address feedback Revise
1 parent 77e8722 commit a196777

11 files changed

+45
-38
lines changed

packages/dom-event-testing-library/domEvents.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,10 @@ export function blur({relatedTarget} = {}) {
234234
return new FocusEvent('blur', {relatedTarget});
235235
}
236236

237+
export function focusout({relatedTarget} = {}) {
238+
return new FocusEvent('focusout', {relatedTarget, bubbles: true});
239+
}
240+
237241
export function click(payload) {
238242
return createMouseEvent('click', {
239243
button: buttonType.primary,
@@ -259,6 +263,10 @@ export function focus({relatedTarget} = {}) {
259263
return new FocusEvent('focus', {relatedTarget});
260264
}
261265

266+
export function focusin({relatedTarget} = {}) {
267+
return new FocusEvent('focusin', {relatedTarget, bubbles: true});
268+
}
269+
262270
export function scroll() {
263271
return createEvent('scroll');
264272
}

packages/dom-event-testing-library/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@ const createEventTarget = node => ({
2222
*/
2323
blur(payload) {
2424
node.dispatchEvent(domEvents.blur(payload));
25+
node.dispatchEvent(domEvents.focusout(payload));
2526
},
2627
click(payload) {
2728
node.dispatchEvent(domEvents.click(payload));
2829
},
2930
focus(payload) {
3031
node.dispatchEvent(domEvents.focus(payload));
32+
node.dispatchEvent(domEvents.focusin(payload));
3133
node.focus();
3234
},
3335
keydown(payload) {

packages/react-dom/src/events/DOMEventProperties.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ const eventPriorities = new Map();
4141

4242
// prettier-ignore
4343
const discreteEventPairsForSimpleEventPlugin = [
44-
DOMTopLevelEventTypes.TOP_BLUR, 'blur',
4544
DOMTopLevelEventTypes.TOP_CANCEL, 'cancel',
4645
DOMTopLevelEventTypes.TOP_CLICK, 'click',
4746
DOMTopLevelEventTypes.TOP_CLOSE, 'close',
@@ -53,7 +52,8 @@ const discreteEventPairsForSimpleEventPlugin = [
5352
DOMTopLevelEventTypes.TOP_DRAG_END, 'dragEnd',
5453
DOMTopLevelEventTypes.TOP_DRAG_START, 'dragStart',
5554
DOMTopLevelEventTypes.TOP_DROP, 'drop',
56-
DOMTopLevelEventTypes.TOP_FOCUS, 'focus',
55+
DOMTopLevelEventTypes.TOP_FOCUS_IN, 'focus',
56+
DOMTopLevelEventTypes.TOP_FOCUS_OUT, 'blur',
5757
DOMTopLevelEventTypes.TOP_INPUT, 'input',
5858
DOMTopLevelEventTypes.TOP_INVALID, 'invalid',
5959
DOMTopLevelEventTypes.TOP_KEY_DOWN, 'keyDown',

packages/react-dom/src/events/DOMModernPluginEventSystem.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,10 @@ import {
4343

4444
import getEventTarget from './getEventTarget';
4545
import {
46-
TOP_FOCUS,
4746
TOP_LOAD,
4847
TOP_ABORT,
4948
TOP_CANCEL,
5049
TOP_INVALID,
51-
TOP_BLUR,
5250
TOP_SCROLL,
5351
TOP_CLOSE,
5452
TOP_RESET,
@@ -207,8 +205,6 @@ function extractEvents(
207205
}
208206

209207
export const capturePhaseEvents: Set<DOMTopLevelEventType> = new Set([
210-
TOP_FOCUS,
211-
TOP_BLUR,
212208
TOP_SCROLL,
213209
TOP_LOAD,
214210
TOP_ABORT,

packages/react-dom/src/events/DOMTopLevelEventTypes.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ export const TOP_ANIMATION_ITERATION = unsafeCastStringToDOMTopLevelType(
3232
export const TOP_ANIMATION_START = unsafeCastStringToDOMTopLevelType(
3333
getVendorPrefixedEventName('animationstart'),
3434
);
35-
export const TOP_BLUR = unsafeCastStringToDOMTopLevelType('blur');
3635
export const TOP_CAN_PLAY = unsafeCastStringToDOMTopLevelType('canplay');
3736
export const TOP_CAN_PLAY_THROUGH = unsafeCastStringToDOMTopLevelType(
3837
'canplaythrough',
@@ -72,7 +71,6 @@ export const TOP_EMPTIED = unsafeCastStringToDOMTopLevelType('emptied');
7271
export const TOP_ENCRYPTED = unsafeCastStringToDOMTopLevelType('encrypted');
7372
export const TOP_ENDED = unsafeCastStringToDOMTopLevelType('ended');
7473
export const TOP_ERROR = unsafeCastStringToDOMTopLevelType('error');
75-
export const TOP_FOCUS = unsafeCastStringToDOMTopLevelType('focus');
7674
export const TOP_GOT_POINTER_CAPTURE = unsafeCastStringToDOMTopLevelType(
7775
'gotpointercapture',
7876
);
@@ -152,6 +150,9 @@ export const TOP_WHEEL = unsafeCastStringToDOMTopLevelType('wheel');
152150
export const TOP_AFTER_BLUR = unsafeCastStringToDOMTopLevelType('afterblur');
153151
export const TOP_BEFORE_BLUR = unsafeCastStringToDOMTopLevelType('beforeblur');
154152

153+
export const TOP_FOCUS_IN = unsafeCastStringToDOMTopLevelType('focusin');
154+
export const TOP_FOCUS_OUT = unsafeCastStringToDOMTopLevelType('focusout');
155+
155156
// List of events that need to be individually attached to media elements.
156157
// Note that events in this list will *not* be listened to at the top level
157158
// unless they're explicitly listed in `ReactBrowserEventEmitter.listenTo`.

packages/react-dom/src/events/ReactDOMEventReplaying.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,8 @@ import {
131131
TOP_POINTER_OUT,
132132
TOP_GOT_POINTER_CAPTURE,
133133
TOP_LOST_POINTER_CAPTURE,
134-
TOP_FOCUS,
135-
TOP_BLUR,
134+
TOP_FOCUS_IN,
135+
TOP_FOCUS_OUT,
136136
} from './DOMTopLevelEventTypes';
137137
import {IS_REPLAYED, PLUGIN_EVENT_SYSTEM} from './EventSystemFlags';
138138
import {
@@ -216,10 +216,10 @@ const discreteReplayableEvents = [
216216
];
217217

218218
const continuousReplayableEvents = [
219-
TOP_FOCUS,
220-
TOP_BLUR,
221219
TOP_DRAG_ENTER,
222220
TOP_DRAG_LEAVE,
221+
TOP_FOCUS_IN,
222+
TOP_FOCUS_OUT,
223223
TOP_MOUSE_OVER,
224224
TOP_MOUSE_OUT,
225225
TOP_POINTER_OVER,
@@ -362,8 +362,8 @@ export function clearIfContinuousEvent(
362362
nativeEvent: AnyNativeEvent,
363363
): void {
364364
switch (topLevelType) {
365-
case TOP_FOCUS:
366-
case TOP_BLUR:
365+
case TOP_FOCUS_IN:
366+
case TOP_FOCUS_OUT:
367367
queuedFocus = null;
368368
break;
369369
case TOP_DRAG_ENTER:
@@ -443,7 +443,7 @@ export function queueIfContinuousEvent(
443443
// moved from outside the window (no target) onto the target once it hydrates.
444444
// Instead of mutating we could clone the event.
445445
switch (topLevelType) {
446-
case TOP_FOCUS: {
446+
case TOP_FOCUS_IN: {
447447
const focusEvent = ((nativeEvent: any): FocusEvent);
448448
queuedFocus = accumulateOrCreateContinuousQueuedReplayableEvent(
449449
queuedFocus,

packages/react-dom/src/events/__tests__/DOMModernPluginEventSystem-test.internal.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -855,9 +855,9 @@ describe('DOMModernPluginEventSystem', () => {
855855
expect(onFocus).toHaveBeenCalledTimes(3);
856856
expect(onFocusCapture).toHaveBeenCalledTimes(3);
857857
expect(log[2]).toEqual(['capture', buttonElement]);
858-
expect(log[3]).toEqual(['bubble', buttonElement]);
859-
expect(log[4]).toEqual(['capture', divElement]);
860-
expect(log[5]).toEqual(['bubble', divElement]);
858+
expect(log[3]).toEqual(['capture', divElement]);
859+
expect(log[4]).toEqual(['bubble', divElement]);
860+
expect(log[5]).toEqual(['bubble', buttonElement]);
861861
});
862862

863863
it('handle propagation of focus events between portals', () => {

packages/react-dom/src/events/plugins/ModernBeforeInputEventPlugin.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {canUseDOM} from 'shared/ExecutionEnvironment';
1111

1212
import {registerTwoPhaseEvent} from '../EventRegistry';
1313
import {
14-
TOP_BLUR,
14+
TOP_FOCUS_OUT,
1515
TOP_COMPOSITION_START,
1616
TOP_COMPOSITION_END,
1717
TOP_COMPOSITION_UPDATE,
@@ -66,24 +66,24 @@ function registerEvents() {
6666
TOP_PASTE,
6767
]);
6868
registerTwoPhaseEvent('onCompositionEnd', [
69-
TOP_BLUR,
7069
TOP_COMPOSITION_END,
70+
TOP_FOCUS_OUT,
7171
TOP_KEY_DOWN,
7272
TOP_KEY_PRESS,
7373
TOP_KEY_UP,
7474
TOP_MOUSE_DOWN,
7575
]);
7676
registerTwoPhaseEvent('onCompositionStart', [
77-
TOP_BLUR,
7877
TOP_COMPOSITION_START,
78+
TOP_FOCUS_OUT,
7979
TOP_KEY_DOWN,
8080
TOP_KEY_PRESS,
8181
TOP_KEY_UP,
8282
TOP_MOUSE_DOWN,
8383
]);
8484
registerTwoPhaseEvent('onCompositionUpdate', [
85-
TOP_BLUR,
8685
TOP_COMPOSITION_UPDATE,
86+
TOP_FOCUS_OUT,
8787
TOP_KEY_DOWN,
8888
TOP_KEY_PRESS,
8989
TOP_KEY_UP,
@@ -154,7 +154,7 @@ function isFallbackCompositionEnd(topLevelType, nativeEvent) {
154154
return nativeEvent.keyCode !== START_KEYCODE;
155155
case TOP_KEY_PRESS:
156156
case TOP_MOUSE_DOWN:
157-
case TOP_BLUR:
157+
case TOP_FOCUS_OUT:
158158
// Events are not possible without cancelling IME.
159159
return true;
160160
default:

packages/react-dom/src/events/plugins/ModernChangeEventPlugin.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ import isTextInputElement from '../isTextInputElement';
1717
import {canUseDOM} from 'shared/ExecutionEnvironment';
1818

1919
import {
20-
TOP_BLUR,
20+
TOP_FOCUS_OUT,
2121
TOP_CHANGE,
2222
TOP_CLICK,
23-
TOP_FOCUS,
23+
TOP_FOCUS_IN,
2424
TOP_INPUT,
2525
TOP_KEY_DOWN,
2626
TOP_KEY_UP,
@@ -42,10 +42,10 @@ import {
4242

4343
function registerEvents() {
4444
registerTwoPhaseEvent('onChange', [
45-
TOP_BLUR,
4645
TOP_CHANGE,
4746
TOP_CLICK,
48-
TOP_FOCUS,
47+
TOP_FOCUS_IN,
48+
TOP_FOCUS_OUT,
4949
TOP_INPUT,
5050
TOP_KEY_DOWN,
5151
TOP_KEY_UP,
@@ -172,7 +172,7 @@ function handlePropertyChange(nativeEvent) {
172172
}
173173

174174
function handleEventsForInputEventPolyfill(topLevelType, target, targetInst) {
175-
if (topLevelType === TOP_FOCUS) {
175+
if (topLevelType === TOP_FOCUS_IN) {
176176
// In IE9, propertychange fires for most input events but is buggy and
177177
// doesn't fire when text is deleted, but conveniently, selectionchange
178178
// appears to fire in all of the remaining cases so we catch those and
@@ -185,7 +185,7 @@ function handleEventsForInputEventPolyfill(topLevelType, target, targetInst) {
185185
// missed a blur event somehow.
186186
stopWatchingForValueChange();
187187
startWatchingForValueChange(target, targetInst);
188-
} else if (topLevelType === TOP_BLUR) {
188+
} else if (topLevelType === TOP_FOCUS_OUT) {
189189
stopWatchingForValueChange();
190190
}
191191
}
@@ -304,7 +304,7 @@ function extractEvents(
304304
}
305305

306306
// When blurring, set the value attribute for number inputs
307-
if (topLevelType === TOP_BLUR) {
307+
if (topLevelType === TOP_FOCUS_OUT) {
308308
handleControlledInputBlur(((targetNode: any): HTMLInputElement));
309309
}
310310
}

packages/react-dom/src/events/plugins/ModernSelectEventPlugin.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import shallowEqual from 'shared/shallowEqual';
1212

1313
import {registerTwoPhaseEvent} from '../EventRegistry';
1414
import {
15-
TOP_BLUR,
15+
TOP_FOCUS_OUT,
1616
TOP_CONTEXT_MENU,
1717
TOP_DRAG_END,
18-
TOP_FOCUS,
18+
TOP_FOCUS_IN,
1919
TOP_KEY_DOWN,
2020
TOP_KEY_UP,
2121
TOP_MOUSE_DOWN,
@@ -35,10 +35,10 @@ const skipSelectionChangeEvent =
3535
canUseDOM && 'documentMode' in document && document.documentMode <= 11;
3636

3737
const rootTargetDependencies = [
38-
TOP_BLUR,
38+
TOP_FOCUS_OUT,
3939
TOP_CONTEXT_MENU,
4040
TOP_DRAG_END,
41-
TOP_FOCUS,
41+
TOP_FOCUS_IN,
4242
TOP_KEY_DOWN,
4343
TOP_KEY_UP,
4444
TOP_MOUSE_DOWN,
@@ -186,7 +186,7 @@ function extractEvents(
186186

187187
switch (topLevelType) {
188188
// Track the input node that has focus.
189-
case TOP_FOCUS:
189+
case TOP_FOCUS_IN:
190190
if (
191191
isTextInputElement(targetNode) ||
192192
targetNode.contentEditable === 'true'
@@ -196,7 +196,7 @@ function extractEvents(
196196
lastSelection = null;
197197
}
198198
break;
199-
case TOP_BLUR:
199+
case TOP_FOCUS_OUT:
200200
activeElement = null;
201201
activeElementInst = null;
202202
lastSelection = null;

0 commit comments

Comments
 (0)