diff --git a/packages/react-dom/src/client/ReactDOMHostConfig.js b/packages/react-dom/src/client/ReactDOMHostConfig.js
index e0ee9f4e1d..5cda5e1ec4 100644
--- a/packages/react-dom/src/client/ReactDOMHostConfig.js
+++ b/packages/react-dom/src/client/ReactDOMHostConfig.js
@@ -452,7 +452,11 @@ export function insertInContainerBefore(
}
}
-function handleSimulateChildBlur(
+// This is a specific event for the React Flare
+// event system, so event responders can act
+// accordingly to a DOM node being unmounted that
+// previously had active document focus.
+function dispatchDetachedVisibleNodeEvent(
child: Instance | TextInstance | SuspenseInstance,
): void {
if (
@@ -463,13 +467,11 @@ function handleSimulateChildBlur(
const targetFiber = getClosestInstanceFromNode(child);
// Simlulate a blur event to the React Flare responder system.
dispatchEventForResponderEventSystem(
- 'blur',
+ 'detachedvisiblenode',
targetFiber,
({
- relatedTarget: null,
target: child,
timeStamp: Date.now(),
- type: 'blur',
}: any),
((child: any): Document | Element),
RESPONDER_EVENT_SYSTEM | IS_PASSIVE,
@@ -481,7 +483,7 @@ export function removeChild(
parentInstance: Instance,
child: Instance | TextInstance | SuspenseInstance,
): void {
- handleSimulateChildBlur(child);
+ dispatchDetachedVisibleNodeEvent(child);
parentInstance.removeChild(child);
}
@@ -492,7 +494,7 @@ export function removeChildFromContainer(
if (container.nodeType === COMMENT_NODE) {
(container.parentNode: any).removeChild(child);
} else {
- handleSimulateChildBlur(child);
+ dispatchDetachedVisibleNodeEvent(child);
container.removeChild(child);
}
}
diff --git a/packages/react-interactions/events/src/dom/Focus.js b/packages/react-interactions/events/src/dom/Focus.js
index 5084ce1e5f..1c7d261fc4 100644
--- a/packages/react-interactions/events/src/dom/Focus.js
+++ b/packages/react-interactions/events/src/dom/Focus.js
@@ -45,7 +45,12 @@ type FocusProps = {
onFocusVisibleChange: boolean => void,
};
-type FocusEventType = 'focus' | 'blur' | 'focuschange' | 'focusvisiblechange';
+type FocusEventType =
+ | 'focus'
+ | 'blur'
+ | 'focuschange'
+ | 'focusvisiblechange'
+ | 'detachedvisiblenode';
type FocusWithinProps = {
disabled?: boolean,
@@ -53,13 +58,15 @@ type FocusWithinProps = {
onBlurWithin?: (e: FocusEvent) => void,
onFocusWithinChange?: boolean => void,
onFocusWithinVisibleChange?: boolean => void,
+ onDetachedVisibleNode?: (e: FocusEvent) => void,
};
type FocusWithinEventType =
| 'focuswithinvisiblechange'
| 'focuswithinchange'
| 'blurwithin'
- | 'focuswithin';
+ | 'focuswithin'
+ | 'detachedvisiblenode';
/**
* Shared between Focus and FocusWithin
@@ -72,7 +79,7 @@ const isMac =
? /^Mac/.test(window.navigator.platform)
: false;
-const targetEventTypes = ['focus', 'blur'];
+const targetEventTypes = ['focus', 'blur', 'detachedvisiblenode'];
const hasPointerEvents =
typeof window !== 'undefined' && window.PointerEvent != null;
@@ -507,6 +514,22 @@ const focusWithinResponderImpl = {
}
break;
}
+ case 'detachedvisiblenode': {
+ const onDetachedVisibleNode = (props.onDetachedVisibleNode: any);
+ if (isFunction(onDetachedVisibleNode)) {
+ const syntheticEvent = createFocusEvent(
+ context,
+ 'detachedvisiblenode',
+ event.target,
+ state.pointerType,
+ );
+ context.dispatchEvent(
+ syntheticEvent,
+ onDetachedVisibleNode,
+ DiscreteEvent,
+ );
+ }
+ }
}
},
onRootEvent(
diff --git a/packages/react-interactions/events/src/dom/__tests__/FocusWithin-test.internal.js b/packages/react-interactions/events/src/dom/__tests__/FocusWithin-test.internal.js
index 12a2d7f03a..d90a95b841 100644
--- a/packages/react-interactions/events/src/dom/__tests__/FocusWithin-test.internal.js
+++ b/packages/react-interactions/events/src/dom/__tests__/FocusWithin-test.internal.js
@@ -141,16 +141,6 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
expect(onFocusWithinChange).toHaveBeenCalledTimes(2);
expect(onFocusWithinChange).toHaveBeenCalledWith(false);
});
-
- it('is called after a focused element is unmounted', () => {
- const target = createEventTarget(innerRef.current);
- target.focus();
- expect(onFocusWithinChange).toHaveBeenCalledTimes(1);
- expect(onFocusWithinChange).toHaveBeenCalledWith(true);
- ReactDOM.render(