Repository: facebook/react
Pull request: Revert "Revert focus event PRs"

We can revert these revert PRs as these were not the cause of the problems we had recently.

Changed files:
filename: packages/react-dom/src/client/ReactDOMComponent.js
patch: @@ -1355,10 +1355,6 @@ export function listenToEventResponderEventTypes(
       const targetEventType = isPassive
         ? eventType
         : eventType.substring(0, eventType.length - 7);
-      // We don't listen to this as we actually emulate it in the host config
-      if (targetEventType === 'beforeblur') {
-        continue;
-      }
       if (!listenerMap.has(eventKey)) {
         if (isPassive) {
           const activeKey = targetEventType + '_active';

filename: packages/react-dom/src/client/ReactDOMHostConfig.js
patch: @@ -62,7 +62,6 @@ import {REACT_OPAQUE_ID_TYPE} from 'shared/ReactSymbols';
 import {
   mountEventResponder,
   unmountEventResponder,
-  DEPRECATED_dispatchEventForResponderEventSystem,
 } from '../events/DeprecatedDOMEventResponderSystem';
 import {retryIfBlockedOn} from '../events/ReactDOMEventReplaying';
 
@@ -74,8 +73,6 @@ import {
   enableScopeAPI,
 } from 'shared/ReactFeatureFlags';
 import {
-  RESPONDER_EVENT_SYSTEM,
-  IS_PASSIVE,
   PLUGIN_EVENT_SYSTEM,
   USE_EVENT_SYSTEM,
 } from '../events/EventSystemFlags';
@@ -528,22 +525,9 @@ function createEvent(type: TopLevelType): Event {
 }
 
 function dispatchBeforeDetachedBlur(target: HTMLElement): void {
-  const targetInstance = getClosestInstanceFromNode(target);
   ((selectionInformation: any): SelectionInformation).activeElementDetached = target;
 
-  if (enableDeprecatedFlareAPI) {
-    DEPRECATED_dispatchEventForResponderEventSystem(
-      'beforeblur',
-      targetInstance,
-      ({
-        target,
-        timeStamp: Date.now(),
-      }: any),
-      target,
-      RESPONDER_EVENT_SYSTEM | IS_PASSIVE,
-    );
-  }
-  if (enableUseEventAPI) {
+  if (enableDeprecatedFlareAPI || enableUseEventAPI) {
     const event = createEvent(TOP_BEFORE_BLUR);
     // Dispatch "beforeblur" directly on the target,
     // so it gets picked up by the event system and
@@ -553,20 +537,7 @@ function dispatchBeforeDetachedBlur(target: HTMLElement): void {
 }
 
 function dispatchAfterDetachedBlur(target: HTMLElement): void {
-  if (enableDeprecatedFlareAPI) {
-    DEPRECATED_dispatchEventForResponderEventSystem(
-      'blur',
-      null,
-      ({
-        isTargetAttached: false,
-        target,
-        timeStamp: Date.now(),
-      }: any),
-      target,
-      RESPONDER_EVENT_SYSTEM | IS_PASSIVE,
-    );
-  }
-  if (enableUseEventAPI) {
+  if (enableDeprecatedFlareAPI || enableUseEventAPI) {
     const event = createEvent(TOP_AFTER_BLUR);
     // So we know what was detached, make the relatedTarget the
     // detached target on the "afterblur" event.

filename: packages/react-interactions/events/src/dom/DeprecatedFocus.js
patch: @@ -22,7 +22,7 @@ import {DiscreteEvent} from 'shared/ReactTypes';
  */
 
 type FocusEvent = {|
-  isTargetAttached: boolean,
+  relatedTarget: null | Element | Document,
   target: Element | Document,
   type: FocusEventType | FocusWithinEventType,
   pointerType: PointerType,
@@ -53,6 +53,7 @@ type FocusEventType = 'focus' | 'blur' | 'focuschange' | 'focusvisiblechange';
 type FocusWithinProps = {
   disabled?: boolean,
   onFocusWithin?: (e: FocusEvent) => void,
+  onAfterBlurWithin?: (e: FocusEvent) => void,
   onBeforeBlurWithin?: (e: FocusEvent) => void,
   onBlurWithin?: (e: FocusEvent) => void,
   onFocusWithinChange?: boolean => void,
@@ -65,7 +66,8 @@ type FocusWithinEventType =
   | 'focuswithinchange'
   | 'blurwithin'
   | 'focuswithin'
-  | 'beforeblurwithin';
+  | 'beforeblurwithin'
+  | 'afterblurwithin';
 
 /**
  * Shared between Focus and FocusWithin
@@ -116,8 +118,7 @@ const focusVisibleEvents = hasPointerEvents
 
 const targetEventTypes = ['focus', 'blur', 'beforeblur', ...focusVisibleEvents];
 
-// Used only for the blur "detachedTarget" logic
-const rootEventTypes = ['blur'];
+const rootEventTypes = ['afterblur'];
 
 function addWindowEventListener(types, callback, options) {
   types.forEach(type => {
@@ -192,10 +193,10 @@ function createFocusEvent(
   type: FocusEventType | FocusWithinEventType,
   target: Element | Document,
   pointerType: PointerType,
-  isTargetAttached: boolean,
+  relatedTarget: null | Element | Document,
 ): FocusEvent {
   return {
-    isTargetAttached,
+    relatedTarget,
     target,
     type,
     pointerType,
@@ -297,7 +298,7 @@ function dispatchFocusEvents(
       'focus',
       target,
       pointerType,
-      true,
+      null,
     );
     context.dispatchEvent(syntheticEvent, onFocus, DiscreteEvent);
   }
@@ -321,7 +322,7 @@ function dispatchBlurEvents(
       'blur',
       target,
       pointerType,
-      true,
+      null,
     );
     context.dispatchEvent(syntheticEvent, onBlur, DiscreteEvent);
   }
@@ -346,7 +347,7 @@ function dispatchFocusWithinEvents(
       'focuswithin',
       target,
       pointerType,
-      true,
+      null,
     );
     context.dispatchEvent(syntheticEvent, onFocusWithin, DiscreteEvent);
   }
@@ -361,19 +362,39 @@ function dispatchBlurWithinEvents(
   const pointerType = state.pointerType;
   const target = ((state.focusTarget: any): Element | Document) || event.target;
   const onBlurWithin = (props.onBlurWithin: any);
-  const isTargetAttached = state.detachedTarget === null;
   if (isFunction(onBlurWithin)) {
     const syntheticEvent = createFocusEvent(
       context,
       'blurwithin',
       target,
       pointerType,
-      isTargetAttached,
+      null,
     );
     context.dispatchEvent(syntheticEvent, onBlurWithin, DiscreteEvent);
   }
 }
 
+function dispatchAfterBlurWithinEvents(
+  context: ReactDOMResponderContext,
+  event: ReactDOMResponderEvent,
+  props: FocusWithinProps,
+  state: FocusState,
+) {
+  const pointerType = state.pointerType;
+  const onAfterBlurWithin = (props.onAfterBlurWithin: any);
+  const relatedTarget = state.detachedTarget;
+  if (isFunction(onAfterBlurWithin) && relatedTarget !== null) {
+    const syntheticEvent = createFocusEvent(
+      context,
+      'afterblurwithin',
+      relatedTarget,
+      pointerType,
+      relatedTarget,
+    );
+    context.dispatchEvent(syntheticEvent, onAfterBlurWithin, DiscreteEvent);
+  }
+}
+
 function dispatchFocusChange(
   context: ReactDOMResponderContext,
   props: FocusProps,
@@ -616,7 +637,7 @@ const focusWithinResponderImpl = {
             'beforeblurwithin',
             event.target,
             state.pointerType,
-            true,
+            null,
           );
           state.detachedTarget = event.target;
           context.dispatchEvent(
@@ -660,10 +681,13 @@ const focusWithinResponderImpl = {
     props: FocusWithinProps,
     state: FocusState,
   ): void {
-    if (event.type === 'blur') {
+    if (event.type === 'afterblur') {
       const detachedTarget = state.detachedTarget;
-      if (detachedTarget !== null && detachedTarget === event.target) {
-        dispatchBlurWithinEvents(context, event, props, state);
+      if (
+        detachedTarget !== null &&
+        detachedTarget === event.nativeEvent.relatedTarget
+      ) {
+        dispatchAfterBlurWithinEvents(context, event, props, state);
         state.detachedTarget = null;
         if (state.addedRootEvents) {
           state.addedRootEvents = false;

filename: packages/react-interactions/events/src/dom/__tests__/FocusWithin-test.internal.js
patch: @@ -290,11 +290,11 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
   });
 
   describe('onBeforeBlurWithin', () => {
-    let onBeforeBlurWithin, onBlurWithin, ref, innerRef, innerRef2;
+    let onBeforeBlurWithin, onAfterBlurWithin, ref, innerRef, innerRef2;
 
     beforeEach(() => {
       onBeforeBlurWithin = jest.fn();
-      onBlurWithin = jest.fn();
+      onAfterBlurWithin = jest.fn();
       ref = React.createRef();
       innerRef = React.createRef();
       innerRef2 = React.createRef();
@@ -305,7 +305,7 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
       const Component = ({show}) => {
         const listener = useFocusWithin({
           onBeforeBlurWithin,
-          onBlurWithin,
+          onAfterBlurWithin,
         });
         return (
           <div ref={ref} DEPRECATED_flareListeners={listener}>
@@ -322,12 +322,12 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
       target.keydown({key: 'Tab'});
       target.focus();
       expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
-      expect(onBlurWithin).toHaveBeenCalledTimes(0);
+      expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
       ReactDOM.render(<Component show={false} />, container);
       expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
-      expect(onBlurWithin).toHaveBeenCalledTimes(1);
-      expect(onBlurWithin).toHaveBeenCalledWith(
-        expect.objectContaining({isTargetAttached: false}),
+      expect(onAfterBlurWithin).toHaveBeenCalledTimes(1);
+      expect(onAfterBlurWithin).toHaveBeenCalledWith(
+        expect.objectContaining({relatedTarget: inner}),
       );
     });
 
@@ -336,7 +336,7 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
       const Component = ({show}) => {
         const listener = useFocusWithin({
           onBeforeBlurWithin,
-          onBlurWithin,
+          onAfterBlurWithin,
         });
         return (
           <div ref={ref} DEPRECATED_flareListeners={listener}>
@@ -357,12 +357,12 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
       target.keydown({key: 'Tab'});
       target.focus();
       expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
-      expect(onBlurWithin).toHaveBeenCalledTimes(0);
+      expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
       ReactDOM.render(<Component show={false} />, container);
       expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
-      expect(onBlurWithin).toHaveBeenCalledTimes(1);
-      expect(onBlurWithin).toHaveBeenCalledWith(
-        expect.objectContaining({isTargetAttached: false}),
+      expect(onAfterBlurWithin).toHaveBeenCalledTimes(1);
+      expect(onAfterBlurWithin).toHaveBeenCalledWith(
+        expect.objectContaining({relatedTarget: inner}),
       );
     });
 
@@ -418,7 +418,7 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
       const Component = ({show}) => {
         const listener = useFocusWithin({
           onBeforeBlurWithin,
-          onBlurWithin,
+          onAfterBlurWithin,
         });
 
         return (
@@ -444,7 +444,7 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
       target.keydown({key: 'Tab'});
       target.focus();
       expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
-      expect(onBlurWithin).toHaveBeenCalledTimes(0);
+      expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
 
       suspend = true;
       root.render(<Component />);
@@ -454,7 +454,7 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
         '<div><input style="display: none;">Loading...</div>',
       );
       expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
-      expect(onBlurWithin).toHaveBeenCalledTimes(1);
+      expect(onAfterBlurWithin).toHaveBeenCalledTimes(1);
       resolve();
 
       document.body.removeChild(container2);