From 8dba182340a8df8362f1207ff12836d918c1b896 Mon Sep 17 00:00:00 2001 From: PetyaMarkovaBogdanova Date: Thu, 27 Aug 2026 16:18:08 +0300 Subject: [PATCH 01/13] fix(ui5-dynamic-page): toggle button reading fixed --- packages/fiori/src/DynamicPage.ts | 6 ++++++ packages/fiori/src/DynamicPageTemplate.tsx | 8 ++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/fiori/src/DynamicPage.ts b/packages/fiori/src/DynamicPage.ts index 05c203029d215..69746b7d7e9da 100644 --- a/packages/fiori/src/DynamicPage.ts +++ b/packages/fiori/src/DynamicPage.ts @@ -358,6 +358,12 @@ class DynamicPage extends UI5Element { return this.hidePinButton || isPhone(); } + get _actionsBarStickyTop(): number { + const titleHeight = this.dynamicPageTitle?.getBoundingClientRect().height || 0; + const headerHeight = this.headerInTitle ? (this.dynamicPageHeader?.getBoundingClientRect().height || 0) : 0; + return titleHeight + headerHeight; + } + /** * Defines if the header is snapped. * diff --git a/packages/fiori/src/DynamicPageTemplate.tsx b/packages/fiori/src/DynamicPageTemplate.tsx index 6ae5e33c31871..3da5eaeb75d17 100644 --- a/packages/fiori/src/DynamicPageTemplate.tsx +++ b/packages/fiori/src/DynamicPageTemplate.tsx @@ -21,7 +21,6 @@ export default function DynamicPageTemplate(this: DynamicPage) { name="headerArea" > } - {this.actionsInTitle && headerActions.call(this)} {this.headerInContent && @@ -31,7 +30,12 @@ export default function DynamicPageTemplate(this: DynamicPage) { > } - {!this.actionsInTitle && headerActions.call(this)} +
+ {headerActions.call(this)} +
Date: Thu, 27 Aug 2026 17:37:51 +0300 Subject: [PATCH 02/13] fix(ui5-dynamic-page): fixed pin button announcement --- packages/fiori/src/DynamicPage.ts | 6 ++++++ packages/fiori/src/DynamicPageTemplate.tsx | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/fiori/src/DynamicPage.ts b/packages/fiori/src/DynamicPage.ts index 69746b7d7e9da..ee9b98a556d9c 100644 --- a/packages/fiori/src/DynamicPage.ts +++ b/packages/fiori/src/DynamicPage.ts @@ -29,6 +29,8 @@ import type DynamicPageHeaderActions from "./DynamicPageHeaderActions.js"; import { DYNAMIC_PAGE_ARIA_LABEL_EXPANDED_HEADER, DYNAMIC_PAGE_ARIA_LABEL_SNAPPED_HEADER, + DYNAMIC_PAGE_ARIA_LABEL_PIN_HEADER, + DYNAMIC_PAGE_ARIA_LABEL_UNPIN_HEADER, } from "./generated/i18n/i18n-defaults.js"; import type { Slot, DefaultSlot } from "@ui5/webcomponents-base/dist/UI5Element.js"; @@ -462,6 +464,10 @@ class DynamicPage extends UI5Element { this.fireDecoratorEvent("pin-button-toggle"); await renderFinished(); this.headerActions?.focusPinButton(); + announce( + DynamicPage.i18nBundle.getText(this.headerPinned ? DYNAMIC_PAGE_ARIA_LABEL_UNPIN_HEADER : DYNAMIC_PAGE_ARIA_LABEL_PIN_HEADER), + InvisibleMessageMode.Polite, + ); } async onToggleTitle() { diff --git a/packages/fiori/src/DynamicPageTemplate.tsx b/packages/fiori/src/DynamicPageTemplate.tsx index 3da5eaeb75d17..29aa13f7520b3 100644 --- a/packages/fiori/src/DynamicPageTemplate.tsx +++ b/packages/fiori/src/DynamicPageTemplate.tsx @@ -32,7 +32,7 @@ export default function DynamicPageTemplate(this: DynamicPage) {
{headerActions.call(this)}
From 69800f9c0b1d1b6092707f068adca7bc1daa9267 Mon Sep 17 00:00:00 2001 From: PetyaMarkovaBogdanova Date: Fri, 28 Aug 2026 09:47:52 +0300 Subject: [PATCH 03/13] fix(ui5-dynamic-page): fixed announcement mode for pin/unpin header button --- packages/fiori/src/DynamicPage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/fiori/src/DynamicPage.ts b/packages/fiori/src/DynamicPage.ts index ee9b98a556d9c..f93436206580e 100644 --- a/packages/fiori/src/DynamicPage.ts +++ b/packages/fiori/src/DynamicPage.ts @@ -466,7 +466,7 @@ class DynamicPage extends UI5Element { this.headerActions?.focusPinButton(); announce( DynamicPage.i18nBundle.getText(this.headerPinned ? DYNAMIC_PAGE_ARIA_LABEL_UNPIN_HEADER : DYNAMIC_PAGE_ARIA_LABEL_PIN_HEADER), - InvisibleMessageMode.Polite, + InvisibleMessageMode.Assertive, ); } From 991f9a3ebd556514c624164ddceb2419f0166c45 Mon Sep 17 00:00:00 2001 From: PetyaMarkovaBogdanova Date: Fri, 28 Aug 2026 11:25:16 +0300 Subject: [PATCH 04/13] fix(ui5-dynamic-page): toggle button reading fixed --- packages/fiori/src/DynamicPage.ts | 6 ------ packages/fiori/src/DynamicPageTemplate.tsx | 2 +- packages/fiori/src/themes/DynamicPage.css | 5 +++++ 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/fiori/src/DynamicPage.ts b/packages/fiori/src/DynamicPage.ts index f93436206580e..69746b7d7e9da 100644 --- a/packages/fiori/src/DynamicPage.ts +++ b/packages/fiori/src/DynamicPage.ts @@ -29,8 +29,6 @@ import type DynamicPageHeaderActions from "./DynamicPageHeaderActions.js"; import { DYNAMIC_PAGE_ARIA_LABEL_EXPANDED_HEADER, DYNAMIC_PAGE_ARIA_LABEL_SNAPPED_HEADER, - DYNAMIC_PAGE_ARIA_LABEL_PIN_HEADER, - DYNAMIC_PAGE_ARIA_LABEL_UNPIN_HEADER, } from "./generated/i18n/i18n-defaults.js"; import type { Slot, DefaultSlot } from "@ui5/webcomponents-base/dist/UI5Element.js"; @@ -464,10 +462,6 @@ class DynamicPage extends UI5Element { this.fireDecoratorEvent("pin-button-toggle"); await renderFinished(); this.headerActions?.focusPinButton(); - announce( - DynamicPage.i18nBundle.getText(this.headerPinned ? DYNAMIC_PAGE_ARIA_LABEL_UNPIN_HEADER : DYNAMIC_PAGE_ARIA_LABEL_PIN_HEADER), - InvisibleMessageMode.Assertive, - ); } async onToggleTitle() { diff --git a/packages/fiori/src/DynamicPageTemplate.tsx b/packages/fiori/src/DynamicPageTemplate.tsx index 29aa13f7520b3..99dcbe416a140 100644 --- a/packages/fiori/src/DynamicPageTemplate.tsx +++ b/packages/fiori/src/DynamicPageTemplate.tsx @@ -32,7 +32,7 @@ export default function DynamicPageTemplate(this: DynamicPage) {
{headerActions.call(this)}
diff --git a/packages/fiori/src/themes/DynamicPage.css b/packages/fiori/src/themes/DynamicPage.css index e53ca7dea16fe..a75fcc873a7af 100644 --- a/packages/fiori/src/themes/DynamicPage.css +++ b/packages/fiori/src/themes/DynamicPage.css @@ -5,6 +5,11 @@ z-index: 3; } +.ui5-dynamic-page-header-actions-wrapper { + /* when actionsInTitle, JS sets position:sticky inline; otherwise flows normally */ + z-index: 4; +} + :host { display: block; height: 100%; From 6cda7ad994db8868a77ddf22be22b9ccb383ba04 Mon Sep 17 00:00:00 2001 From: PetyaMarkovaBogdanova Date: Tue, 1 Sep 2026 08:45:57 +0300 Subject: [PATCH 05/13] fix(ui5-dynamic-page): toggle button reading fixed- #13990 --- packages/fiori/src/DynamicPage.ts | 6 ------ packages/fiori/src/DynamicPageTemplate.tsx | 8 +------- packages/fiori/src/themes/DynamicPage.css | 5 ----- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/packages/fiori/src/DynamicPage.ts b/packages/fiori/src/DynamicPage.ts index 69746b7d7e9da..05c203029d215 100644 --- a/packages/fiori/src/DynamicPage.ts +++ b/packages/fiori/src/DynamicPage.ts @@ -358,12 +358,6 @@ class DynamicPage extends UI5Element { return this.hidePinButton || isPhone(); } - get _actionsBarStickyTop(): number { - const titleHeight = this.dynamicPageTitle?.getBoundingClientRect().height || 0; - const headerHeight = this.headerInTitle ? (this.dynamicPageHeader?.getBoundingClientRect().height || 0) : 0; - return titleHeight + headerHeight; - } - /** * Defines if the header is snapped. * diff --git a/packages/fiori/src/DynamicPageTemplate.tsx b/packages/fiori/src/DynamicPageTemplate.tsx index 99dcbe416a140..faa222f9d7b4a 100644 --- a/packages/fiori/src/DynamicPageTemplate.tsx +++ b/packages/fiori/src/DynamicPageTemplate.tsx @@ -21,6 +21,7 @@ export default function DynamicPageTemplate(this: DynamicPage) { name="headerArea" > } + {headerActions.call(this)}
{this.headerInContent && @@ -30,13 +31,6 @@ export default function DynamicPageTemplate(this: DynamicPage) { > } -
- {headerActions.call(this)} -
-
Date: Tue, 1 Sep 2026 09:56:28 +0300 Subject: [PATCH 06/13] fix(ui5-dynamic-page): toggle button reading fixed --- packages/fiori/src/DynamicPage.ts | 4 ++++ packages/fiori/src/DynamicPageTemplate.tsx | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/fiori/src/DynamicPage.ts b/packages/fiori/src/DynamicPage.ts index 05c203029d215..8494b3e369f84 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 faa222f9d7b4a..b500ee2845f32 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" > } - {headerActions.call(this)} + {!this.headerActionsAfterHeader && headerActions.call(this)}
{this.headerInContent && @@ -30,6 +30,7 @@ export default function DynamicPageTemplate(this: DynamicPage) { name="headerArea" > } + {this.headerActionsAfterHeader && headerActions.call(this)}
Date: Tue, 1 Sep 2026 14:37:25 +0300 Subject: [PATCH 07/13] fix(ui5-dynamic-page): toggle button reading fixed --- .../fiori/cypress/specs/DynamicPage.cy.tsx | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/packages/fiori/cypress/specs/DynamicPage.cy.tsx b/packages/fiori/cypress/specs/DynamicPage.cy.tsx index 23847b6f4ba42..92aa2e93aa4b5 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( From 42122f30bc9378141c295e91a9cb01acfc9c0eb1 Mon Sep 17 00:00:00 2001 From: PetyaMarkovaBogdanova Date: Thu, 3 Sep 2026 18:07:15 +0300 Subject: [PATCH 08/13] fix(ui5-dynamic-page): toggle button reading fixed --- .../fiori/src/DynamicPageHeaderTemplate.tsx | 2 +- packages/fiori/src/DynamicPageTemplate.tsx | 22 +++++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/fiori/src/DynamicPageHeaderTemplate.tsx b/packages/fiori/src/DynamicPageHeaderTemplate.tsx index d399abb02aaa3..4e683f4308af1 100644 --- a/packages/fiori/src/DynamicPageHeaderTemplate.tsx +++ b/packages/fiori/src/DynamicPageHeaderTemplate.tsx @@ -2,7 +2,7 @@ import type DynamicPageHeader from "./DynamicPageHeader.js"; export default function DynamicPageHeaderTemplate(this: DynamicPageHeader) { return ( -
+
); diff --git a/packages/fiori/src/DynamicPageTemplate.tsx b/packages/fiori/src/DynamicPageTemplate.tsx index b500ee2845f32..e0768096c3dd2 100644 --- a/packages/fiori/src/DynamicPageTemplate.tsx +++ b/packages/fiori/src/DynamicPageTemplate.tsx @@ -16,21 +16,25 @@ export default function DynamicPageTemplate(this: DynamicPage) { > {this.headerInTitle && - +
+ +
} {!this.headerActionsAfterHeader && headerActions.call(this)}
{this.headerInContent && - +
+ + {this.headerActionsAfterHeader && headerActions.call(this)} +
} - {this.headerActionsAfterHeader && headerActions.call(this)}
Date: Thu, 3 Sep 2026 18:25:45 +0300 Subject: [PATCH 09/13] fix(ui5-dynamic-page): toggle button reading fixed --- .../fiori/cypress/specs/DynamicPage.cy.tsx | 145 ------------------ packages/fiori/src/DynamicPage.ts | 4 - .../fiori/src/DynamicPageHeaderTemplate.tsx | 2 +- packages/fiori/src/DynamicPageTemplate.tsx | 23 ++- 4 files changed, 10 insertions(+), 164 deletions(-) diff --git a/packages/fiori/cypress/specs/DynamicPage.cy.tsx b/packages/fiori/cypress/specs/DynamicPage.cy.tsx index 92aa2e93aa4b5..23847b6f4ba42 100644 --- a/packages/fiori/cypress/specs/DynamicPage.cy.tsx +++ b/packages/fiori/cypress/specs/DynamicPage.cy.tsx @@ -1197,151 +1197,6 @@ 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 8494b3e369f84..05c203029d215 100644 --- a/packages/fiori/src/DynamicPage.ts +++ b/packages/fiori/src/DynamicPage.ts @@ -312,10 +312,6 @@ 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/DynamicPageHeaderTemplate.tsx b/packages/fiori/src/DynamicPageHeaderTemplate.tsx index 4e683f4308af1..d399abb02aaa3 100644 --- a/packages/fiori/src/DynamicPageHeaderTemplate.tsx +++ b/packages/fiori/src/DynamicPageHeaderTemplate.tsx @@ -2,7 +2,7 @@ import type DynamicPageHeader from "./DynamicPageHeader.js"; export default function DynamicPageHeaderTemplate(this: DynamicPageHeader) { return ( -
+
); diff --git a/packages/fiori/src/DynamicPageTemplate.tsx b/packages/fiori/src/DynamicPageTemplate.tsx index e0768096c3dd2..faa222f9d7b4a 100644 --- a/packages/fiori/src/DynamicPageTemplate.tsx +++ b/packages/fiori/src/DynamicPageTemplate.tsx @@ -16,24 +16,19 @@ export default function DynamicPageTemplate(this: DynamicPage) { > {this.headerInTitle && -
- -
- } - {!this.headerActionsAfterHeader && headerActions.call(this)} -
- - {this.headerInContent && -
- {this.headerActionsAfterHeader && headerActions.call(this)} -
+ } + {headerActions.call(this)} +
+ + {this.headerInContent && + }
Date: Fri, 4 Sep 2026 10:18:09 +0300 Subject: [PATCH 10/13] fix(ui5-dynamic-page): toggle button reading fixed- #13990 --- packages/fiori/src/DynamicPage.ts | 6 ++++++ packages/fiori/src/DynamicPageTemplate.tsx | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/fiori/src/DynamicPage.ts b/packages/fiori/src/DynamicPage.ts index 05c203029d215..69746b7d7e9da 100644 --- a/packages/fiori/src/DynamicPage.ts +++ b/packages/fiori/src/DynamicPage.ts @@ -358,6 +358,12 @@ class DynamicPage extends UI5Element { return this.hidePinButton || isPhone(); } + get _actionsBarStickyTop(): number { + const titleHeight = this.dynamicPageTitle?.getBoundingClientRect().height || 0; + const headerHeight = this.headerInTitle ? (this.dynamicPageHeader?.getBoundingClientRect().height || 0) : 0; + return titleHeight + headerHeight; + } + /** * Defines if the header is snapped. * diff --git a/packages/fiori/src/DynamicPageTemplate.tsx b/packages/fiori/src/DynamicPageTemplate.tsx index faa222f9d7b4a..3da5eaeb75d17 100644 --- a/packages/fiori/src/DynamicPageTemplate.tsx +++ b/packages/fiori/src/DynamicPageTemplate.tsx @@ -21,7 +21,6 @@ export default function DynamicPageTemplate(this: DynamicPage) { name="headerArea" > } - {headerActions.call(this)}
{this.headerInContent && @@ -31,6 +30,13 @@ export default function DynamicPageTemplate(this: DynamicPage) { > } +
+ {headerActions.call(this)} +
+
Date: Fri, 4 Sep 2026 13:43:10 +0300 Subject: [PATCH 11/13] fix(ui5-dynamic-page): toggle button reading fixed- #13990 --- packages/fiori/src/DynamicPageHeaderActionsTemplate.tsx | 4 ++-- packages/fiori/src/DynamicPageTemplate.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/fiori/src/DynamicPageHeaderActionsTemplate.tsx b/packages/fiori/src/DynamicPageHeaderActionsTemplate.tsx index 0ddcdf8208ab1..6ee405214968e 100644 --- a/packages/fiori/src/DynamicPageHeaderActionsTemplate.tsx +++ b/packages/fiori/src/DynamicPageHeaderActionsTemplate.tsx @@ -12,7 +12,7 @@ export default function DynamicPageHeaderActionsTemplate(this: DynamicPageHeader icon={this.arrowButtonIcon} accessibleName={this.expandLabel} accessibilityAttributes={this.accessibilityAttributes} - tooltip={this.expandLabel} + onMouseOver={this.onExpandHoverIn} onMouseOut={this.onExpandHoverOut} /> @@ -24,7 +24,7 @@ export default function DynamicPageHeaderActionsTemplate(this: DynamicPageHeader pressed={this.pinned} accessibilityAttributes={this.accessibilityAttributes} accessibleName={this.pinLabel} - tooltip={this.pinLabel} + /> }
diff --git a/packages/fiori/src/DynamicPageTemplate.tsx b/packages/fiori/src/DynamicPageTemplate.tsx index 3da5eaeb75d17..29aa13f7520b3 100644 --- a/packages/fiori/src/DynamicPageTemplate.tsx +++ b/packages/fiori/src/DynamicPageTemplate.tsx @@ -32,7 +32,7 @@ export default function DynamicPageTemplate(this: DynamicPage) {
{headerActions.call(this)}
From d55982e0e46e17af26bf1ee8aa3078f8de877b34 Mon Sep 17 00:00:00 2001 From: PetyaMarkovaBogdanova Date: Fri, 4 Sep 2026 15:23:26 +0300 Subject: [PATCH 12/13] fix(ui5-dynamic-page): toggle button reading fixed- #13990 --- packages/fiori/src/DynamicPageHeaderActionsTemplate.tsx | 2 -- packages/fiori/src/themes/DynamicPage.css | 5 +++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/fiori/src/DynamicPageHeaderActionsTemplate.tsx b/packages/fiori/src/DynamicPageHeaderActionsTemplate.tsx index 6ee405214968e..1a770f30a4915 100644 --- a/packages/fiori/src/DynamicPageHeaderActionsTemplate.tsx +++ b/packages/fiori/src/DynamicPageHeaderActionsTemplate.tsx @@ -12,7 +12,6 @@ export default function DynamicPageHeaderActionsTemplate(this: DynamicPageHeader icon={this.arrowButtonIcon} accessibleName={this.expandLabel} accessibilityAttributes={this.accessibilityAttributes} - onMouseOver={this.onExpandHoverIn} onMouseOut={this.onExpandHoverOut} /> @@ -24,7 +23,6 @@ export default function DynamicPageHeaderActionsTemplate(this: DynamicPageHeader pressed={this.pinned} accessibilityAttributes={this.accessibilityAttributes} accessibleName={this.pinLabel} - /> }
diff --git a/packages/fiori/src/themes/DynamicPage.css b/packages/fiori/src/themes/DynamicPage.css index e53ca7dea16fe..251883241ca98 100644 --- a/packages/fiori/src/themes/DynamicPage.css +++ b/packages/fiori/src/themes/DynamicPage.css @@ -5,6 +5,11 @@ z-index: 3; } +.ui5-dynamic-page-header-actions-wrapper { + position: relative; + z-index: 4; +} + :host { display: block; height: 100%; From 12780214c8b7efd17e3f89e4a16aeb96f58a8fe3 Mon Sep 17 00:00:00 2001 From: PetyaMarkovaBogdanova Date: Fri, 4 Sep 2026 17:43:38 +0300 Subject: [PATCH 13/13] fix(ui5-dynamic-page): toggle button reading fixed --- .../fiori/cypress/specs/DynamicPage.cy.tsx | 115 ++++++++++++++++-- .../src/DynamicPageHeaderActionsTemplate.tsx | 2 + packages/fiori/src/DynamicPageTemplate.tsx | 2 +- packages/fiori/src/themes/DynamicPage.css | 3 +- 4 files changed, 112 insertions(+), 10 deletions(-) diff --git a/packages/fiori/cypress/specs/DynamicPage.cy.tsx b/packages/fiori/cypress/specs/DynamicPage.cy.tsx index 23847b6f4ba42..1b9bdbb798b9b 100644 --- a/packages/fiori/cypress/specs/DynamicPage.cy.tsx +++ b/packages/fiori/cypress/specs/DynamicPage.cy.tsx @@ -1329,37 +1329,35 @@ describe("ARIA attributes", () => { cy.get("[ui5-dynamic-page]").as("dynamicPage"); - // Initially the header should not be pinned, so tooltip should be "Pin Header" cy.get("@dynamicPage") .shadow() .find("[ui5-dynamic-page-header-actions]") .shadow() .find(".ui5-dynamic-page-header-action-pin") - .should("have.attr", "tooltip", "Pin Header"); + .should("have.prop", "accessibleName", "Pin Header") + .should("have.prop", "tooltip", "Pin Header"); - // Pin the header cy.get("@dynamicPage") .invoke("prop", "headerPinned", true); - // After pinning, tooltip should change to "Unpin Header" cy.get("@dynamicPage") .shadow() .find("[ui5-dynamic-page-header-actions]") .shadow() .find(".ui5-dynamic-page-header-action-pin") - .should("have.attr", "tooltip", "Unpin Header"); + .should("have.prop", "accessibleName", "Unpin Header") + .should("have.prop", "tooltip", "Unpin Header"); - // Unpin the header cy.get("@dynamicPage") .invoke("prop", "headerPinned", false); - // After unpinning, tooltip should change back to "Pin Header" cy.get("@dynamicPage") .shadow() .find("[ui5-dynamic-page-header-actions]") .shadow() .find(".ui5-dynamic-page-header-action-pin") - .should("have.attr", "tooltip", "Pin Header"); + .should("have.prop", "accessibleName", "Pin Header") + .should("have.prop", "tooltip", "Pin Header"); }); it("should use default aria-label based on header state", () => { @@ -1478,4 +1476,105 @@ describe("ARIA attributes", () => { .should("have.attr", "role", "contentinfo") .should("have.attr", "aria-label", "Page Footer"); }); +}); + +describe("Header actions DOM order and screen reader reading", () => { + it("renders header actions wrapper outside the banner landmark", () => { + cy.mount( + + +
Page Title
+
+ +
Header Content
+
+
Content
+
+ ); + + cy.get("[ui5-dynamic-page]") + .shadow() + .find(".ui5-dynamic-page-title-header-wrapper .ui5-dynamic-page-header-actions-wrapper") + .should("not.exist"); + + cy.get("[ui5-dynamic-page]") + .shadow() + .find(".ui5-dynamic-page-scroll-container > .ui5-dynamic-page-header-actions-wrapper") + .should("exist"); + }); + + it("renders header actions after the header slot in DOM order when header is expanded", () => { + cy.mount( + + +
Page Title
+
+ +
Header Content
+
+
Content
+
+ ); + + cy.get("[ui5-dynamic-page]") + .shadow() + .find(".ui5-dynamic-page-scroll-container") + .then($container => { + const children = [...$container[0].children]; + const headerSlotIdx = children.findIndex(el => el.nodeName === "SLOT" && el.getAttribute("name") === "headerArea"); + const actionsIdx = children.findIndex(el => el.classList.contains("ui5-dynamic-page-header-actions-wrapper")); + expect(actionsIdx).to.be.greaterThan(headerSlotIdx); + }); + }); + + it("applies sticky positioning to the actions wrapper when the header is snapped", () => { + cy.mount( + + +
Page Title
+
+ +
Header Content
+
+
Content
+
+ ); + + cy.get("[ui5-dynamic-page]") + .shadow() + .find(".ui5-dynamic-page-header-actions-wrapper") + .should("not.have.attr", "style"); + + cy.get("[ui5-dynamic-page]") + .invoke("prop", "headerSnapped", true); + + cy.get("[ui5-dynamic-page]") + .shadow() + .find(".ui5-dynamic-page-header-actions-wrapper") + .should("have.attr", "style") + .and("include", "--_ui5_dp_header_actions_top"); + }); + + it("applies sticky positioning to the actions wrapper when the header is 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-header-actions-wrapper") + .should("have.attr", "style") + .and("include", "--_ui5_dp_header_actions_top"); + }); }); \ No newline at end of file diff --git a/packages/fiori/src/DynamicPageHeaderActionsTemplate.tsx b/packages/fiori/src/DynamicPageHeaderActionsTemplate.tsx index 1a770f30a4915..0ddcdf8208ab1 100644 --- a/packages/fiori/src/DynamicPageHeaderActionsTemplate.tsx +++ b/packages/fiori/src/DynamicPageHeaderActionsTemplate.tsx @@ -12,6 +12,7 @@ export default function DynamicPageHeaderActionsTemplate(this: DynamicPageHeader icon={this.arrowButtonIcon} accessibleName={this.expandLabel} accessibilityAttributes={this.accessibilityAttributes} + tooltip={this.expandLabel} onMouseOver={this.onExpandHoverIn} onMouseOut={this.onExpandHoverOut} /> @@ -23,6 +24,7 @@ export default function DynamicPageHeaderActionsTemplate(this: DynamicPageHeader pressed={this.pinned} accessibilityAttributes={this.accessibilityAttributes} accessibleName={this.pinLabel} + tooltip={this.pinLabel} /> } diff --git a/packages/fiori/src/DynamicPageTemplate.tsx b/packages/fiori/src/DynamicPageTemplate.tsx index 29aa13f7520b3..ab086dd62ef98 100644 --- a/packages/fiori/src/DynamicPageTemplate.tsx +++ b/packages/fiori/src/DynamicPageTemplate.tsx @@ -32,7 +32,7 @@ export default function DynamicPageTemplate(this: DynamicPage) {
{headerActions.call(this)}
diff --git a/packages/fiori/src/themes/DynamicPage.css b/packages/fiori/src/themes/DynamicPage.css index 251883241ca98..aab49c86df5e9 100644 --- a/packages/fiori/src/themes/DynamicPage.css +++ b/packages/fiori/src/themes/DynamicPage.css @@ -6,7 +6,8 @@ } .ui5-dynamic-page-header-actions-wrapper { - position: relative; + position: sticky; + top: var(--_ui5_dp_header_actions_top, auto); z-index: 4; }