);
cy.get("[ui5-illustrated-message]")
- .shadow()
- .find(".ui5-illustrated-message-root")
- .should(($contents) => {
- const scrollHeight = $contents[0].scrollHeight;
- const offsetHeight = $contents[0].offsetHeight;
- expect(scrollHeight).to.be.lessThan(500);
- expect(scrollHeight).to.equal(offsetHeight);
- });
-
+ .should("have.attr", "media", IllustratedMessage.MEDIA.DIALOG);
+ });
+
+ it("media is scene when container width is 800px", () => {
+ cy.mount(
+
+ );
+
cy.get("[ui5-illustrated-message]")
- .should("have.prop", "media", expectedMedia);
+ .should("have.attr", "media", IllustratedMessage.MEDIA.SCENE);
});
- it("content with fixed design fits the parent container", () => {
+ it("media updates from dot to scene when container width expands", () => {
+ // This test guards against a specific bug: without the `scrollHeight > clientHeight`
+ // guard in _checkHeightConstraints, _contentHeightForMedia["scene"] gets recorded
+ // while scene is rendered at full width. When width then shrinks (dot media), the
+ // container height (auto) shrinks too. On re-expansion, _mediaExceedsContainerHeight("scene")
+ // compares the stored scene height against the current (small) clientHeight and wrongly
+ // blocks the upgrade back to scene.
cy.mount(
-
-
+
+
);
- cy.get("div")
- .invoke("css", "height", "250px");
+ // First render at wide width so _contentHeightForMedia["scene"] gets populated
+ cy.get("[ui5-illustrated-message]")
+ .should("have.attr", "media", IllustratedMessage.MEDIA.SCENE);
+
+ // Shrink to dot — clientHeight (auto) now equals the small dot illustration height
+ cy.get("#resizable-container")
+ .invoke("css", "width", "200px");
cy.get("[ui5-illustrated-message]")
- .shadow()
- .find(".ui5-illustrated-message-root")
- .should(($contents) => {
- const scrollHeight = $contents[0].scrollHeight;
- const offsetHeight = $contents[0].offsetHeight;
- expect(scrollHeight).to.be.lessThan(250);
- expect(scrollHeight).to.equal(offsetHeight);
- });
+ .should("have.attr", "media", IllustratedMessage.MEDIA.DOT);
+
+ // Expand back — must resolve to scene, not stay stuck at dot/dialog
+ cy.get("#resizable-container")
+ .invoke("css", "width", "800px");
+
+ cy.get("[ui5-illustrated-message]")
+ .should("have.attr", "media", IllustratedMessage.MEDIA.SCENE);
});
it("shows image with unconstrained height when container has auto height", () => {
@@ -240,8 +259,82 @@ describe("Vertical responsiveness", () => {
.find(".ui5-illustrated-message-illustration svg")
.should("have.css", "height", "160px");
});
+});
+
+describe("Height-based responsiveness (restricted height, wide container)", () => {
+ it("media is scene when container height is 600px", () => {
+ cy.mount(
+
+
+
+ );
+
+ cy.get("[ui5-illustrated-message]")
+ .should("have.attr", "media", IllustratedMessage.MEDIA.SCENE);
+
+ cy.get("[ui5-illustrated-message]")
+ .shadow()
+ .find(".ui5-illustrated-message-inner")
+ .should(($el) => {
+ expect($el[0].scrollHeight).to.be.lte($el[0].offsetHeight + 1);
+ });
+ });
+
+ it("media shrinks to dialog when container height is 350px", () => {
+ cy.mount(
+
+
+
+ );
+
+ cy.get("[ui5-illustrated-message]")
+ .should("have.attr", "media", IllustratedMessage.MEDIA.DIALOG);
+
+ cy.get("[ui5-illustrated-message]")
+ .shadow()
+ .find(".ui5-illustrated-message-inner")
+ .should(($el) => {
+ expect($el[0].scrollHeight).to.be.lte($el[0].offsetHeight + 1);
+ });
+ });
- it("Illustration visible, when container fit content height", () => {
+ it("media shrinks to spot when container height is 250px", () => {
+ cy.mount(
+
+
+
+ );
+
+ cy.get("[ui5-illustrated-message]")
+ .should("have.attr", "media", IllustratedMessage.MEDIA.SPOT);
+
+ cy.get("[ui5-illustrated-message]")
+ .shadow()
+ .find(".ui5-illustrated-message-inner")
+ .should(($el) => {
+ expect($el[0].scrollHeight).to.be.lte($el[0].offsetHeight + 1);
+ });
+ });
+
+ it("media shrinks to dot when container height is 180px", () => {
+ cy.mount(
+
+
+
+ );
+
+ cy.get("[ui5-illustrated-message]")
+ .should("have.attr", "media", IllustratedMessage.MEDIA.DOT);
+
+ cy.get("[ui5-illustrated-message]")
+ .shadow()
+ .find(".ui5-illustrated-message-inner")
+ .should(($el) => {
+ expect($el[0].scrollHeight).to.be.lte($el[0].offsetHeight + 1);
+ });
+ });
+
+ it("illustration is visible when container height fits content", () => {
cy.mount(
@@ -257,7 +350,7 @@ describe("Vertical responsiveness", () => {
});
});
- it("Illustration visible, when IM slotted and container has fixed height", () => {
+ it("illustration is visible when IM is slotted and container has fixed height", () => {
cy.mount(
@@ -274,6 +367,62 @@ describe("Vertical responsiveness", () => {
});
});
+describe("Height-based responsiveness (restricted height, dialog-width container)", () => {
+ it("media is dialog when container height is 500px", () => {
+ cy.mount(
+
+
+
+ );
+
+ cy.get("[ui5-illustrated-message]")
+ .should("have.attr", "media", IllustratedMessage.MEDIA.DIALOG);
+
+ cy.get("[ui5-illustrated-message]")
+ .shadow()
+ .find(".ui5-illustrated-message-inner")
+ .should(($el) => {
+ expect($el[0].scrollHeight).to.be.lte($el[0].offsetHeight + 1);
+ });
+ });
+
+ it("media shrinks to spot when container height is 250px", () => {
+ cy.mount(
+
+
+
+ );
+
+ cy.get("[ui5-illustrated-message]")
+ .should("have.attr", "media", IllustratedMessage.MEDIA.SPOT);
+
+ cy.get("[ui5-illustrated-message]")
+ .shadow()
+ .find(".ui5-illustrated-message-inner")
+ .should(($el) => {
+ expect($el[0].scrollHeight).to.be.lte($el[0].offsetHeight + 1);
+ });
+ });
+
+ it("media shrinks to dot when container height is 180px", () => {
+ cy.mount(
+
+
+
+ );
+
+ cy.get("[ui5-illustrated-message]")
+ .should("have.attr", "media", IllustratedMessage.MEDIA.DOT);
+
+ cy.get("[ui5-illustrated-message]")
+ .shadow()
+ .find(".ui5-illustrated-message-inner")
+ .should(($el) => {
+ expect($el[0].scrollHeight).to.be.lte($el[0].offsetHeight + 1);
+ });
+ });
+});
+
describe("Dot design resource handling", () => {
it("uses substitute Spot illustration", () => {
cy.mount(
diff --git a/packages/fiori/src/IllustratedMessage.ts b/packages/fiori/src/IllustratedMessage.ts
index 73652773cd9d..d2b8fd297f68 100644
--- a/packages/fiori/src/IllustratedMessage.ts
+++ b/packages/fiori/src/IllustratedMessage.ts
@@ -1,5 +1,5 @@
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
-import type { Slot, DefaultSlot } from "@ui5/webcomponents-base/dist/UI5Element.js";
+import type { Slot, DefaultSlot, ChangeInfo } from "@ui5/webcomponents-base/dist/UI5Element.js";
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
import slot from "@ui5/webcomponents-base/dist/decorators/slot-strict.js";
@@ -275,20 +275,15 @@ class IllustratedMessage extends UI5Element {
@i18n("@ui5/webcomponents-fiori")
static i18nBundle: I18nBundle;
- _lastKnownOffsetWidthForMedia: Record;
- _lastKnownOffsetHeightForMedia: Record;
- _lastKnownMedia: string;
+ _contentHeightForMedia: Record;
_handleResize: ResizeObserverCallback;
constructor() {
super();
this._handleResize = this.handleResize.bind(this);
- // this will store the last known offsetWidth of the IllustratedMessage DOM node for a given media (e.g. "Spot")
- this._lastKnownOffsetWidthForMedia = {};
- this._lastKnownOffsetHeightForMedia = {};
- // this will store the last known media, in order to detect if IllustratedMessage has been hidden by expand/collapse container
- this._lastKnownMedia = "base";
+ // this will store the height of the inner content of the IllustratedMessage (illustration + title + subtitle + actions) for a given media (e.g. "Spot")
+ this._contentHeightForMedia = {};
}
static get BREAKPOINTS() {
@@ -300,15 +295,6 @@ class IllustratedMessage extends UI5Element {
};
}
- static get BREAKPOINTS_HEIGHT() {
- return {
- DIALOG: 415,
- SPOT: 284,
- DOT: 207,
- BASE: 61,
- };
- }
-
static get MEDIA() {
return {
BASE: "base",
@@ -378,48 +364,66 @@ class IllustratedMessage extends UI5Element {
ResizeHandler.deregister(this, this._handleResize);
}
+ onInvalidation(changeInfo: ChangeInfo) {
+ if (
+ (changeInfo.type === "property" && ["name", "titleText", "subtitleText"].includes(changeInfo.name))
+ || (changeInfo.type === "slot" && ["title", "subtitle", "default"].includes(changeInfo.name))
+ ) {
+ this._contentHeightForMedia = {};
+ }
+ }
+
handleResize() {
- if (this.design !== IllustrationMessageDesign.Auto) {
- this._adjustHeightToFitContainer();
- return;
+ if (this.design === IllustrationMessageDesign.Auto) {
+ this._checkHeightConstraints();
+ this._applyMedia();
}
+ }
- this._applyMedia();
- window.requestAnimationFrame(this._adjustHeightToFitContainer.bind(this));
+ /**
+ * Checks if the current height of the component is enough to display the illustration, title, subtitle and actions.
+ * If not, the minimum required height for the current media is stored in the `_contentHeightForMedia` object.
+ * @private
+ * @since 1.5.0
+ */
+ _checkHeightConstraints() {
+ if (this.media && this.scrollHeight > this.clientHeight) { // needs vertical responsiveness
+ const innerEl = this.shadowRoot!.querySelector(".ui5-illustrated-message-inner");
+ const innerElHeight = innerEl ? innerEl.scrollHeight : 0;
+ innerElHeight && (this._contentHeightForMedia[this.media] = innerElHeight);
+ }
}
- _applyMedia(heightChange?: boolean) {
- const currOffsetWidth = this.offsetWidth,
- currOffsetHeight = this.offsetHeight;
-
- const design = heightChange ? currOffsetHeight : currOffsetWidth,
- oBreakpounts = heightChange ? IllustratedMessage.BREAKPOINTS_HEIGHT : IllustratedMessage.BREAKPOINTS;
- let newMedia = "";
-
- if (design <= oBreakpounts.BASE) {
- newMedia = IllustratedMessage.MEDIA.BASE;
- } else if (design <= oBreakpounts.DOT) {
- newMedia = IllustratedMessage.MEDIA.DOT;
- } else if (design <= oBreakpounts.SPOT) {
- newMedia = IllustratedMessage.MEDIA.SPOT;
- } else if (design <= oBreakpounts.DIALOG) {
- newMedia = IllustratedMessage.MEDIA.DIALOG;
+ _applyMedia() {
+ const width = this.offsetWidth;
+ let media = "",
+ mediaIndex = -1;
+
+ if (width <= IllustratedMessage.BREAKPOINTS.BASE) {
+ media = IllustratedMessage.MEDIA.BASE;
+ } else if (width <= IllustratedMessage.BREAKPOINTS.DOT) {
+ media = IllustratedMessage.MEDIA.DOT;
+ } else if (width <= IllustratedMessage.BREAKPOINTS.SPOT) {
+ media = IllustratedMessage.MEDIA.SPOT;
+ } else if (width <= IllustratedMessage.BREAKPOINTS.DIALOG) {
+ media = IllustratedMessage.MEDIA.DIALOG;
} else {
- newMedia = IllustratedMessage.MEDIA.SCENE;
+ media = IllustratedMessage.MEDIA.SCENE;
}
- const lastKnownOffsetWidth = this._lastKnownOffsetWidthForMedia[newMedia],
- lastKnownOffsetHeight = this._lastKnownOffsetHeightForMedia[newMedia];
- // prevents infinite resizing, when same width is detected for the same media,
- // excluding the case in which, the control is placed inside expand/collapse container
- if (!(lastKnownOffsetWidth && currOffsetWidth === lastKnownOffsetWidth
- && lastKnownOffsetHeight && currOffsetHeight === lastKnownOffsetHeight)
- || this._lastKnownOffsetWidthForMedia[this._lastKnownMedia] === 0
- || this._lastKnownOffsetHeightForMedia[this._lastKnownMedia] === 0) {
- this.media = newMedia;
- this._lastKnownOffsetWidthForMedia[newMedia] = currOffsetWidth;
- this._lastKnownOffsetHeightForMedia[newMedia] = currOffsetHeight;
- this._lastKnownMedia = newMedia;
+
+ mediaIndex = Object.values(IllustratedMessage.MEDIA).indexOf(media);
+
+ while (mediaIndex > 0 && this._mediaExceedsContainerHeight(media)) {
+ mediaIndex--;
+ media = Object.values(IllustratedMessage.MEDIA)[mediaIndex];
}
+
+ this.media = media;
+ }
+
+ _mediaExceedsContainerHeight(media: string): boolean {
+ const containerHeight = this.clientHeight || 0;
+ return !!this._contentHeightForMedia[media] && containerHeight < this._contentHeightForMedia[media];
}
_setSVGAccAttrs() {
@@ -446,21 +450,12 @@ class IllustratedMessage extends UI5Element {
}
}
- _adjustHeightToFitContainer() {
- const illustrationWrapper = this.shadowRoot!.querySelector(".ui5-illustrated-message-illustration"),
- illustration = illustrationWrapper.querySelector("svg");
-
- if (illustration) {
- illustrationWrapper.classList.toggle("ui5-illustrated-message-illustration-fit-content", false);
- if (this.getDomRef()!.scrollHeight > this.getDomRef()!.offsetHeight) {
- illustrationWrapper.classList.toggle("ui5-illustrated-message-illustration-fit-content", true);
- this._applyMedia(true /* height change */);
- }
- }
- }
-
onAfterRendering() {
this._setSVGAccAttrs();
+ if (this.design === IllustrationMessageDesign.Auto) {
+ this._checkHeightConstraints();
+ this._applyMedia();
+ }
}
/**
diff --git a/packages/fiori/src/themes/IllustratedMessage.css b/packages/fiori/src/themes/IllustratedMessage.css
index b7e7b8d6d4d2..769658c83750 100644
--- a/packages/fiori/src/themes/IllustratedMessage.css
+++ b/packages/fiori/src/themes/IllustratedMessage.css
@@ -6,7 +6,6 @@
box-sizing: border-box;
width: 100%;
height:100%;
- padding: 1rem;
}
.ui5-illustrated-message-root,
@@ -17,10 +16,19 @@
align-items: center;
justify-content: center;
height: inherit;
- min-height: 0;
flex-basis: content;
}
+.ui5-illustrated-message-root,
+.ui5-illustrated-message-inner {
+ min-height: 0;
+}
+
+.ui5-illustrated-message-inner {
+ padding: 1rem;
+ box-sizing: border-box;
+}
+
.ui5-illustrated-message-illustration {
margin: 2rem 0;
}
@@ -30,24 +38,6 @@
max-width: 100%;
}
-.ui5-illustrated-message-illustration.ui5-illustrated-message-illustration-fit-content {
- position: relative;
- width: 0;
- padding: 0;
- padding-left: 100%;
- height: 100%;
- display: flex;
- align-items: center;
-}
-
-.ui5-illustrated-message-illustration.ui5-illustrated-message-illustration-fit-content svg {
- position: absolute;
- height: 100%;
- width: 100%;
- left: 0;
- top: 0;
-}
-
.ui5-illustrated-message-title [ui5-title],
.ui5-illustrated-message-title ::slotted([slot="title"]) {
text-align: center;
@@ -93,7 +83,7 @@
margin-bottom: 1rem;
}
-:host([media="spot"]) {
+:host([media="spot"]) .ui5-illustrated-message-inner {
padding: 0.5rem;
}
@@ -119,7 +109,7 @@
margin: 0.5rem 0;
}
-:host([media="dot"]) {
+:host([media="dot"]) .ui5-illustrated-message-inner {
padding: 0.25rem;
min-height: 4rem; /* needed to avoid infinite loop between "dot" and "base" when only illustration is used (no title, etc.) */
}