From bcd88dad8ec12691520dcf08798d93b7f15fdf74 Mon Sep 17 00:00:00 2001 From: Siyana Todorova Date: Fri, 3 Jul 2026 14:59:08 +0300 Subject: [PATCH 1/5] fix: change approach --- packages/fiori/src/SideNavigation.ts | 54 +++++++++++++++++++ packages/fiori/src/SideNavigationItemBase.ts | 3 ++ packages/fiori/src/themes/SideNavigation.css | 10 ++++ .../src/themes/SideNavigationItemBase.css | 4 ++ 4 files changed, 71 insertions(+) diff --git a/packages/fiori/src/SideNavigation.ts b/packages/fiori/src/SideNavigation.ts index 046031c798a71..8309aefb35c5c 100644 --- a/packages/fiori/src/SideNavigation.ts +++ b/packages/fiori/src/SideNavigation.ts @@ -1,5 +1,6 @@ import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; import type { Slot, DefaultSlot } from "@ui5/webcomponents-base/dist/UI5Element.js"; +import type { ChangeInfo } from "@ui5/webcomponents-base/dist/UI5Element.js"; import { createMultiInstanceChecker } from "@ui5/webcomponents-base/dist/util/createMultiInstanceChecker.js"; import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js"; import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; @@ -237,6 +238,16 @@ class SideNavigation extends UI5Element { } _handleResizeBound: () => void; + _fnTransitionEnd?: (event: TransitionEvent) => void; + _bAnimating = false; + + onInvalidation(changeInfo: ChangeInfo) { + if (changeInfo.type === "property" && changeInfo.name === "collapsed") { + if (this.getDomRef()) { + this._bAnimating = true; + } + } + } onBeforeRendering() { super.onBeforeRendering(); @@ -247,6 +258,7 @@ class SideNavigation extends UI5Element { item.sideNavCollapsed = this.collapsed; item.inPopover = this.inPopover; item.sideNavigation = this; + item.sideNavAnimating = this._bAnimating; }); this.initGroupsSettings(this.items); @@ -497,6 +509,8 @@ class SideNavigation extends UI5Element { if (this.collapsed) { this.handleResize(); } + + this._handleExpandCollapseAnimation(); } onEnterDOM() { @@ -505,6 +519,10 @@ class SideNavigation extends UI5Element { onExitDOM() { ResizeHandler.deregister(this, this._handleResizeBound); + if (this._fnTransitionEnd) { + this.removeEventListener("transitionend", this._fnTransitionEnd); + this._fnTransitionEnd = undefined; + } } handleResize() { @@ -622,6 +640,42 @@ class SideNavigation extends UI5Element { return isPhone() || window.innerWidth < SCREEN_WIDTH_BREAKPOINT; } + _handleExpandCollapseAnimation() { + if (!this._bAnimating) { + return; + } + + const oDomRef = this.getDomRef(); + if (!oDomRef) { + return; + } + + oDomRef.classList.add("ui5-sn-animating"); + + if (this._fnTransitionEnd) { + this.removeEventListener("transitionend", this._fnTransitionEnd); + } + + this._fnTransitionEnd = (oEvent: TransitionEvent) => { + if (oEvent.propertyName !== "width" && oEvent.propertyName !== "min-width") { + return; + } + + oDomRef.classList.remove("ui5-sn-animating"); + this.removeEventListener("transitionend", this._fnTransitionEnd!); + this._fnTransitionEnd = undefined; + this._bAnimating = false; + + this._getAllItems(this.items) + .concat(this._getAllItems(this.fixedItems)) + .forEach(item => { + item.sideNavAnimating = false; + }); + }; + + this.addEventListener("transitionend", this._fnTransitionEnd); + } + _handleItemClick(e: KeyboardEvent | MouseEvent, item: SideNavigationSelectableItemBase) { this.fireDecoratorEvent("item-click", { item }); diff --git a/packages/fiori/src/SideNavigationItemBase.ts b/packages/fiori/src/SideNavigationItemBase.ts index a1781507a77a8..466bd786cd8ac 100644 --- a/packages/fiori/src/SideNavigationItemBase.ts +++ b/packages/fiori/src/SideNavigationItemBase.ts @@ -79,6 +79,9 @@ class SideNavigationItemBase extends UI5Element implements ITabbable { @property({ type: Boolean }) sideNavCollapsed = false; + @property({ type: Boolean }) + sideNavAnimating = false; + @property({ type: Boolean }) inPopover = false; diff --git a/packages/fiori/src/themes/SideNavigation.css b/packages/fiori/src/themes/SideNavigation.css index 47fdadb8fa4b6..c2379289e2a10 100644 --- a/packages/fiori/src/themes/SideNavigation.css +++ b/packages/fiori/src/themes/SideNavigation.css @@ -35,6 +35,10 @@ border-radius: inherit; } +.ui5-sn-root.ui5-sn-animating { + overflow: hidden; +} + .ui5-sn-flexible { display: flex; flex-direction: column; @@ -48,6 +52,12 @@ position: relative; } +.ui5-sn-root.ui5-sn-animating .ui5-sn-list.ui5-sn-flexible, +.ui5-sn-root.ui5-sn-animating .ui5-sn-list.ui5-sn-fixed { + width: var(--_ui5_side_navigation_width); + overflow: hidden; +} + .ui5-sn-list { margin: 0; list-style: none; diff --git a/packages/fiori/src/themes/SideNavigationItemBase.css b/packages/fiori/src/themes/SideNavigationItemBase.css index a2f94abb16ba6..0645081b1bc7b 100644 --- a/packages/fiori/src/themes/SideNavigationItemBase.css +++ b/packages/fiori/src/themes/SideNavigationItemBase.css @@ -261,6 +261,10 @@ and there is an additional border that appears on hover. */ justify-content: center; } +:host([side-nav-collapsed][side-nav-animating]) .ui5-sn-item { + justify-content: flex-start; +} + :host([slot="fixedItems"]:not(side-nav-collapsed)) .ui5-sn-item.ui5-sn-item-level1 { margin-top: var(--_ui5_side_navigation_first_fixed_item_margin_top); /* space for the focus*/ } From d6a8abc223280db6da0f56d5edf5a37a587f3637 Mon Sep 17 00:00:00 2001 From: Siyana Todorova Date: Tue, 7 Jul 2026 09:28:49 +0300 Subject: [PATCH 2/5] fix: eslint imports --- packages/fiori/src/SideNavigation.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/fiori/src/SideNavigation.ts b/packages/fiori/src/SideNavigation.ts index 8309aefb35c5c..081e8b123515e 100644 --- a/packages/fiori/src/SideNavigation.ts +++ b/packages/fiori/src/SideNavigation.ts @@ -1,6 +1,4 @@ -import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; -import type { Slot, DefaultSlot } from "@ui5/webcomponents-base/dist/UI5Element.js"; -import type { ChangeInfo } from "@ui5/webcomponents-base/dist/UI5Element.js"; +import UI5Element, { type Slot, type DefaultSlot, type ChangeInfo } from "@ui5/webcomponents-base/dist/UI5Element.js"; import { createMultiInstanceChecker } from "@ui5/webcomponents-base/dist/util/createMultiInstanceChecker.js"; import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js"; import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; From 992420c7c159e9162573eab4a5284437d928c14e Mon Sep 17 00:00:00 2001 From: Siyana Todorova Date: Wed, 8 Jul 2026 13:46:12 +0300 Subject: [PATCH 3/5] chore: fix lint error --- packages/fiori/src/SideNavigation.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/fiori/src/SideNavigation.ts b/packages/fiori/src/SideNavigation.ts index 081e8b123515e..ad2018a22ae4f 100644 --- a/packages/fiori/src/SideNavigation.ts +++ b/packages/fiori/src/SideNavigation.ts @@ -1,4 +1,5 @@ -import UI5Element, { type Slot, type DefaultSlot, type ChangeInfo } from "@ui5/webcomponents-base/dist/UI5Element.js"; +import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; +import type { Slot, DefaultSlot, ChangeInfo } from "@ui5/webcomponents-base/dist/UI5Element.js"; import { createMultiInstanceChecker } from "@ui5/webcomponents-base/dist/util/createMultiInstanceChecker.js"; import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js"; import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; From f65ab53bd15e6f8e2ffbd1f8b1791608b0729137 Mon Sep 17 00:00:00 2001 From: Siyana Todorova Date: Mon, 20 Jul 2026 16:44:04 +0300 Subject: [PATCH 4/5] chore: address review comments --- packages/fiori/src/SideNavigation.ts | 40 +++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/packages/fiori/src/SideNavigation.ts b/packages/fiori/src/SideNavigation.ts index ad2018a22ae4f..77d7bb0c4f4a2 100644 --- a/packages/fiori/src/SideNavigation.ts +++ b/packages/fiori/src/SideNavigation.ts @@ -15,6 +15,7 @@ import ItemNavigation from "@ui5/webcomponents-base/dist/delegate/ItemNavigation import type { ITabbable } from "@ui5/webcomponents-base/dist/delegate/ItemNavigation.js"; import { isPhone } from "@ui5/webcomponents-base/dist/Device.js"; import createInstanceChecker from "@ui5/webcomponents-base/dist/util/createInstanceChecker.js"; +import { getAnimationMode } from "@ui5/webcomponents-base/dist/config/AnimationMode.js"; import NavigationMode from "@ui5/webcomponents-base/dist/types/NavigationMode.js"; import type SideNavigationItemBase from "./SideNavigationItemBase.js"; @@ -238,11 +239,12 @@ class SideNavigation extends UI5Element { _handleResizeBound: () => void; _fnTransitionEnd?: (event: TransitionEvent) => void; + _animationTimeoutId?: ReturnType; _bAnimating = false; onInvalidation(changeInfo: ChangeInfo) { if (changeInfo.type === "property" && changeInfo.name === "collapsed") { - if (this.getDomRef()) { + if (this.getDomRef() && getAnimationMode() !== "none") { this._bAnimating = true; } } @@ -522,6 +524,11 @@ class SideNavigation extends UI5Element { this.removeEventListener("transitionend", this._fnTransitionEnd); this._fnTransitionEnd = undefined; } + if (this._animationTimeoutId) { + clearTimeout(this._animationTimeoutId); + this._animationTimeoutId = undefined; + } + this._bAnimating = false; } handleResize() { @@ -655,14 +662,20 @@ class SideNavigation extends UI5Element { this.removeEventListener("transitionend", this._fnTransitionEnd); } - this._fnTransitionEnd = (oEvent: TransitionEvent) => { - if (oEvent.propertyName !== "width" && oEvent.propertyName !== "min-width") { - return; - } + if (this._animationTimeoutId) { + clearTimeout(this._animationTimeoutId); + } + const cleanupAnimation = () => { oDomRef.classList.remove("ui5-sn-animating"); - this.removeEventListener("transitionend", this._fnTransitionEnd!); - this._fnTransitionEnd = undefined; + if (this._fnTransitionEnd) { + this.removeEventListener("transitionend", this._fnTransitionEnd); + this._fnTransitionEnd = undefined; + } + if (this._animationTimeoutId) { + clearTimeout(this._animationTimeoutId); + this._animationTimeoutId = undefined; + } this._bAnimating = false; this._getAllItems(this.items) @@ -672,7 +685,20 @@ class SideNavigation extends UI5Element { }); }; + this._fnTransitionEnd = (oEvent: TransitionEvent) => { + if (oEvent.propertyName !== "width" && oEvent.propertyName !== "min-width") { + return; + } + + cleanupAnimation(); + }; + this.addEventListener("transitionend", this._fnTransitionEnd); + + // Fallback timeout in case transitionend doesn't fire + this._animationTimeoutId = setTimeout(() => { + cleanupAnimation(); + }, 500); } _handleItemClick(e: KeyboardEvent | MouseEvent, item: SideNavigationSelectableItemBase) { From 27177c241485845b1b346fc951d47e027fb5f2ab Mon Sep 17 00:00:00 2001 From: Siyana Todorova Date: Wed, 22 Jul 2026 14:16:44 +0300 Subject: [PATCH 5/5] fix: remove unnecessary check --- packages/fiori/src/SideNavigation.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/fiori/src/SideNavigation.ts b/packages/fiori/src/SideNavigation.ts index 77d7bb0c4f4a2..cc636aa9bc187 100644 --- a/packages/fiori/src/SideNavigation.ts +++ b/packages/fiori/src/SideNavigation.ts @@ -15,7 +15,6 @@ import ItemNavigation from "@ui5/webcomponents-base/dist/delegate/ItemNavigation import type { ITabbable } from "@ui5/webcomponents-base/dist/delegate/ItemNavigation.js"; import { isPhone } from "@ui5/webcomponents-base/dist/Device.js"; import createInstanceChecker from "@ui5/webcomponents-base/dist/util/createInstanceChecker.js"; -import { getAnimationMode } from "@ui5/webcomponents-base/dist/config/AnimationMode.js"; import NavigationMode from "@ui5/webcomponents-base/dist/types/NavigationMode.js"; import type SideNavigationItemBase from "./SideNavigationItemBase.js"; @@ -244,7 +243,7 @@ class SideNavigation extends UI5Element { onInvalidation(changeInfo: ChangeInfo) { if (changeInfo.type === "property" && changeInfo.name === "collapsed") { - if (this.getDomRef() && getAnimationMode() !== "none") { + if (this.getDomRef()) { this._bAnimating = true; } }