diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 248802890e46d6..beee58ba8dd240 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -17,6 +17,7 @@
- `Popover`: make sure offset middleware always applies the latest frame offset values ([#43329](https://github.com/WordPress/gutenberg/pull/43329/)).
- `Dropdown`: anchor popover to the dropdown wrapper (instead of the toggle) ([#43377](https://github.com/WordPress/gutenberg/pull/43377/)).
- `Guide`: Fix error when rendering with no pages ([#43380](https://github.com/WordPress/gutenberg/pull/43380/)).
+- `Popover`: Ensure position is correct when a nested popover's parent references an element in an iframe and the iframe is scrolled ([#43544](https://github.com/WordPress/gutenberg/pull/43544/)).
### Enhancements
diff --git a/packages/components/src/popover/index.js b/packages/components/src/popover/index.js
index c40804e1164e23..3b90de04928f40 100644
--- a/packages/components/src/popover/index.js
+++ b/packages/components/src/popover/index.js
@@ -48,6 +48,10 @@ import {
positionToPlacement,
placementToMotionAnimationProps,
} from './utils';
+import {
+ useRootReferenceDocument,
+ RootReferenceDocumentProvider,
+} from './root-reference-document-context';
/**
* Name of slot in which popover should fill.
@@ -430,74 +434,108 @@ const Popover = (
};
}, [ referenceOwnerDocument, update ] );
+ // If the root document is scrolled trigger an update of the popover
+ // position. This covers cases where a popover is a child of another
+ // popover, and the first popover in the chain references an element
+ // in an iframe.
+ const rootReferenceDocument = useRootReferenceDocument();
+ useLayoutEffect( () => {
+ // Return early if the root document is the same as the owner document,
+ // as the scroll event will be listened to in other code.
+ const isSameDocument = rootReferenceDocument === referenceOwnerDocument;
+
+ // Return early if this isn't an iframe document.
+ const isNotIframeDocument = rootReferenceDocument === document;
+
+ if (
+ ! rootReferenceDocument ||
+ isSameDocument ||
+ isNotIframeDocument
+ ) {
+ return;
+ }
+
+ rootReferenceDocument.addEventListener( 'scroll', update );
+ return () =>
+ rootReferenceDocument?.removeEventListener( 'scroll', update );
+ }, [ referenceOwnerDocument, rootReferenceDocument, update ] );
+
const mergedFloatingRef = useMergeRefs( [
floating,
dialogRef,
forwardedRef,
] );
- // Disable reason: We care to capture the _bubbled_ events from inputs
- // within popover as inferring close intent.
-
let content = (
- // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
- // eslint-disable-next-line jsx-a11y/no-static-element-interactions
-
- { /* Prevents scroll on the document */ }
- { isExpanded && }
- { isExpanded && (
-
-
- { headerTitle }
-
-
-
- ) }
-
{ children }
- { hasArrow && (
-
-
-
- ) }
-
+ {
+ // Disable reason: We care to capture the _bubbled_ events from inputs
+ // within popover as inferring close intent.
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions
+ }
+
+ { /* Prevents scroll on the document */ }
+ { isExpanded && }
+ { isExpanded && (
+