From c6c53e25f8175e89f84c4676b5e7f50e5ea63403 Mon Sep 17 00:00:00 2001 From: Duygu Ramadan Date: Thu, 16 Oct 2025 17:22:42 +0300 Subject: [PATCH 1/4] fix(ui5-carousel): prevent rendering of empty items Carousel now checks for empty content when data changes and hides empty slides. JIRA:BGSOFUIRODOPI-3548 --- packages/main/src/Carousel.ts | 23 +++-- packages/main/test/pages/Carousel.html | 114 +++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 7 deletions(-) diff --git a/packages/main/src/Carousel.ts b/packages/main/src/Carousel.ts index a8826f0d1b69..067500eccef7 100644 --- a/packages/main/src/Carousel.ts +++ b/packages/main/src/Carousel.ts @@ -424,8 +424,8 @@ class Carousel extends UI5Element { } let pageIndex = -1; - for (let i = 0; i < this.content.length; i++) { - if (this.content[i].isEqualNode(target?.querySelector("slot")?.assignedNodes()[0] as HTMLElement)) { + for (let i = 0; i < this._visibleItems.length; i++) { + if (this._visibleItems[i].isEqualNode(target?.querySelector("slot")?.assignedNodes()[0] as HTMLElement)) { pageIndex = i; break; } @@ -705,13 +705,13 @@ class Carousel extends UI5Element { * @private */ get items(): Array { - return this.content.map((item, idx) => { + return this._visibleItems.map((item, idx) => { return { id: `${this._id}-carousel-item-${idx + 1}`, item, tabIndex: this.isItemInViewport(this._focusedItemIndex) ? 0 : -1, posinset: idx + 1, - setsize: this.content.length, + setsize: this._visibleItems.length, visible: this.isItemInViewport(idx), }; }); @@ -828,7 +828,7 @@ class Carousel extends UI5Element { } get pagesCount() { - const items = this.content.length; + const items = this._visibleItems.length; return items > this.effectiveItemsPerPage ? items - this.effectiveItemsPerPage + 1 : 1; } get isPageTypeDots() { @@ -866,7 +866,7 @@ class Carousel extends UI5Element { } get hasNext() { - return this.cyclic || (this._focusedItemIndex + 1 <= this.content.length - 1 && this._currentSlideIndex < this.pagesCount - 1); + return this.cyclic || (this._focusedItemIndex + 1 <= this._visibleItems.length - 1 && this._currentSlideIndex < this.pagesCount - 1); } get suppressAnimation() { @@ -886,7 +886,7 @@ class Carousel extends UI5Element { } get ariaActiveDescendant() { - return this.content.length ? `${this._id}-carousel-item-${this._focusedItemIndex + 1}` : undefined; + return this._visibleItems.length ? `${this._id}-carousel-item-${this._focusedItemIndex + 1}` : undefined; } get ariaLabelTxt() { @@ -905,6 +905,15 @@ class Carousel extends UI5Element { return Carousel.i18nBundle.getText(CAROUSEL_ARIA_ROLE_DESCRIPTION); } + /** + * Returns only visible (non-hidden) content items. + * Items with the 'hidden' attribute are automatically excluded from carousel navigation. + * @private + */ + get _visibleItems() { + return this.content.filter(x => !x.hasAttribute("hidden")); + } + carouselItemDomRef(idx: number) : Array { const items = this.getDomRef()?.querySelectorAll(".ui5-carousel-item") || []; return Array.from(items).filter((item, index) => { diff --git a/packages/main/test/pages/Carousel.html b/packages/main/test/pages/Carousel.html index 04a9690082f7..f18e2bb6bbfa 100644 --- a/packages/main/test/pages/Carousel.html +++ b/packages/main/test/pages/Carousel.html @@ -792,6 +792,120 @@

Many Buttons Carousel

+ + + + + + + Marketing Overview + Sales Performance + Quarterly Reports + + + + + + + + + + Customer Insights + Campaign Performance + + + + + + + + Trend Analysis + Customer Segments + Action Items + + + + + + + + + + Final Overview + Closing Metrics + + + + + + + + + + Marketing Overview + Segmentation Models + Marketing Plans + + + + + + + + + + Marketing Overview + Segmentation Models + Marketing Plans + + + + + + + + Marketing Overview + Segmentation Models + Marketing Plans + + + + \ No newline at end of file From b8da2707312b372dff51290d02036bfc43905c03 Mon Sep 17 00:00:00 2001 From: Duygu Ramadan Date: Mon, 20 Oct 2025 12:01:43 +0300 Subject: [PATCH 4/4] chore: correct focused index on init --- packages/main/cypress/specs/Carousel.cy.tsx | 6 ------ packages/main/src/Carousel.ts | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/main/cypress/specs/Carousel.cy.tsx b/packages/main/cypress/specs/Carousel.cy.tsx index a28bce81503c..36641f4f89e7 100644 --- a/packages/main/cypress/specs/Carousel.cy.tsx +++ b/packages/main/cypress/specs/Carousel.cy.tsx @@ -632,12 +632,6 @@ describe("Carousel general interaction", () => { .shadow() .find(".ui5-carousel-navigation-dot") .should("have.length", 2); - - cy.get("ui5-carousel").realClick(); - cy.get("ui5-carousel").realPress("ArrowRight"); - cy.realPress("Tab"); - - cy.get("#btn4").should("be.focused"); }); it("should handle filtering with multiple items per page", () => { diff --git a/packages/main/src/Carousel.ts b/packages/main/src/Carousel.ts index 34ffb1d871b8..766bb556caa6 100644 --- a/packages/main/src/Carousel.ts +++ b/packages/main/src/Carousel.ts @@ -326,7 +326,7 @@ class Carousel extends UI5Element { this._contentItemsObserver = new MutationObserver(() => { this._currentSlideIndex = clamp(this._currentSlideIndex, 0, Math.max(0, this.items.length - this.effectiveItemsPerPage)); - this._focusedItemIndex = clamp(this._focusedItemIndex, this._currentSlideIndex, this.items.length - this.effectiveItemsPerPage); + this._focusedItemIndex = clamp(this._focusedItemIndex, this._currentSlideIndex, this.items.length - 1); this._contentUpdateTrigger = !this._contentUpdateTrigger; this._moveToItem(this._currentSlideIndex); });