diff --git a/packages/fiori/cypress/specs/DynamicPage.cy.tsx b/packages/fiori/cypress/specs/DynamicPage.cy.tsx
index 23847b6f4ba4..92aa2e93aa4b 100644
--- a/packages/fiori/cypress/specs/DynamicPage.cy.tsx
+++ b/packages/fiori/cypress/specs/DynamicPage.cy.tsx
@@ -1197,6 +1197,151 @@ describe("Page layout when content overflows", () => {
});
});
+describe("Header actions DOM order", () => {
+ it("renders header actions after the header slot when expanded", () => {
+ cy.mount(
+
+
+ Page Title
+
+
+ Header Content
+
+ content
+
+ );
+
+ cy.get("[ui5-dynamic-page]").should("have.prop", "headerSnapped", false);
+
+ // Actions must NOT be inside the sticky title-header-wrapper
+ cy.get("[ui5-dynamic-page]")
+ .shadow()
+ .find(".ui5-dynamic-page-title-header-wrapper [ui5-dynamic-page-header-actions]")
+ .should("not.exist");
+
+ // Actions must be a direct child of the scroll container (after the header slot)
+ cy.get("[ui5-dynamic-page]")
+ .shadow()
+ .find(".ui5-dynamic-page-scroll-container > [ui5-dynamic-page-header-actions]")
+ .should("exist");
+
+ // DOM order: header slot must come before actions in the scroll container
+ cy.get("[ui5-dynamic-page]")
+ .shadow()
+ .find(".ui5-dynamic-page-scroll-container")
+ .then(($container) => {
+ const children = Array.from($container[0].children);
+ const headerSlotIdx = children.findIndex(
+ (el) => el.tagName === "SLOT" && el.getAttribute("name") === "headerArea"
+ );
+ const actionsIdx = children.findIndex((el) =>
+ el.hasAttribute("ui5-dynamic-page-header-actions")
+ );
+ expect(headerSlotIdx).to.be.greaterThan(-1);
+ expect(actionsIdx).to.be.greaterThan(headerSlotIdx);
+ });
+ });
+
+ it("renders header actions inside the sticky wrapper when snapped", () => {
+ cy.mount(
+
+
+ Page Title
+
+
+ Header Content
+
+ content
+
+ );
+
+ cy.get("[ui5-dynamic-page]").invoke("prop", "headerSnapped", true);
+
+ cy.get("[ui5-dynamic-page]")
+ .shadow()
+ .find(".ui5-dynamic-page-title-header-wrapper [ui5-dynamic-page-header-actions]")
+ .should("exist");
+
+ cy.get("[ui5-dynamic-page]")
+ .shadow()
+ .find(".ui5-dynamic-page-scroll-container > [ui5-dynamic-page-header-actions]")
+ .should("not.exist");
+ });
+
+ it("renders header actions inside the sticky wrapper when pinned", () => {
+ cy.mount(
+
+
+ Page Title
+
+
+ Header Content
+
+ content
+
+ );
+
+ cy.get("[ui5-dynamic-page]").invoke("prop", "headerPinned", true);
+
+ cy.get("[ui5-dynamic-page]")
+ .shadow()
+ .find(".ui5-dynamic-page-title-header-wrapper [ui5-dynamic-page-header-actions]")
+ .should("exist");
+
+ cy.get("[ui5-dynamic-page]")
+ .shadow()
+ .find(".ui5-dynamic-page-scroll-container > [ui5-dynamic-page-header-actions]")
+ .should("not.exist");
+ });
+
+ it("moves header actions back inside the sticky wrapper when snapping after being expanded", () => {
+ cy.mount(
+
+
+ Page Title
+
+
+ Header Content
+
+ content
+
+ );
+
+ // Expanded: actions outside the sticky wrapper
+ cy.get("[ui5-dynamic-page]")
+ .shadow()
+ .find(".ui5-dynamic-page-scroll-container > [ui5-dynamic-page-header-actions]")
+ .should("exist");
+
+ // Snap the header
+ cy.get("[ui5-dynamic-page]").invoke("prop", "headerSnapped", true);
+
+ // Snapped: actions move inside the sticky wrapper
+ cy.get("[ui5-dynamic-page]")
+ .shadow()
+ .find(".ui5-dynamic-page-title-header-wrapper [ui5-dynamic-page-header-actions]")
+ .should("exist");
+
+ cy.get("[ui5-dynamic-page]")
+ .shadow()
+ .find(".ui5-dynamic-page-scroll-container > [ui5-dynamic-page-header-actions]")
+ .should("not.exist");
+
+ // Expand again: actions return outside the sticky wrapper
+ cy.get("[ui5-dynamic-page]").invoke("prop", "headerSnapped", false);
+
+ cy.get("[ui5-dynamic-page]")
+ .shadow()
+ .find(".ui5-dynamic-page-scroll-container > [ui5-dynamic-page-header-actions]")
+ .should("exist");
+
+ cy.get("[ui5-dynamic-page]")
+ .shadow()
+ .find(".ui5-dynamic-page-title-header-wrapper [ui5-dynamic-page-header-actions]")
+ .should("not.exist");
+ });
+});
+
describe("ARIA attributes", () => {
it("sets expanded state attributes", () => {
cy.mount(
diff --git a/packages/fiori/src/DynamicPage.ts b/packages/fiori/src/DynamicPage.ts
index 05c203029d21..8494b3e369f8 100644
--- a/packages/fiori/src/DynamicPage.ts
+++ b/packages/fiori/src/DynamicPage.ts
@@ -312,6 +312,10 @@ class DynamicPage extends UI5Element {
return !this.showHeaderInStickArea && !this.headerInTitle && !this.hasSnappedTitleOnMobile;
}
+ get headerActionsAfterHeader(): boolean {
+ return this.headerInContent && !this._headerSnapped;
+ }
+
get _headerLabel() {
return this._headerSnapped
? DynamicPage.i18nBundle.getText(DYNAMIC_PAGE_ARIA_LABEL_SNAPPED_HEADER)
diff --git a/packages/fiori/src/DynamicPageTemplate.tsx b/packages/fiori/src/DynamicPageTemplate.tsx
index 6ae5e33c3187..b500ee2845f3 100644
--- a/packages/fiori/src/DynamicPageTemplate.tsx
+++ b/packages/fiori/src/DynamicPageTemplate.tsx
@@ -21,7 +21,7 @@ export default function DynamicPageTemplate(this: DynamicPage) {
name="headerArea"
>
}
- {this.actionsInTitle && headerActions.call(this)}
+ {!this.headerActionsAfterHeader && headerActions.call(this)}
{this.headerInContent &&
@@ -30,8 +30,7 @@ export default function DynamicPageTemplate(this: DynamicPage) {
name="headerArea"
>
}
-
- {!this.actionsInTitle && headerActions.call(this)}
+ {this.headerActionsAfterHeader && headerActions.call(this)}