Skip to content

Commit 245b23b

Browse files
committed
fix(ui5-dialog): Dynamically opened dialogs no longer flicker when opened
1 parent 768a8ca commit 245b23b

File tree

4 files changed

+114
-37
lines changed

4 files changed

+114
-37
lines changed

packages/main/cypress/specs/Dialog.cy.tsx

Lines changed: 98 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -357,8 +357,6 @@ describe("Events", () => {
357357

358358
describe("Dialog general interaction", () => {
359359
it("tests dialog toggling", () => {
360-
361-
362360
cy.mount(
363361
<>
364362
<Dialog id="dialog" accessibleName="Resizable" stretch>
@@ -406,6 +404,62 @@ describe("Dialog general interaction", () => {
406404
.should("be.calledOnce");
407405
});
408406

407+
it("dynamic dialog initial positioning", () => {
408+
const dialog = document.createElement("ui5-dialog") as Dialog;
409+
dialog.setAttribute("id", "dynamic-dialog");
410+
411+
const content = document.createElement("div");
412+
content.style.height = "600px";
413+
content.style.width = "600px";
414+
content.textContent = "Content";
415+
416+
dialog.appendChild(content);
417+
418+
// Spy on Object.assign only for this dialog's style
419+
cy.window().then(() => {
420+
const originalAssign = Object.assign;
421+
const topAndLeftStyles: Array<{ top: number; left: number }> = [];
422+
423+
cy.stub(Object, "assign").callsFake(function(target: any, ...sources: any[]) {
424+
// Check if target is the dialog's style object
425+
if (target === dialog.style) {
426+
const styleObj = sources[0];
427+
if (styleObj && styleObj.top !== undefined && styleObj.left !== undefined) {
428+
topAndLeftStyles.push({
429+
top: parseInt(styleObj.top),
430+
left: parseInt(styleObj.left)
431+
});
432+
}
433+
}
434+
return originalAssign.call(this, target, ...sources);
435+
}).as("objectAssignStub");
436+
437+
cy.wrap(topAndLeftStyles).as("topAndLeftStyles");
438+
439+
dialog.setAttribute("open", "true");
440+
document.body.appendChild(dialog);
441+
});
442+
443+
cy.get<Dialog>("#dynamic-dialog").ui5DialogOpened();
444+
445+
// Assert the captured style values
446+
cy.get<Array<{ top: number; left: number }>>("@topAndLeftStyles")
447+
.should((topAndLeftStyles) => {
448+
expect(topAndLeftStyles.length).to.be.greaterThan(1, "'top' and 'left' styles should have been assigned");
449+
450+
// styles from initial call of _center method
451+
const firstStyles = topAndLeftStyles[0];
452+
453+
expect(firstStyles.top).to.not.equal(0, "top should not start from 0");
454+
expect(firstStyles.left).to.not.equal(0, "left should not start from 0");
455+
456+
// styles from _center method called as resize handler callback
457+
const secondStyles = topAndLeftStyles[1];
458+
459+
expect(secondStyles.top).to.equal(firstStyles.top, "top should remain the same after resize event");
460+
expect(secondStyles.left).to.equal(firstStyles.left, "left should remain the same after resize event");
461+
});
462+
});
409463

410464
it("dialog repositions after screen resize", () => {
411465
cy.mount(
@@ -516,46 +570,56 @@ describe("Dialog general interaction", () => {
516570
cy.get("#draggable-dialog").invoke("attr", "open", true);
517571
cy.get<Dialog>("#draggable-dialog").ui5DialogOpened();
518572

573+
let topBeforeDragging: number;
574+
let leftBeforeDragging: number;
575+
519576
// Capture position before dragging
520577
cy.get("#draggable-dialog")
521-
.then(dialog => {
522-
const topBeforeDragging = parseInt(dialog.css("top"));
523-
const leftBeforeDragging = parseInt(dialog.css("left"));
578+
.should(dialog => {
579+
topBeforeDragging = parseInt(dialog.css("top"));
580+
leftBeforeDragging = parseInt(dialog.css("left"));
524581

525-
// Drag dialog
526-
cy.get("#draggable-dialog")
527-
.find("#header-slot")
528-
.trigger("mousedown", { which: 1 })
529-
.trigger("mousemove", { clientX: 150, clientY: 150 })
530-
.trigger("mouseup");
582+
expect(topBeforeDragging).to.equal(492);
583+
expect(leftBeforeDragging).to.equal(560);
584+
});
531585

532-
// Capture position after dragging
533-
cy.get("#draggable-dialog")
534-
.should(dialogAfterDragging => {
535-
const topAfterDragging = parseInt(dialogAfterDragging.css("top"));
536-
const leftAfterDragging = parseInt(dialogAfterDragging.css("left"));
586+
// Drag dialog
587+
cy.get("#draggable-dialog")
588+
.find("#header-slot")
589+
.trigger("mousedown", { which: 1 })
590+
.trigger("mousemove", { clientX: 200, clientY: 150, })
591+
.trigger("mouseup");
537592

538-
// Assert position changes
539-
expect(topBeforeDragging).not.to.equal(topAfterDragging);
540-
expect(leftBeforeDragging).not.to.equal(leftAfterDragging);
541-
});
593+
cy.get("#draggable-dialog")
594+
.should("have.css", "top", "141px")
595+
.should("have.css", "left", "40px");
542596

543-
// Close dialog
544-
cy.get("#draggable-dialog").invoke("attr", "open", false);
597+
// Capture position after dragging
598+
cy.get("#draggable-dialog")
599+
.should(dialogAfterDragging => {
600+
const topAfterDragging = parseInt(dialogAfterDragging.css("top"));
601+
const leftAfterDragging = parseInt(dialogAfterDragging.css("left"));
545602

546-
// Reopen dialog
547-
cy.get("#draggable-dialog").invoke("attr", "open", true);
603+
// Assert position changes
604+
expect(topBeforeDragging).not.to.equal(topAfterDragging);
605+
expect(leftBeforeDragging).not.to.equal(leftAfterDragging);
606+
});
548607

549-
// Capture position after reopening
550-
cy.get("#draggable-dialog")
551-
.should(dialogAfterReopening => {
552-
const topAfterReopening = parseInt(dialogAfterReopening.css("top"));
553-
const leftAfterReopening = parseInt(dialogAfterReopening.css("left"));
608+
// Close dialog
609+
cy.get("#draggable-dialog").invoke("attr", "open", false);
554610

555-
// Assert position resets
556-
expect(topBeforeDragging).to.equal(topAfterReopening);
557-
expect(leftBeforeDragging).to.equal(leftAfterReopening);
558-
});
611+
// Reopen dialog
612+
cy.get("#draggable-dialog").invoke("attr", "open", true);
613+
614+
// Capture position after reopening
615+
cy.get("#draggable-dialog")
616+
.should(dialogAfterReopening => {
617+
const topAfterReopening = parseInt(dialogAfterReopening.css("top"));
618+
const leftAfterReopening = parseInt(dialogAfterReopening.css("left"));
619+
620+
// Assert position resets
621+
expect(topBeforeDragging).to.equal(topAfterReopening);
622+
expect(leftBeforeDragging).to.equal(leftAfterReopening);
559623
});
560624
});
561625

@@ -631,7 +695,6 @@ describe("Dialog general interaction", () => {
631695
});
632696

633697
it("resizable - mouse support", () => {
634-
635698
cy.mount(
636699
<>
637700
<Dialog id="resizable-dialog" resizable>
@@ -1583,4 +1646,4 @@ describe("Dialog initially open", () => {
15831646
// Assert dialog matches :popover-open selector
15841647
cy.get("#dialogOpen").should("match", ":popover-open");
15851648
});
1586-
});
1649+
});

packages/main/src/Dialog.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,10 @@ class Dialog extends Popup {
329329

330330
_show() {
331331
super._show();
332-
this._center();
332+
requestAnimationFrame(() => {
333+
this._updateMediaRange(); // TODO: Carefully put in Popup
334+
this._center();
335+
});
333336
}
334337

335338
onBeforeRendering() {

packages/main/src/Popup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ abstract class Popup extends UI5Element {
604604
}
605605

606606
/**
607-
* Sets "block" display to the popup. The property can be overriden by derivatives of Popup.
607+
* Sets "popover=manual" to the popup. The method can be overridden by derivatives of Popup.
608608
* @protected
609609
*/
610610
_show() {

packages/main/test/pages/Dialog.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
</head>
2525

2626
<body>
27+
<ui5-button id="btnUnwanted">Open dialog with unwanted animation</ui5-button>
2728
<div class="wrapper">
2829
<div>
2930
<ui5-segmented-button id="theme-switch">
@@ -871,6 +872,16 @@
871872
</ui5-dialog>
872873

873874
<script>
875+
document.getElementById("btnUnwanted").addEventListener("click", function () {
876+
const dialog = document.createElement("ui5-dialog");
877+
const child1 = `<div data-v-f5de9ea6="" data-v-d7dd9f52="" class="header" slot="header" design="Header"><h3 data-v-f5de9ea6="" automation-id="contactEmailFormModal-header">Contact form</h3><udex-control-button data-v-f5de9ea6="" automation-id="contactEmailFormModal-headerCloseButton" slot="endContent" icon-only="" has-icon="" accessible-name="close button"><template shadowrootmode="open" shadowrootdelegatesfocus=""></template></udex-control-button></div>`
878+
const child2 = `<div data-v-d7dd9f52="" class="modal-content" automation-id="contactEmailForm-modalContent"><div data-v-d7dd9f52="" class="modal-form-header" automation-id="contactEmailForm-modalFormHeader"><div data-v-9c54a22e="" data-v-d7dd9f52="" class="header-container" automation-id="contactEmailForm-contactEmailHeader"><div data-v-9c54a22e="" class="relative block h-full max-w-[130px]" automation-id="partnerProfilePage-partnerLogo-root"><div automation-id="partnerProfilePage-partnerLogo" class="border-grey-2 block h-[62px] w-[62px] rounded-sm border bg-white flex justify-center items-center !w-[60px] !h-[60px] !border-none"><img class="h-full w-full object-contain" src="/sap/details/api//media/storage/0000225382/1740396106410-0000225382.png" automation-id="partnerProfilePage-partnerLogo-img"></div></div><div data-v-9c54a22e="" class="HeadingMediumM-size"><div data-v-9c54a22e="">Contact SEIDOR</div><div data-v-9c54a22e=""></div></div></div></div><form data-v-a57c5c36="" data-v-d7dd9f52="" automation-id="contactEmailForm-contactEmailFormContent"><div data-v-a57c5c36="" class="form-field-wrapper mobile-only"><div data-v-a57c5c36="" class="newsletter-option"><p data-v-a57c5c36="" class="newsletter-label">Stay informed about our updates</p><udex-checkbox data-v-a57c5c36="" id="newsletter" tabindex="-1" automation-id="newsletter-option"><template shadowrootmode="open"></template></udex-checkbox><small data-v-a57c5c36="" class="newsletter-info">You can unsubscribe at any time.</small></div></div><div data-v-a57c5c36="" class="system-field alternate-view"><udex-text-field data-v-a57c5c36="" id="referral_code" name="referral_code" placeholder="Enter your referral code" tabindex="-1" automation-id="referral-code-input"><template shadowrootmode="open"></template></udex-text-field><small data-v-a57c5c36="" class="field-hint">Enter code above</small></div><udex-message-strip data-v-a57c5c36="" status-type="Icon" class="mb-8"><template shadowrootmode="open"></template><div data-v-a57c5c36="" class="flex items-center justify-between gap-4"><span data-v-a57c5c36="" class="body-s-size">We suggest to read some tips before selecting a Partner</span><udex-button data-v-a57c5c36="" class="flex shrink-0 justify-center" id="custom-usage-patterns-forms-learn-more" accessible-name="Read Tips before Selecting a Partner" udex-button=""><template shadowrootmode="open" shadowrootdelegatesfocus=""></template>Read Tips</udex-button></div></udex-message-strip><div data-v-a57c5c36="" class="HeadingMediumXXXS-size mb-4" automation-id="contactEmailFormContent-personalDetailsTitle">Personal Details</div><div data-v-a57c5c36="" class="flex flex-col gap-1.5 max-[850px]:gap-2" automation-id="contactEmailFormContent-personalDetails"><div data-v-a57c5c36="" class="flex justify-between gap-6 max-[850px]:flex-col max-[850px]:gap-2" automation-id="contactEmailFormContent-personalDetailsNames"><udex-text-field data-v-a57c5c36="" value-state="Standard" aria-describedby="firstNameError" supporting-text="" automation-id="contactEmailFormContent-firstName"><template shadowrootmode="open"></template></udex-text-field><udex-text-field data-v-a57c5c36="" value-state="Standard" aria-describedby="lastNameError" supporting-text="" automation-id="contactEmailFormContent-lastName"><template shadowrootmode="open"></template></udex-text-field></div><div data-v-a57c5c36="" class="flex justify-between gap-6 max-[850px]:flex-col max-[850px]:gap-2" automation-id="contactEmailFormContent-personalDetailsContacts"><udex-text-field data-v-a57c5c36="" value-state="Standard" aria-describedby="emailError" supporting-text="" automation-id="contactEmailFormContent-email"><template shadowrootmode="open"></template></udex-text-field><udex-phone-number-field data-v-a57c5c36="" id="udex-phone-number-field" value-state="Standard" automation-id="contactEmailFormContent-phoneNumber"><template shadowrootmode="open"></template></udex-phone-number-field></div></div><div data-v-a57c5c36="" class="HeadingMediumXXXS-size mb-4" automation-id="contactEmailFormContent-companyDetailsTitle">Company Details</div><div data-v-a57c5c36="" class="mb-1 flex flex-col gap-1.5" automation-id="contactEmailFormContent-companyDetails"><div data-v-a57c5c36="" class="flex gap-6 max-[850px]:flex-col max-[850px]:gap-2" automation-id="contactEmailFormContent-companyDetailsInputs"><udex-text-field data-v-a57c5c36="" value-state="Standard" aria-describedby="companyError" supporting-text="" automation-id="contactEmailFormContent-companyName"><template shadowrootmode="open"></template></udex-text-field><udex-country-selector data-v-a57c5c36="" id="country-selector" value-state="Standard" width="380px" search-placeholder="Search Countries and Regions" supporting-text="" automation-id="contactEmailFormContent-countrySelector" value=""><template shadowrootmode="open"></template></udex-country-selector></div><div data-v-a57c5c36="" class="flex gap-6 max-[850px]:flex-col max-[850px]:gap-2" automation-id="contactEmailFormContent-companyDetailsInputs"><udex-select-box data-v-a57c5c36="" value-state="Standard" supporting-text="" value=""><template shadowrootmode="open"></template><udex-select-box-item data-v-a57c5c36=""></udex-select-box-item><udex-select-box-item data-v-a57c5c36=""></udex-select-box-item><udex-select-box-item data-v-a57c5c36=""></udex-select-box-item><udex-select-box-item data-v-a57c5c36=""></udex-select-box-item><udex-select-box-item data-v-a57c5c36=""></udex-select-box-item><udex-select-box-item data-v-a57c5c36=""></udex-select-box-item><udex-select-box-item data-v-a57c5c36=""></udex-select-box-item><udex-select-box-item data-v-a57c5c36=""></udex-select-box-item><udex-select-box-item data-v-a57c5c36=""></udex-select-box-item></udex-select-box></div></div><udex-textarea data-v-a57c5c36="" id="textarea" value-state="Standard" supporting-text="" automation-id="contactEmailFormContent-comments"><template shadowrootmode="open"></template></udex-textarea><div data-v-1f58e182="" data-v-a57c5c36="" class="agreement body-s-size" automation-id="contactEmailFormContent-agreement"><div data-v-1f58e182="" class="checkbox-agreement-wrapper" automation-id="contactEmailForm-checkboxWrapper"><udex-checkbox data-v-1f58e182="" value-state="Standard" automation-id="contactEmailForm-checkbox"><template shadowrootmode="open"></template></udex-checkbox><span data-v-1f58e182="" automation-id="contactEmailForm-agreementText"> I agree that SAP will share this data with SEIDOR so that they can contact me regarding my request. * <udex-link data-v-1f58e182="" automation-id="contactEmailForm-privacyLink"><template shadowrootmode="open"></template>View Privacy Statement.</udex-link></span></div><!----></div><div data-v-a57c5c36="" class="mt-6" automation-id="contactEmailFormContent-recaptcha"></div><!----><udex-button data-v-a57c5c36="" class="mt-6" id="custom-usage-patterns-forms-submit" accessible-name="Contact Sales Submit" udex-button="" automation-id="contactEmailFormContent-submitButton"><template shadowrootmode="open" shadowrootdelegatesfocus=""></template>Send</udex-button></form></div>`;
879+
880+
dialog.innerHTML = child1 + child2;
881+
document.body.appendChild(dialog);
882+
dialog.open = true;
883+
});
884+
874885
cbCompact.addEventListener("ui5-change", function () {
875886
document.body.classList.toggle("ui5-content-density-compact", cbCompact.checked);
876887
});

0 commit comments

Comments
 (0)