From bdfa6e9c661ce3c54e68654d44115814bb3dbb4f Mon Sep 17 00:00:00 2001 From: Dobrin Dimchev Date: Wed, 24 Jun 2026 17:31:23 +0300 Subject: [PATCH 1/2] fix(ui5-shellbar): ignore non-ShellBarItem children in overflow calculation A stray element in the default slot (e.g. a bare ) was included in the overflow algorithm. Without _id / stableDomRef, every recalculation wrote undefined back into reactive properties and re-rendered the wrapper with key={undefined}, so widths never converged and RenderQueue threw "Web component processed too many times this task, max allowed is: 10" when resizing across the overflow breakpoint. Filter `this.items` through a new `isInstanceOfShellBarItem` checker at every read site (overflow algorithm, items-overflow-state update, and template), so only real ui5-shellbar-item instances participate in the overflow calculation and the rendered wrapper list. Adds a Cypress regression test that oscillates viewports across the overflow breakpoint with a stray in the default slot and asserts no "processed too many times" error fires. --- packages/fiori/cypress/specs/ShellBar.cy.tsx | 52 ++++++++++++++++++++ packages/fiori/src/ShellBar.ts | 19 +++++-- packages/fiori/src/ShellBarItem.ts | 8 +++ packages/fiori/src/ShellBarTemplate.tsx | 2 +- 4 files changed, 77 insertions(+), 4 deletions(-) diff --git a/packages/fiori/cypress/specs/ShellBar.cy.tsx b/packages/fiori/cypress/specs/ShellBar.cy.tsx index 10593b3193c0..179bb4795920 100644 --- a/packages/fiori/cypress/specs/ShellBar.cy.tsx +++ b/packages/fiori/cypress/specs/ShellBar.cy.tsx @@ -1877,4 +1877,56 @@ describe("Start button spacing", () => { cy.get("[ui5-shellbar]").shadow().find(".ui5-shellbar-start-button").should("have.css", "gap", "8px"); }); +}); + +describe("Non-ShellBarItem children in default slot", () => { + it("should not throw 'processed too many times' when resizing across the overflow breakpoint with a stray in the default slot", () => { + // Capture any errors thrown during the run — the bug surfaced as an + // uncaught Error: "Web component processed too many times this task, + // max allowed is: 10" from RenderQueue when the overflow algorithm + // failed to converge. + const errors: string[] = []; + cy.on("uncaught:exception", (err) => { + errors.push(err.message); + return false; // don't fail the test here; we assert below + }); + + cy.mount( + + + + Product Identifier + + + + {/* The problem child — a stray non-ShellBarItem element in the default slot. */} + + + + + + ); + + cy.wait(RESIZE_THROTTLE_RATE); + + // Oscillate across the overflow breakpoint several times. This is what + // triggered the runaway re-render in the regression — a single resize + // is not enough; the algorithm has to be invoked while the previous + // pass is still settling. + for (let i = 0; i < 6; i++) { + cy.viewport(1400, 800); + cy.wait(RESIZE_THROTTLE_RATE); + cy.viewport(320, 800); + cy.wait(RESIZE_THROTTLE_RATE); + } + + // ShellBar must still be in the DOM and operating after the oscillation. + cy.get("#shellbar-stray").should("exist"); + + // And no "processed too many times" error must have fired. + cy.then(() => { + const matches = errors.filter(e => /processed too many times/i.test(e)); + expect(matches, `unexpected RenderQueue errors:\n${matches.join("\n")}`).to.have.length(0); + }); + }); }); \ No newline at end of file diff --git a/packages/fiori/src/ShellBar.ts b/packages/fiori/src/ShellBar.ts index 3061e6069183..c4dd977989e6 100644 --- a/packages/fiori/src/ShellBar.ts +++ b/packages/fiori/src/ShellBar.ts @@ -44,6 +44,7 @@ import ShellBarAccessibility from "./shellbar/ShellBarAccessibility.js"; import ShellBarItemNavigation from "./shellbar/ShellBarItemNavigation.js"; import ShellBarItem from "./ShellBarItem.js"; +import { isInstanceOfShellBarItem } from "./ShellBarItem.js"; import ShellBarSpacer from "./ShellBarSpacer.js"; import type ShellBarBranding from "./ShellBarBranding.js"; import type { ShellBarOverflowResult } from "./shellbar/ShellBarOverflow.js"; @@ -764,7 +765,7 @@ class ShellBar extends UI5Element { const result = this.overflow.updateOverflow({ actions: this.actions, content: this.sortContent(this.content), - customItems: this.items, + customItems: this._validItems, hiddenItemsIds: this.hiddenItemsIds, showSearchField: this.enabledFeatures.search && this.showSearchField, overflowOuter: this.overflowOuter!, @@ -786,7 +787,7 @@ class ShellBar extends UI5Element { const { hiddenItemsIds, showOverflowButton } = result; // Update items overflow state - this.items.forEach(item => { + this._validItems.forEach(item => { item.inOverflow = hiddenItemsIds.includes(item._id); if (item.inOverflow) { // clear the hidden class to ensure the item is visible in the overflow popover @@ -862,11 +863,23 @@ class ShellBar extends UI5Element { get overflowItems() { return this.overflow.getOverflowItems({ actions: this.actions, - customItems: this.items, + customItems: this._validItems, hiddenItemsIds: this.hiddenItemsIds, }); } + /** + * Only entries that are actually `ui5-shellbar-item` instances participate in the + * overflow calculation and template rendering. The default slot's type is + * `HTMLElement`, so any stray child (e.g. a bare ``) ends up in `this.items`; + * if such an element reaches the overflow algorithm it has no `_id` / `stableDomRef`, + * which writes `undefined` back into reactive properties on every pass and re-enters + * the render queue until `RenderQueue` throws "processed too many times". + */ + get _validItems(): ShellBarItem[] { + return this.items.filter(isInstanceOfShellBarItem); + } + /** * Returns badge text for overflow button. * Shows count if only one item with count is overflowed, otherwise shows attention dot. diff --git a/packages/fiori/src/ShellBarItem.ts b/packages/fiori/src/ShellBarItem.ts index e7439ba49c46..14691977d1d5 100644 --- a/packages/fiori/src/ShellBarItem.ts +++ b/packages/fiori/src/ShellBarItem.ts @@ -3,6 +3,7 @@ import property from "@ui5/webcomponents-base/dist/decorators/property.js"; import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js"; import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js"; import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js"; +import createInstanceChecker from "@ui5/webcomponents-base/dist/util/createInstanceChecker.js"; import type { AccessibilityAttributes, UI5CustomEvent } from "@ui5/webcomponents-base"; import Button from "@ui5/webcomponents/dist/Button.js"; import ButtonBadge from "@ui5/webcomponents/dist/ButtonBadge.js"; @@ -109,6 +110,10 @@ class ShellBarItem extends UI5Element { return this.getAttribute("stable-dom-ref") || `${this._id}-stable-dom-ref`; } + get isShellBarItem(): boolean { + return true; + } + hasListItems() { return this.inOverflow; } @@ -130,5 +135,8 @@ class ShellBarItem extends UI5Element { ShellBarItem.define(); +const isInstanceOfShellBarItem = createInstanceChecker("isShellBarItem"); + export default ShellBarItem; +export { isInstanceOfShellBarItem }; export type { ShellBarItemClickEventDetail, ShellBarItemAccessibilityAttributes }; diff --git a/packages/fiori/src/ShellBarTemplate.tsx b/packages/fiori/src/ShellBarTemplate.tsx index c77aea90fc98..8aec6325cfab 100644 --- a/packages/fiori/src/ShellBarTemplate.tsx +++ b/packages/fiori/src/ShellBarTemplate.tsx @@ -153,7 +153,7 @@ export default function ShellBarTemplate(this: ShellBar) { )} {/* Custom Items */} - {this.items.map(item => ( + {this._validItems.map(item => (
Date: Wed, 24 Jun 2026 17:38:12 +0300 Subject: [PATCH 2/2] chore(ui5-shellbar): align checker export with repo convention - Combine ShellBarItem default+named import in ShellBar.ts - Inline 'export const isInstanceOfShellBarItem = createInstanceChecker(...)' in ShellBarItem.ts to match the pattern used by MenuItem, SideNavigationItem, UserSettingsAppearanceViewGroup, etc. Addresses review feedback on #13729. --- packages/fiori/src/ShellBar.ts | 3 +-- packages/fiori/src/ShellBarItem.ts | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/fiori/src/ShellBar.ts b/packages/fiori/src/ShellBar.ts index c4dd977989e6..914177eafb69 100644 --- a/packages/fiori/src/ShellBar.ts +++ b/packages/fiori/src/ShellBar.ts @@ -43,8 +43,7 @@ import ShellBarOverflow from "./shellbar/ShellBarOverflow.js"; import ShellBarAccessibility from "./shellbar/ShellBarAccessibility.js"; import ShellBarItemNavigation from "./shellbar/ShellBarItemNavigation.js"; -import ShellBarItem from "./ShellBarItem.js"; -import { isInstanceOfShellBarItem } from "./ShellBarItem.js"; +import ShellBarItem, { isInstanceOfShellBarItem } from "./ShellBarItem.js"; import ShellBarSpacer from "./ShellBarSpacer.js"; import type ShellBarBranding from "./ShellBarBranding.js"; import type { ShellBarOverflowResult } from "./shellbar/ShellBarOverflow.js"; diff --git a/packages/fiori/src/ShellBarItem.ts b/packages/fiori/src/ShellBarItem.ts index 14691977d1d5..7f9e8698cef2 100644 --- a/packages/fiori/src/ShellBarItem.ts +++ b/packages/fiori/src/ShellBarItem.ts @@ -135,8 +135,6 @@ class ShellBarItem extends UI5Element { ShellBarItem.define(); -const isInstanceOfShellBarItem = createInstanceChecker("isShellBarItem"); - export default ShellBarItem; -export { isInstanceOfShellBarItem }; +export const isInstanceOfShellBarItem = createInstanceChecker("isShellBarItem"); export type { ShellBarItemClickEventDetail, ShellBarItemAccessibilityAttributes };