diff --git a/packages/block-editor/src/components/block-tools/index.js b/packages/block-editor/src/components/block-tools/index.js
index 6f02c04be6b323..f19bb532e9d725 100644
--- a/packages/block-editor/src/components/block-tools/index.js
+++ b/packages/block-editor/src/components/block-tools/index.js
@@ -8,9 +8,12 @@ import clsx from 'clsx';
*/
import { useSelect, useDispatch } from '@wordpress/data';
import { isTextField } from '@wordpress/dom';
-import { Popover } from '@wordpress/components';
+import {
+ Popover,
+ privateApis as componentsPrivateApis,
+} from '@wordpress/components';
import { __unstableUseShortcutEventMatch as useShortcutEventMatch } from '@wordpress/keyboard-shortcuts';
-import { useRef, useState } from '@wordpress/element';
+import { useRef, useState, createPortal } from '@wordpress/element';
import {
switchToBlockType,
hasBlockSupport,
@@ -33,6 +36,10 @@ import usePopoverScroll from '../block-popover/use-popover-scroll';
import ZoomOutModeInserters from './zoom-out-mode-inserters';
import { useShowBlockTools } from './use-show-block-tools';
import { unlock } from '../../lock-unlock';
+
+const { __experimentalGetOverlayLegacySlot: getOverlayLegacySlot } = unlock(
+ componentsPrivateApis
+);
import usePasteStyles from '../use-paste-styles';
import { BlockRenameModal, useBlockRename } from '../block-rename';
import { BlockVisibilityModal } from '../block-visibility';
@@ -322,18 +329,24 @@ export default function BlockTools( {
) }
{ /* Used for the inline rich text toolbar. Until this toolbar is combined into BlockToolbar, someone implementing their own BlockToolbar will also need to use this to see the image caption toolbar. */ }
- { ! isZoomOutMode && ! hasFixedToolbar && (
-
- ) }
+ { ! isZoomOutMode &&
+ ! hasFixedToolbar &&
+ createPortal(
+ ,
+ getOverlayLegacySlot()
+ ) }
{ children }
{ /* Used for inline rich text popovers. */ }
-
+ { createPortal(
+ ,
+ getOverlayLegacySlot()
+ ) }
{ isZoomOutMode && ! isDragging && (
{
- let container = document.body.querySelector(
+ const legacySlot = getOverlayLegacySlot();
+ let container = legacySlot.querySelector(
'.' + fallbackContainerClassname
);
if ( ! container ) {
container = document.createElement( 'div' );
container.className = fallbackContainerClassname;
- document.body.append( container );
+ legacySlot.append( container );
}
return container;
diff --git a/packages/components/src/private-apis.ts b/packages/components/src/private-apis.ts
index 43d85db9365f7a..d60339bfa7e641 100644
--- a/packages/components/src/private-apis.ts
+++ b/packages/components/src/private-apis.ts
@@ -11,6 +11,7 @@ import { Menu } from './menu';
import { ComponentsContext } from './context/context-system-provider';
import Theme from './theme';
import { Tabs } from './tabs';
+import { getOverlayLegacySlot } from './utils/overlay-legacy-slot';
import { kebabCase, normalizeTextString } from './utils/strings';
import { withIgnoreIMEEvents } from './utils/with-ignore-ime-events';
import { lock } from './lock-unlock';
@@ -34,6 +35,7 @@ import { ValidatedFormTokenField } from './validated-form-controls/components/fo
export const privateApis = {};
lock( privateApis, {
__experimentalPopoverLegacyPositionToPlacement,
+ __experimentalGetOverlayLegacySlot: getOverlayLegacySlot,
ComponentsContext,
Tabs,
Theme,
diff --git a/packages/customize-widgets/src/components/customize-widgets/index.js b/packages/customize-widgets/src/components/customize-widgets/index.js
index d206108398283a..0d503bd1466d2c 100644
--- a/packages/customize-widgets/src/components/customize-widgets/index.js
+++ b/packages/customize-widgets/src/components/customize-widgets/index.js
@@ -2,7 +2,11 @@
* WordPress dependencies
*/
import { useState, useEffect, useRef, createPortal } from '@wordpress/element';
-import { SlotFillProvider, Popover } from '@wordpress/components';
+import {
+ SlotFillProvider,
+ Popover,
+ privateApis as componentsPrivateApis,
+} from '@wordpress/components';
/**
* Internal dependencies
@@ -12,6 +16,11 @@ import SidebarBlockEditor from '../sidebar-block-editor';
import FocusControl from '../focus-control';
import SidebarControls from '../sidebar-controls';
import useClearSelectedBlock from './use-clear-selected-block';
+import { unlock } from '../../lock-unlock';
+
+const { __experimentalGetOverlayLegacySlot: getOverlayLegacySlot } = unlock(
+ componentsPrivateApis
+);
export default function CustomizeWidgets( {
api,
@@ -19,9 +28,6 @@ export default function CustomizeWidgets( {
blockEditorSettings,
} ) {
const [ activeSidebarControl, setActiveSidebarControl ] = useState( null );
- const parentContainer = document.getElementById(
- 'customize-theme-controls'
- );
const popoverRef = useRef();
useClearSelectedBlock( activeSidebarControl, popoverRef );
@@ -55,16 +61,15 @@ export default function CustomizeWidgets( {
activeSidebarControl.container[ 0 ]
);
- // We have to portal this to the parent of both the editor and the inspector,
- // so that the popovers will appear above both of them.
- const popover =
- parentContainer &&
- createPortal(
-
-
-
,
- parentContainer
- );
+ // Portal this into the overlay legacy slot so popovers appear above both
+ // the editor and the inspector. The slot's stacking context (above the
+ // customizer panes, below the WP admin bar) handles the layering.
+ const popover = createPortal(
+
+
+
,
+ getOverlayLegacySlot()
+ );
return (
diff --git a/packages/edit-widgets/src/components/header/index.js b/packages/edit-widgets/src/components/header/index.js
index c3d26d7f17b388..af11b6372e2862 100644
--- a/packages/edit-widgets/src/components/header/index.js
+++ b/packages/edit-widgets/src/components/header/index.js
@@ -3,9 +3,12 @@
*/
import { BlockToolbar } from '@wordpress/block-editor';
import { useSelect } from '@wordpress/data';
-import { useRef } from '@wordpress/element';
+import { createPortal, useRef } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
-import { Popover } from '@wordpress/components';
+import {
+ Popover,
+ privateApis as componentsPrivateApis,
+} from '@wordpress/components';
import { PinnedItems } from '@wordpress/interface';
import { useViewportMatch } from '@wordpress/compose';
import { store as preferencesStore } from '@wordpress/preferences';
@@ -17,6 +20,11 @@ import { VisuallyHidden } from '@wordpress/ui';
import DocumentTools from './document-tools';
import SaveButton from '../save-button';
import MoreMenu from '../more-menu';
+import { unlock } from '../../lock-unlock';
+
+const { __experimentalGetOverlayLegacySlot: getOverlayLegacySlot } = unlock(
+ componentsPrivateApis
+);
function Header() {
const isLargeViewport = useViewportMatch( 'medium' );
@@ -54,10 +62,13 @@ function Header() {