From 130ef3b47a3f530c60a5ff18624af98213b3975f Mon Sep 17 00:00:00 2001 From: Serhii Kulykov Date: Thu, 20 Feb 2025 15:01:25 +0200 Subject: [PATCH] fix: bring popover overlay to front when used in modeless dialog (#8692) --- .../src/vaadin-popover-overlay-mixin.js | 34 +++++++++++++++++++ packages/popover/test/nested.test.js | 19 +++++++++++ test/integration/dialog-popover.test.js | 29 +++++++++++++++- 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/packages/popover/src/vaadin-popover-overlay-mixin.js b/packages/popover/src/vaadin-popover-overlay-mixin.js index 226fd61dc43..a5ead7176bf 100644 --- a/packages/popover/src/vaadin-popover-overlay-mixin.js +++ b/packages/popover/src/vaadin-popover-overlay-mixin.js @@ -5,6 +5,26 @@ */ import { OverlayMixin } from '@vaadin/overlay/src/vaadin-overlay-mixin.js'; import { PositionMixin } from '@vaadin/overlay/src/vaadin-overlay-position-mixin.js'; +import { setNestedOverlay } from '@vaadin/overlay/src/vaadin-overlay-stack-mixin.js'; + +/** + * Returns the closest parent overlay for given node, if any. + * @param {HTMLElement} node + * @return {HTMLElement} + */ +const getClosestOverlay = (node) => { + let n = node; + + while (n && n !== node.ownerDocument) { + n = n.parentNode || n.host; + + if (n && n._hasOverlayStackMixin) { + return n; + } + } + + return null; +}; /** * A mixin providing common popover overlay functionality. @@ -24,6 +44,10 @@ export const PopoverOverlayMixin = (superClass) => }; } + static get observers() { + return ['__openedOrTargetChanged(opened, positionTarget)']; + } + /** * Tag name prefix used by custom properties. * @protected @@ -96,4 +120,14 @@ export const PopoverOverlayMixin = (superClass) => this.style.top = `${overlayRect.top + offset}px`; } } + + /** @private */ + __openedOrTargetChanged(opened, target) { + if (target) { + const parent = getClosestOverlay(target); + if (parent) { + setNestedOverlay(parent, opened ? this : null); + } + } + } }; diff --git a/packages/popover/test/nested.test.js b/packages/popover/test/nested.test.js index 7c383aa828e..f040144a8f0 100644 --- a/packages/popover/test/nested.test.js +++ b/packages/popover/test/nested.test.js @@ -1,5 +1,6 @@ import { expect } from '@vaadin/chai-plugins'; import { esc, fixtureSync, nextRender, nextUpdate, outsideClick } from '@vaadin/testing-helpers'; +import sinon from 'sinon'; import './not-animated-styles.js'; import { Popover } from '../vaadin-popover.js'; import { mouseenter, mouseleave } from './helpers.js'; @@ -167,4 +168,22 @@ describe('nested popover', () => { expect(popover.opened).to.be.true; }); }); + + describe('bring to front', () => { + beforeEach(async () => { + // Open the first popover + target.click(); + await nextRender(); + + // Open the second popover + secondTarget.click(); + await nextRender(); + }); + + it('should bring to front nested overlay on parent overlay bringToFront()', () => { + const spy = sinon.spy(secondPopover._overlayElement, 'bringToFront'); + popover._overlayElement.bringToFront(); + expect(spy).to.be.calledOnce; + }); + }); }); diff --git a/test/integration/dialog-popover.test.js b/test/integration/dialog-popover.test.js index 449829fcda8..b4ea9f5eaa6 100644 --- a/test/integration/dialog-popover.test.js +++ b/test/integration/dialog-popover.test.js @@ -1,6 +1,6 @@ import { expect } from '@vaadin/chai-plugins'; import { resetMouse, sendKeys, sendMouse } from '@vaadin/test-runner-commands'; -import { fixtureSync, nextFrame, nextRender, nextUpdate } from '@vaadin/testing-helpers'; +import { fixtureSync, mousedown, nextFrame, nextRender, nextUpdate, touchstart } from '@vaadin/testing-helpers'; import './not-animated-styles.js'; import '@vaadin/dialog'; import '@vaadin/popover'; @@ -84,6 +84,33 @@ describe('popover in dialog', () => { expect(dialog.opened).to.be.true; }); }); + + describe('modeless dialog', () => { + beforeEach(async () => { + dialog.modeless = true; + + button.click(); + await nextRender(); + }); + + it('should bring popover overlay to front on dialog overlay mousedown', () => { + mousedown(dialog.$.overlay); + + const dialogZIndex = parseInt(getComputedStyle(dialog.$.overlay).zIndex); + const popoverZIndex = parseInt(getComputedStyle(overlay).zIndex); + + expect(popoverZIndex).to.equal(dialogZIndex + 1); + }); + + it('should bring popover overlay to front on dialog overlay touchstart', () => { + touchstart(dialog.$.overlay); + + const dialogZIndex = parseInt(getComputedStyle(dialog.$.overlay).zIndex); + const popoverZIndex = parseInt(getComputedStyle(overlay).zIndex); + + expect(popoverZIndex).to.equal(dialogZIndex + 1); + }); + }); }); });