diff --git a/packages/react-art/src/ReactARTHostConfig.js b/packages/react-art/src/ReactARTHostConfig.js
index d1a4145ff3..7086dcccb5 100644
--- a/packages/react-art/src/ReactARTHostConfig.js
+++ b/packages/react-art/src/ReactARTHostConfig.js
@@ -500,3 +500,7 @@ export function beforeActiveInstanceBlur() {
export function afterActiveInstanceBlur() {
// noop
}
+
+export function preparePortalMount(portalInstance: any): void {
+ // noop
+}
diff --git a/packages/react-dom/src/client/ReactDOMHostConfig.js b/packages/react-dom/src/client/ReactDOMHostConfig.js
index 15a317b1f6..dd7ac44686 100644
--- a/packages/react-dom/src/client/ReactDOMHostConfig.js
+++ b/packages/react-dom/src/client/ReactDOMHostConfig.js
@@ -64,8 +64,10 @@ import {
enableSuspenseServerRenderer,
enableDeprecatedFlareAPI,
enableFundamentalAPI,
+ enableModernEventSystem,
} from 'shared/ReactFeatureFlags';
import {TOP_BEFORE_BLUR, TOP_AFTER_BLUR} from '../events/DOMTopLevelEventTypes';
+import {listenToEvent} from '../events/DOMModernPluginEventSystem';
export type Type = string;
export type Props = {
@@ -1098,3 +1100,9 @@ export function makeOpaqueHydratingObject(
valueOf: attemptToReadValue,
};
}
+
+export function preparePortalMount(portalInstance: Instance): void {
+ if (enableModernEventSystem) {
+ listenToEvent('onMouseEnter', portalInstance);
+ }
+}
diff --git a/packages/react-dom/src/events/plugins/__tests__/ModernEnterLeaveEventPlugin-test.internal.js b/packages/react-dom/src/events/plugins/__tests__/ModernEnterLeaveEventPlugin-test.internal.js
index beda6392b1..81809ff7fa 100644
--- a/packages/react-dom/src/events/plugins/__tests__/ModernEnterLeaveEventPlugin-test.internal.js
+++ b/packages/react-dom/src/events/plugins/__tests__/ModernEnterLeaveEventPlugin-test.internal.js
@@ -239,4 +239,30 @@ describe('EnterLeaveEventPlugin', () => {
ReactDOM.render(, container);
});
+
+ it('should work with portals outside of the root', () => {
+ const divRef = React.createRef();
+ const onMouseLeave = jest.fn();
+
+ function Component() {
+ return (
+
+ {ReactDOM.createPortal(
, document.body)}
+
+ );
+ }
+
+ ReactDOM.render(, container);
+
+ // Leave from the portal div
+ divRef.current.dispatchEvent(
+ new MouseEvent('mouseout', {
+ bubbles: true,
+ cancelable: true,
+ relatedTarget: document.body,
+ }),
+ );
+
+ expect(onMouseLeave).toHaveBeenCalledTimes(1);
+ });
});
diff --git a/packages/react-native-renderer/src/ReactFabricHostConfig.js b/packages/react-native-renderer/src/ReactFabricHostConfig.js
index 714c81d7dd..fa3b25103f 100644
--- a/packages/react-native-renderer/src/ReactFabricHostConfig.js
+++ b/packages/react-native-renderer/src/ReactFabricHostConfig.js
@@ -514,3 +514,7 @@ export function beforeActiveInstanceBlur() {
export function afterActiveInstanceBlur() {
// noop
}
+
+export function preparePortalMount(portalInstance: Instance): void {
+ // noop
+}
diff --git a/packages/react-native-renderer/src/ReactNativeHostConfig.js b/packages/react-native-renderer/src/ReactNativeHostConfig.js
index 8acf509953..fd9b08cf4b 100644
--- a/packages/react-native-renderer/src/ReactNativeHostConfig.js
+++ b/packages/react-native-renderer/src/ReactNativeHostConfig.js
@@ -562,3 +562,7 @@ export function beforeActiveInstanceBlur() {
export function afterActiveInstanceBlur() {
// noop
}
+
+export function preparePortalMount(portalInstance: Instance): void {
+ // noop
+}
diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js
index 7d26eb47bb..d8b5b72806 100644
--- a/packages/react-noop-renderer/src/createReactNoop.js
+++ b/packages/react-noop-renderer/src/createReactNoop.js
@@ -449,6 +449,10 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
afterActiveInstanceBlur() {
// NO-OP
},
+
+ preparePortalMount() {
+ // NO-OP
+ },
};
const hostConfig = useMutation
diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js
index d364336ec8..87f57ff58b 100644
--- a/packages/react-reconciler/src/ReactFiberCompleteWork.new.js
+++ b/packages/react-reconciler/src/ReactFiberCompleteWork.new.js
@@ -82,6 +82,7 @@ import {
mountFundamentalComponent,
cloneFundamentalInstance,
shouldUpdateFundamentalComponent,
+ preparePortalMount,
} from './ReactFiberHostConfig';
import {
getRootHostContainer,
@@ -973,6 +974,9 @@ function completeWork(
case HostPortal:
popHostContainer(workInProgress);
updateHostContainer(workInProgress);
+ if (current === null) {
+ preparePortalMount(workInProgress.stateNode.containerInfo);
+ }
return null;
case ContextProvider:
// Pop provider fiber
diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js
index efe0ef63ff..ee12b05bfd 100644
--- a/packages/react-reconciler/src/ReactFiberCompleteWork.old.js
+++ b/packages/react-reconciler/src/ReactFiberCompleteWork.old.js
@@ -82,6 +82,7 @@ import {
mountFundamentalComponent,
cloneFundamentalInstance,
shouldUpdateFundamentalComponent,
+ preparePortalMount,
} from './ReactFiberHostConfig';
import {
getRootHostContainer,
@@ -973,6 +974,9 @@ function completeWork(
case HostPortal:
popHostContainer(workInProgress);
updateHostContainer(workInProgress);
+ if (current === null) {
+ preparePortalMount(workInProgress.stateNode.containerInfo);
+ }
return null;
case ContextProvider:
// Pop provider fiber
diff --git a/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js b/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js
index 3d2544b5b2..6bc1c38daf 100644
--- a/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js
+++ b/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js
@@ -83,6 +83,7 @@ export const makeClientIdInDEV = $$$hostConfig.makeClientIdInDEV;
export const makeServerId = $$$hostConfig.makeServerId;
export const beforeActiveInstanceBlur = $$$hostConfig.beforeActiveInstanceBlur;
export const afterActiveInstanceBlur = $$$hostConfig.afterActiveInstanceBlur;
+export const preparePortalMount = $$$hostConfig.preparePortalMount;
// -------------------
// Mutation
diff --git a/packages/react-test-renderer/src/ReactTestHostConfig.js b/packages/react-test-renderer/src/ReactTestHostConfig.js
index bcf31c04b0..296bdcf56d 100644
--- a/packages/react-test-renderer/src/ReactTestHostConfig.js
+++ b/packages/react-test-renderer/src/ReactTestHostConfig.js
@@ -435,3 +435,7 @@ export function beforeActiveInstanceBlur() {
export function afterActiveInstanceBlur() {
// noop
}
+
+export function preparePortalMount(portalInstance: Instance): void {
+ // noop
+}