diff --git a/packages/react-dom/src/client/ReactDOMHostConfig.js b/packages/react-dom/src/client/ReactDOMHostConfig.js
index 2e9224817acb9..e0ee9f4e1da1d 100644
--- a/packages/react-dom/src/client/ReactDOMHostConfig.js
+++ b/packages/react-dom/src/client/ReactDOMHostConfig.js
@@ -55,6 +55,7 @@ import {
addRootEventTypesForResponderInstance,
mountEventResponder,
unmountEventResponder,
+ dispatchEventForResponderEventSystem,
} from '../events/DOMEventResponderSystem';
import {retryIfBlockedOn} from '../events/ReactDOMEventReplaying';
@@ -108,6 +109,10 @@ import {
enableFlareAPI,
enableFundamentalAPI,
} from 'shared/ReactFeatureFlags';
+import {
+ RESPONDER_EVENT_SYSTEM,
+ IS_PASSIVE,
+} from 'legacy-events/EventSystemFlags';
let SUPPRESS_HYDRATION_WARNING;
if (__DEV__) {
@@ -447,10 +452,36 @@ export function insertInContainerBefore(
}
}
+function handleSimulateChildBlur(
+ child: Instance | TextInstance | SuspenseInstance,
+): void {
+ if (
+ enableFlareAPI &&
+ selectionInformation &&
+ child === selectionInformation.focusedElem
+ ) {
+ const targetFiber = getClosestInstanceFromNode(child);
+ // Simlulate a blur event to the React Flare responder system.
+ dispatchEventForResponderEventSystem(
+ 'blur',
+ targetFiber,
+ ({
+ relatedTarget: null,
+ target: child,
+ timeStamp: Date.now(),
+ type: 'blur',
+ }: any),
+ ((child: any): Document | Element),
+ RESPONDER_EVENT_SYSTEM | IS_PASSIVE,
+ );
+ }
+}
+
export function removeChild(
parentInstance: Instance,
child: Instance | TextInstance | SuspenseInstance,
): void {
+ handleSimulateChildBlur(child);
parentInstance.removeChild(child);
}
@@ -461,6 +492,7 @@ export function removeChildFromContainer(
if (container.nodeType === COMMENT_NODE) {
(container.parentNode: any).removeChild(child);
} else {
+ handleSimulateChildBlur(child);
container.removeChild(child);
}
}
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 d9808ce691a6e..12a2d7f03aec4 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
@@ -77,23 +77,24 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
describe('onFocusWithinChange', () => {
let onFocusWithinChange, ref, innerRef, innerRef2;
+ const Component = ({show}) => {
+ const listener = useFocusWithin({
+ onFocusWithinChange,
+ });
+ return (
+
+ );
+ };
+
beforeEach(() => {
onFocusWithinChange = jest.fn();
ref = React.createRef();
innerRef = React.createRef();
innerRef2 = React.createRef();
- const Component = () => {
- const listener = useFocusWithin({
- onFocusWithinChange,
- });
- return (
-
- );
- };
- ReactDOM.render(, container);
+ ReactDOM.render(, container);
});
it('is called after "blur" and "focus" events on focus target', () => {
@@ -140,28 +141,39 @@ 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(, container);
+ expect(onFocusWithinChange).toHaveBeenCalledTimes(2);
+ expect(onFocusWithinChange).toHaveBeenCalledWith(false);
+ });
});
describe('onFocusWithinVisibleChange', () => {
let onFocusWithinVisibleChange, ref, innerRef, innerRef2;
+ const Component = ({show}) => {
+ const listener = useFocusWithin({
+ onFocusWithinVisibleChange,
+ });
+ return (
+
+ );
+ };
+
beforeEach(() => {
onFocusWithinVisibleChange = jest.fn();
ref = React.createRef();
innerRef = React.createRef();
innerRef2 = React.createRef();
- const Component = () => {
- const listener = useFocusWithin({
- onFocusWithinVisibleChange,
- });
- return (
-
- );
- };
- ReactDOM.render(, container);
+ ReactDOM.render(, container);
});
it('is called after "focus" and "blur" on focus target if keyboard was used', () => {
@@ -258,6 +270,18 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
});
+
+ it('is called after a focused element is unmounted', () => {
+ const inner = innerRef.current;
+ const target = createEventTarget(inner);
+ target.keydown({key: 'Tab'});
+ target.focus();
+ expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1);
+ expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true);
+ ReactDOM.render(, container);
+ expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2);
+ expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false);
+ });
});
it('expect displayName to show up for event component', () => {
diff --git a/packages/react-interactions/events/src/dom/testing-library/index.js b/packages/react-interactions/events/src/dom/testing-library/index.js
index d46f96a5058ce..f740a31040aa6 100644
--- a/packages/react-interactions/events/src/dom/testing-library/index.js
+++ b/packages/react-interactions/events/src/dom/testing-library/index.js
@@ -34,6 +34,7 @@ const createEventTarget = node => ({
},
focus(payload) {
node.dispatchEvent(domEvents.focus(payload));
+ node.focus();
},
scroll(payload) {
node.dispatchEvent(domEvents.scroll(payload));