diff --git a/packages/main/cypress/specs/Popover.cy.tsx b/packages/main/cypress/specs/Popover.cy.tsx
index 9c197a00f026..59920f1f0f43 100644
--- a/packages/main/cypress/specs/Popover.cy.tsx
+++ b/packages/main/cypress/specs/Popover.cy.tsx
@@ -7,6 +7,7 @@ import Label from "../../src/Label.js";
import List from "../../src/List.js";
import ListItem from "../../src/ListItemStandard.js";
import Input from "../../src/Input.js";
+import Dialog from "../../src/Dialog.js";
describe("Rendering", () => {
it("tests arrow positioning", () => {
@@ -1810,3 +1811,88 @@ describe("Responsive paddings", () => {
cy.get("[ui5-popover]").should("have.attr", "media-range", "M");
});
});
+
+describe("Opener visibility in scrollable containers", () => {
+ it("should close popover when opener scrolls out of view in scrollable container", () => {
+ cy.mount(
+
+ );
+
+ cy.get("[ui5-popover]").ui5PopoverOpened();
+
+ cy.get("#scrollContainer").scrollTo(0, 200);
+
+ cy.get("[ui5-popover]").should("have.prop", "open", false);
+ });
+
+ it("should close popover when opener scrolls out in Dialog scenario", () => {
+ cy.mount(
+
+ );
+
+ cy.get("[ui5-popover]").ui5PopoverOpened();
+
+ cy.get("#dialogScrollContainer").scrollTo(0, 500);
+
+ cy.get("[ui5-popover]").should("have.prop", "open", false);
+ });
+
+ it("should work with nested scrollable containers", () => {
+ cy.mount(
+
+ );
+
+ cy.get("[ui5-popover]").ui5PopoverOpened();
+
+ cy.get("#innerScroll").scrollTo(0, 300);
+
+ cy.get("[ui5-popover]").should("have.prop", "open", false);
+ });
+
+ it("should handle horizontal scrolling", () => {
+ cy.mount(
+
+ );
+
+ cy.get("[ui5-popover]").ui5PopoverOpened();
+
+ cy.get("#horizontalScroll").scrollTo(500, 0);
+
+ cy.get("[ui5-popover]").should("have.prop", "open", false);
+ });
+});
diff --git a/packages/main/src/Popover.ts b/packages/main/src/Popover.ts
index 37d3379583d4..5bc66cc7c2fe 100644
--- a/packages/main/src/Popover.ts
+++ b/packages/main/src/Popover.ts
@@ -227,6 +227,7 @@ class Popover extends Popup {
_oldPlacement?: CalculatedPlacement;
_width?: string;
_height?: string;
+ _openerIntersectionObserver?: IntersectionObserver | null;
_popoverResize: PopoverResize;
@@ -290,11 +291,14 @@ class Popover extends Popup {
this._initialHeight = this.style.height;
this._openerRect = opener.getBoundingClientRect();
+ this._observeOpenerVisibility();
await super.openPopup();
}
closePopup(escPressed = false, preventRegistryUpdate = false, preventFocusRestore = false) : void {
+ this._unobserveOpenerVisibility();
+
Object.assign(this.style, {
width: this._initialWidth,
height: this._initialHeight,
@@ -363,6 +367,10 @@ class Popover extends Popup {
let rootNode = this.getRootNode();
+ if (!rootNode) {
+ return null;
+ }
+
if (rootNode === this) {
rootNode = document;
}
@@ -550,6 +558,49 @@ class Popover extends Popup {
return top + (Number.parseInt(this.style.top || "0") - actualTop);
}
+ /**
+ * Callback invoked when the opener element's intersection changes.
+ * Closes popover when opener is out of view.
+ * @private
+ */
+ _onOpenerIntersection(entries: Array): void {
+ if (this.open && !entries[0]?.isIntersecting) {
+ this.closePopup();
+ }
+ }
+
+ /**
+ * Starts observing the opener element's visibility in the viewport.
+ * @private
+ */
+ _observeOpenerVisibility(): void {
+ this._unobserveOpenerVisibility();
+
+ const opener = this.getOpenerHTMLElement(this.opener);
+
+ if (!opener) {
+ return;
+ }
+
+ this._openerIntersectionObserver = new IntersectionObserver(
+ this._onOpenerIntersection.bind(this),
+ { threshold: 0 },
+ );
+
+ this._openerIntersectionObserver.observe(opener);
+ }
+
+ /**
+ * Stops observing and cleans up the IntersectionObserver.
+ * @private
+ */
+ _unobserveOpenerVisibility(): void {
+ if (this._openerIntersectionObserver) {
+ this._openerIntersectionObserver.disconnect();
+ this._openerIntersectionObserver = null;
+ }
+ }
+
getPopoverSize(calcScrollHeight: boolean = false): PopoverSize {
const rect = this.getBoundingClientRect();
const width = rect.width;
diff --git a/packages/main/test/pages/Popover.html b/packages/main/test/pages/Popover.html
index dbf1b8fec06c..1719e3d661a0 100644
--- a/packages/main/test/pages/Popover.html
+++ b/packages/main/test/pages/Popover.html
@@ -228,6 +228,21 @@
Close
+
+
+
+ Open popover and scroll
+
+ LargeHeader
+
+ Close
+
@@ -747,6 +762,15 @@ Popover in ShadowRoot, Opener set as ID in window.document
document.getElementById("big-popover").open = true;
});
+ document.getElementById("closePopoverAfterScroll").addEventListener("click", function (event) {
+ scrollableDialog.opener = event.target;
+ scrollableDialog.open = true;
+ });
+
+ document.getElementById("closeAfterScrollBtn").addEventListener("click", function (event) {
+ scrollableDialog.open = false;
+ });
+
document.getElementById("acc-role-popover-button").addEventListener("click", function (event) {
document.getElementById("acc-role-popover").opener = event.target;
document.getElementById("acc-role-popover").open = true;