Skip to content

Commit 5a872b0

Browse files
fix(ui5-popover): correctly position a popover if dynamically created (#2679)
Now when a popover is dynamically created it is shown only after its size can be determined. This prevents its position to flicker on opening. Fixes: #1755
1 parent 679dae3 commit 5a872b0

File tree

4 files changed

+89
-16
lines changed

4 files changed

+89
-16
lines changed

packages/main/src/Popover.js

+41-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Integer from "@ui5/webcomponents-base/dist/types/Integer.js";
2+
import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js";
23
import Popup from "./Popup.js";
34
import PopoverPlacementType from "./types/PopoverPlacementType.js";
45
import PopoverVerticalAlign from "./types/PopoverVerticalAlign.js";
@@ -250,6 +251,12 @@ const metadata = {
250251
* @public
251252
*/
252253
class Popover extends Popup {
254+
constructor() {
255+
super();
256+
257+
this._handleResize = this.handleResize.bind(this);
258+
}
259+
253260
static get metadata() {
254261
return metadata;
255262
}
@@ -266,6 +273,14 @@ class Popover extends Popup {
266273
return 10; // px
267274
}
268275

276+
onEnterDOM() {
277+
ResizeHandler.register(this, this._handleResize);
278+
}
279+
280+
onExitDOM() {
281+
ResizeHandler.deregister(this, this._handleResize);
282+
}
283+
269284
isOpenerClicked(event) {
270285
const target = event.target;
271286
return target === this._opener || (target.getFocusDomRef && target.getFocusDomRef() === this._opener) || event.composedPath().indexOf(this._opener) > -1;
@@ -277,14 +292,15 @@ class Popover extends Popup {
277292
* @param {boolean} preventInitialFocus prevents applying the focus inside the popover
278293
* @public
279294
*/
280-
openBy(opener, preventInitialFocus = false) {
295+
async openBy(opener, preventInitialFocus = false) {
281296
if (!opener || this.opened) {
282297
return;
283298
}
284299

285300
this._opener = opener;
301+
this._openerRect = opener.getBoundingClientRect();
286302

287-
super.open(preventInitialFocus);
303+
await super.open(preventInitialFocus);
288304
}
289305

290306
/**
@@ -332,21 +348,36 @@ class Popover extends Popup {
332348
&& openerRect.right === 0;
333349
}
334350

351+
handleResize() {
352+
if (this.opened) {
353+
this.reposition();
354+
}
355+
}
356+
335357
reposition() {
336358
this.show();
337359
}
338360

339361
show() {
340362
let placement;
341-
const popoverSize = this.popoverSize;
342-
const openerRect = this._opener.getBoundingClientRect();
363+
const popoverSize = this.getPopoverSize();
364+
365+
if (popoverSize.width === 0 || popoverSize.height === 0) {
366+
// size can not be determined properly at this point, popover will be shown with the next reposition
367+
return;
368+
}
343369

344-
if (this.shouldCloseDueToNoOpener(openerRect) && this.isFocusWithin()) {
370+
if (this.isOpen()) {
371+
// update opener rect if it was changed during the popover being opened
372+
this._openerRect = this._opener.getBoundingClientRect();
373+
}
374+
375+
if (this.shouldCloseDueToNoOpener(this._openerRect) && this.isFocusWithin()) {
345376
// reuse the old placement as the opener is not available,
346377
// but keep the popover open as the focus is within
347378
placement = this._oldPlacement;
348379
} else {
349-
placement = this.calcPlacement(openerRect, popoverSize);
380+
placement = this.calcPlacement(this._openerRect, popoverSize);
350381
}
351382

352383
const stretching = this.horizontalAlign === PopoverHorizontalAlign.Stretch;
@@ -379,7 +410,7 @@ class Popover extends Popup {
379410
}
380411
}
381412

382-
get popoverSize() {
413+
getPopoverSize() {
383414
let width,
384415
height;
385416
let rect = this.getBoundingClientRect();
@@ -391,17 +422,15 @@ class Popover extends Popup {
391422
return { width, height };
392423
}
393424

394-
this.style.visibility = "hidden";
395425
this.style.display = "block";
426+
this.style.top = "-10000px";
427+
this.style.left = "-10000px";
396428

397429
rect = this.getBoundingClientRect();
398430

399431
width = rect.width;
400432
height = rect.height;
401433

402-
this.hide();
403-
this.style.visibility = "visible";
404-
405434
return { width, height };
406435
}
407436

@@ -595,6 +624,7 @@ class Popover extends Popup {
595624
switch (this.horizontalAlign) {
596625
case PopoverHorizontalAlign.Center:
597626
case PopoverHorizontalAlign.Stretch:
627+
598628
left = targetRect.left - (popoverSize.width - targetRect.width) / 2;
599629
break;
600630
case PopoverHorizontalAlign.Left:

packages/main/src/Popup.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { renderFinished } from "@ui5/webcomponents-base/dist/Render.js";
12
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
23
import { getRTL } from "@ui5/webcomponents-base/dist/config/RTL.js";
34
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
@@ -321,7 +322,7 @@ class Popup extends UI5Element {
321322
* @param {boolean} preventInitialFocus prevents applying the focus inside the popup
322323
* @public
323324
*/
324-
open(preventInitialFocus) {
325+
async open(preventInitialFocus) {
325326
const prevented = !this.fireEvent("before-open", {}, true, false);
326327
if (prevented) {
327328
return;
@@ -337,6 +338,7 @@ class Popup extends UI5Element {
337338
this._zIndex = getNextZIndex();
338339
this.style.zIndex = this._zIndex;
339340
this._focusedElementBeforeOpen = getFocusedElement();
341+
340342
this.show();
341343

342344
if (!this._disableInitialFocus && !preventInitialFocus) {
@@ -346,6 +348,8 @@ class Popup extends UI5Element {
346348
this._addOpenedPopup();
347349

348350
this.opened = true;
351+
352+
await renderFinished();
349353
this.fireEvent("after-open", {}, false, false);
350354
}
351355

packages/main/test/pages/Popover.html

+25-4
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@
137137

138138

139139
<ui5-multi-combobox>
140-
<ui5-li>Hello</ui5-li>
141-
<ui5-li>World</ui5-li>
140+
<ui5-cb-item text="Hello"></ui5-cb-item>
141+
<ui5-cb-item text="World"></ui5-cb-item>
142142
</ui5-multi-combobox>
143143

144144
<br>
@@ -375,6 +375,13 @@
375375
<ui5-button id="secondBtn">Second button</ui5-button>
376376
<ui5-popover id="popNoFocusableContent" placement-type="Bottom">Sample text for the ui5-popover</ui5-popover>
377377

378+
<br>
379+
<br>
380+
381+
<div style="height: 6rem; text-align: center;">
382+
<ui5-button id="btnOpenDynamic" icon="overflow"></ui5-button>
383+
</div>
384+
378385
<br>
379386

380387
<script>
@@ -458,15 +465,29 @@
458465
popXRight.openBy(targetOpener2);
459466
});
460467

461-
firstBtn.addEventListener("click", (event) => {
468+
firstBtn.addEventListener("click", function (event) {
462469
popNoFocusableContent.innerHTML = "First button is clicked";
463470
popNoFocusableContent.openBy(event.target);
464471
});
465472

466-
secondBtn.addEventListener("click", (event) => {
473+
secondBtn.addEventListener("click", function (event) {
467474
popNoFocusableContent.innerHTML = "Second button is clicked";
468475
popNoFocusableContent.openBy(event.target);
469476
});
477+
478+
btnOpenDynamic.addEventListener("click", function () {
479+
var popover = document.createElement("ui5-popover"),
480+
button = document.createElement("ui5-button");
481+
482+
button.textContent = "Focusable element";
483+
popover.appendChild(button);
484+
popover.setAttribute("id", "dynamic-popover");
485+
popover.setAttribute("placement-type", "Bottom");
486+
487+
document.body.appendChild(popover);
488+
489+
popover.openBy(btnOpenDynamic);
490+
});
470491
</script>
471492
</body>
472493

packages/main/test/specs/Popover.spec.js

+18
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,24 @@ describe("Popover general interaction", () => {
250250

251251
assert.ok(activeElementId, popoverId, "Popover remains focused");
252252
});
253+
254+
it("tests that dynamically created popover is opened", () => {
255+
browser.url("http://localhost:8080/test-resources/pages/Popover.html");
256+
257+
const btnOpenDynamic = $("#btnOpenDynamic");
258+
btnOpenDynamic.click();
259+
const popover = $('#dynamic-popover');
260+
261+
browser.waitUntil(
262+
() => popover.getCSSProperty("top").parsed.value > 0 && popover.getCSSProperty("left").parsed.value > 0,
263+
{
264+
timeout: 500,
265+
timeoutMsg: "popover was not opened after a timeout"
266+
}
267+
);
268+
269+
assert.ok(true, "popover is opened");
270+
});
253271
});
254272

255273
describe("Acc", () => {

0 commit comments

Comments
 (0)