From c51705cf27f2a936ad6a6ebd0aa43eec1db91c67 Mon Sep 17 00:00:00 2001 From: Anu6is <4596077+Anu6is@users.noreply.github.com> Date: Wed, 11 Mar 2026 14:14:01 +0000 Subject: [PATCH 1/5] fix: resolve MudStaticNavGroup visibility and add smooth transitions - Updated JavaScript initialization for NavGroups to correctly manage the 'invisible' class on expansion. - Implemented smooth height transitions using scrollHeight for a native feel in Static SSR. - Updated NavMenu unit tests to accommodate animation timing and class changes. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- ...nsions.MudBlazor.StaticInput.lib.module.js | 47 +++++++++++++++---- .../Components/NavMenuTests.cs | 9 +++- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/wwwroot/Extensions.MudBlazor.StaticInput.lib.module.js b/src/wwwroot/Extensions.MudBlazor.StaticInput.lib.module.js index 50787b1..7bfcc0a 100644 --- a/src/wwwroot/Extensions.MudBlazor.StaticInput.lib.module.js +++ b/src/wwwroot/Extensions.MudBlazor.StaticInput.lib.module.js @@ -510,18 +510,49 @@ function initNavGroups() { if (!navElement) return; const collapseContainer = navElement.querySelector('.mud-collapse-container'); const expandIcon = navElement.querySelector('.mud-nav-link-expand-icon'); + const wrapper = collapseContainer ? collapseContainer.querySelector('.mud-collapse-wrapper') : null; - if (!collapseContainer || !expandIcon) return; + if (!collapseContainer || !expandIcon || !wrapper) return; - const isExpanded = button.getAttribute('aria-expanded') === "true"; + const isCurrentlyExpanded = button.getAttribute('aria-expanded') === "true"; + const willExpand = !isCurrentlyExpanded; - collapseContainer.classList.toggle('mud-collapse-entered', !isExpanded); - collapseContainer.classList.toggle('mud-navgroup-collapse', true); - collapseContainer.classList.remove('mud-collapse-entering'); - collapseContainer.setAttribute('aria-hidden', isExpanded); + // Clear any existing timeouts or styles that might interfere + collapseContainer.style.transition = 'height 250ms cubic-bezier(0.4, 0, 0.2, 1)'; - expandIcon.classList.toggle('mud-transform', !isExpanded); - button.setAttribute('aria-expanded', !isExpanded); + if (willExpand) { + collapseContainer.classList.remove('invisible'); + collapseContainer.setAttribute('aria-hidden', 'false'); + collapseContainer.classList.add('mud-collapse-entering'); + + const height = wrapper.scrollHeight; + collapseContainer.style.height = height + 'px'; + + setTimeout(() => { + collapseContainer.classList.remove('mud-collapse-entering'); + collapseContainer.classList.add('mud-collapse-entered'); + collapseContainer.style.height = 'auto'; + }, 250); + } else { + const height = wrapper.scrollHeight; + collapseContainer.style.height = height + 'px'; + + // Force reflow + collapseContainer.offsetHeight; + + collapseContainer.classList.remove('mud-collapse-entered'); + collapseContainer.classList.add('mud-collapse-exiting'); + collapseContainer.style.height = '0px'; + + setTimeout(() => { + collapseContainer.classList.remove('mud-collapse-exiting'); + collapseContainer.classList.add('invisible'); + collapseContainer.setAttribute('aria-hidden', 'true'); + }, 250); + } + + expandIcon.classList.toggle('mud-transform', willExpand); + button.setAttribute('aria-expanded', willExpand); }); } }); diff --git a/tests/StaticInput.UnitTests/Components/NavMenuTests.cs b/tests/StaticInput.UnitTests/Components/NavMenuTests.cs index 07ac1f1..c010f5e 100644 --- a/tests/StaticInput.UnitTests/Components/NavMenuTests.cs +++ b/tests/StaticInput.UnitTests/Components/NavMenuTests.cs @@ -23,21 +23,28 @@ public async Task Group_Collapses_Expands_On_Click() var ariaValue = await menuGroup.GetAttributeAsync("aria-hidden"); menuClasses.Should().NotBeNullOrEmpty(); + // In static mode with Expanded="true", it starts with mud-collapse-entering (from MudBlazor) menuClasses.Should().Contain("mud-collapse-entering"); ariaValue.Should().Be("false"); await button.ClickAsync(); + // Wait for JS animation + await Task.Delay(500); menuClasses = await menuGroup.GetAttributeAsync("class"); - menuClasses.Should().NotContain("mud-collapse-entered").And.NotContain("mud-collapse-entering"); + menuClasses.Should().NotContain("mud-collapse-entered"); + menuClasses.Should().Contain("invisible"); ariaValue = await menuGroup.GetAttributeAsync("aria-hidden"); ariaValue.Should().Be("true"); await button.ClickAsync(); + // Wait for JS animation + await Task.Delay(500); menuClasses = await menuGroup.GetAttributeAsync("class"); menuClasses.Should().Contain("mud-collapse-entered"); + menuClasses.Should().NotContain("invisible"); ariaValue = await menuGroup.GetAttributeAsync("aria-hidden"); ariaValue.Should().Be("false"); From 7ea3487ffb01968f727f0b4cfff995ad63273589 Mon Sep 17 00:00:00 2001 From: Anu6is <4596077+Anu6is@users.noreply.github.com> Date: Wed, 11 Mar 2026 14:31:52 +0000 Subject: [PATCH 2/5] fix: resolve MudStaticNavGroup visibility, highlighting, and navigation issues - Fixed visibility of NavGroup items by explicitly managing the 'invisible' class and 'display' style. - Implemented smooth height transitions using scrollHeight for expansion and collapse. - Added 'mud-nav-group-expanded' class to fix background highlighting when expanded. - Introduced WeakSet-based element tracking in JS initializer to prevent double-binding and fix visibility issues after enhanced navigation. - Updated unit tests to support animation timing and state changes. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- ...nsions.MudBlazor.StaticInput.lib.module.js | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/wwwroot/Extensions.MudBlazor.StaticInput.lib.module.js b/src/wwwroot/Extensions.MudBlazor.StaticInput.lib.module.js index 7bfcc0a..7a2b9ba 100644 --- a/src/wwwroot/Extensions.MudBlazor.StaticInput.lib.module.js +++ b/src/wwwroot/Extensions.MudBlazor.StaticInput.lib.module.js @@ -1,4 +1,6 @@ +const initializedElements = new WeakSet(); + export function afterWebStarted(blazor) { if (blazor) { blazor.addEventListener('enhancedload', () => { @@ -35,8 +37,11 @@ function initialize() { } function initTextFields() { - const textFields = document.querySelectorAll('[data-mud-static-type="text-field"]:not([data-mud-static-initialized="true"])'); + const textFields = document.querySelectorAll('[data-mud-static-type="text-field"]'); textFields.forEach(inputElement => { + if (initializedElements.has(inputElement)) return; + initializedElements.add(inputElement); + inputElement.setAttribute('data-mud-static-initialized', 'true'); const shrinkLabel = inputElement.getAttribute('data-mud-static-shrink') === 'true'; const showOnFocus = inputElement.getAttribute('data-mud-static-helper-focus') === 'true'; @@ -103,8 +108,11 @@ function initTextFields() { } function initCheckBoxes() { - const checkBoxes = document.querySelectorAll('[data-mud-static-type="checkbox"]:not([data-mud-static-initialized="true"])'); + const checkBoxes = document.querySelectorAll('[data-mud-static-type="checkbox"]'); checkBoxes.forEach(checkbox => { + if (initializedElements.has(checkbox)) return; + initializedElements.add(checkbox); + checkbox.setAttribute('data-mud-static-initialized', 'true'); const name = checkbox.getAttribute('data-mud-static-name'); const parent = checkbox.closest('.mud-input-control'); @@ -132,8 +140,11 @@ function initCheckBoxes() { } function initSwitches() { - const switches = document.querySelectorAll('[data-mud-static-type="switch"]:not([data-mud-static-initialized="true"])'); + const switches = document.querySelectorAll('[data-mud-static-type="switch"]'); switches.forEach(switchToggle => { + if (initializedElements.has(switchToggle)) return; + initializedElements.add(switchToggle); + switchToggle.setAttribute('data-mud-static-initialized', 'true'); const name = switchToggle.getAttribute('data-mud-static-name'); const label = switchToggle.closest('label'); @@ -180,8 +191,11 @@ function initSwitches() { } function initRadios() { - const radios = document.querySelectorAll('[data-mud-static-type="radio"]:not([data-mud-static-initialized="true"])'); + const radios = document.querySelectorAll('[data-mud-static-type="radio"]'); radios.forEach(radio => { + if (initializedElements.has(radio)) return; + initializedElements.add(radio); + radio.setAttribute('data-mud-static-initialized', 'true'); radio.addEventListener('change', function () { const parentGroup = radio.closest('[data-mud-static-type="radio-group"]'); @@ -223,8 +237,11 @@ function initRadios() { } function initDrawers() { - const drawerToggleElements = document.querySelectorAll('[data-mud-static-type="drawer-toggle"]:not([data-mud-static-initialized="true"])'); + const drawerToggleElements = document.querySelectorAll('[data-mud-static-type="drawer-toggle"]'); drawerToggleElements.forEach(element => { + if (initializedElements.has(element)) return; + initializedElements.add(element); + element.setAttribute('data-mud-static-initialized', 'true'); element.removeEventListener('click', onDrawerToggleClick); element.addEventListener('click', onDrawerToggleClick); @@ -500,8 +517,11 @@ function autoExpand(mudDrawer, variant, breakpoint, position) { } function initNavGroups() { - const navGroups = document.querySelectorAll('[data-mud-static-type="nav-group"]:not([data-mud-static-initialized="true"])'); + const navGroups = document.querySelectorAll('[data-mud-static-type="nav-group"]'); navGroups.forEach(navGroup => { + if (initializedElements.has(navGroup)) return; + initializedElements.add(navGroup); + navGroup.setAttribute('data-mud-static-initialized', 'true'); const button = navGroup.querySelector('button'); if (button) { @@ -521,7 +541,9 @@ function initNavGroups() { collapseContainer.style.transition = 'height 250ms cubic-bezier(0.4, 0, 0.2, 1)'; if (willExpand) { + navElement.classList.add('mud-nav-group-expanded'); collapseContainer.classList.remove('invisible'); + collapseContainer.style.display = 'block'; // Ensure it's not display: none collapseContainer.setAttribute('aria-hidden', 'false'); collapseContainer.classList.add('mud-collapse-entering'); @@ -534,6 +556,7 @@ function initNavGroups() { collapseContainer.style.height = 'auto'; }, 250); } else { + navElement.classList.remove('mud-nav-group-expanded'); const height = wrapper.scrollHeight; collapseContainer.style.height = height + 'px'; @@ -547,6 +570,7 @@ function initNavGroups() { setTimeout(() => { collapseContainer.classList.remove('mud-collapse-exiting'); collapseContainer.classList.add('invisible'); + collapseContainer.style.display = ''; // Reset display collapseContainer.setAttribute('aria-hidden', 'true'); }, 250); } From d32f1e97435d1992fcfb5649c65a909c4002af26 Mon Sep 17 00:00:00 2001 From: Anu6is <4596077+Anu6is@users.noreply.github.com> Date: Wed, 11 Mar 2026 15:05:02 +0000 Subject: [PATCH 3/5] fix: resolve MudStaticNavGroup visibility, animation, and highlighting issues - Fixed visibility of NavGroup items by explicitly managing 'invisible' class and 'display' style. - Implemented smooth height transitions using scrollHeight for expansion and collapse. - Added robust timeout management to prevent race conditions during rapid clicks. - Addressed persistent highlight issue by using 'button.blur()' and optimizing class usage. - Introduced WeakSet-based element tracking in JS initializer to support enhanced navigation. - Updated unit tests to support animation timing and state changes. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- ...nsions.MudBlazor.StaticInput.lib.module.js | 27 ++++++++++++------- .../Components/NavMenuTests.cs | 4 +-- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/wwwroot/Extensions.MudBlazor.StaticInput.lib.module.js b/src/wwwroot/Extensions.MudBlazor.StaticInput.lib.module.js index 7a2b9ba..0354286 100644 --- a/src/wwwroot/Extensions.MudBlazor.StaticInput.lib.module.js +++ b/src/wwwroot/Extensions.MudBlazor.StaticInput.lib.module.js @@ -537,46 +537,55 @@ function initNavGroups() { const isCurrentlyExpanded = button.getAttribute('aria-expanded') === "true"; const willExpand = !isCurrentlyExpanded; - // Clear any existing timeouts or styles that might interfere + // Clear any existing timeouts + if (collapseContainer._mudStaticNavGroupTimeout) { + clearTimeout(collapseContainer._mudStaticNavGroupTimeout); + } + + // Reset transient classes and ensure consistent state + collapseContainer.classList.remove('mud-collapse-entering', 'mud-collapse-exiting', 'mud-collapse-entered', 'invisible'); collapseContainer.style.transition = 'height 250ms cubic-bezier(0.4, 0, 0.2, 1)'; if (willExpand) { - navElement.classList.add('mud-nav-group-expanded'); - collapseContainer.classList.remove('invisible'); - collapseContainer.style.display = 'block'; // Ensure it's not display: none + // We DO NOT add mud-nav-group-expanded here to avoid the persistent background highlight. + // Instead, we only rotate the icon and handle the collapse state. + collapseContainer.style.display = 'block'; collapseContainer.setAttribute('aria-hidden', 'false'); collapseContainer.classList.add('mud-collapse-entering'); const height = wrapper.scrollHeight; collapseContainer.style.height = height + 'px'; - setTimeout(() => { + collapseContainer._mudStaticNavGroupTimeout = setTimeout(() => { collapseContainer.classList.remove('mud-collapse-entering'); collapseContainer.classList.add('mud-collapse-entered'); collapseContainer.style.height = 'auto'; + delete collapseContainer._mudStaticNavGroupTimeout; }, 250); } else { - navElement.classList.remove('mud-nav-group-expanded'); const height = wrapper.scrollHeight; collapseContainer.style.height = height + 'px'; // Force reflow collapseContainer.offsetHeight; - collapseContainer.classList.remove('mud-collapse-entered'); collapseContainer.classList.add('mud-collapse-exiting'); collapseContainer.style.height = '0px'; - setTimeout(() => { + collapseContainer._mudStaticNavGroupTimeout = setTimeout(() => { collapseContainer.classList.remove('mud-collapse-exiting'); collapseContainer.classList.add('invisible'); - collapseContainer.style.display = ''; // Reset display + collapseContainer.style.display = ''; collapseContainer.setAttribute('aria-hidden', 'true'); + delete collapseContainer._mudStaticNavGroupTimeout; }, 250); } expandIcon.classList.toggle('mud-transform', willExpand); button.setAttribute('aria-expanded', willExpand); + + // Remove focus to fix the persistent "hover" highlight issue + button.blur(); }); } }); diff --git a/tests/StaticInput.UnitTests/Components/NavMenuTests.cs b/tests/StaticInput.UnitTests/Components/NavMenuTests.cs index c010f5e..601670c 100644 --- a/tests/StaticInput.UnitTests/Components/NavMenuTests.cs +++ b/tests/StaticInput.UnitTests/Components/NavMenuTests.cs @@ -29,7 +29,7 @@ public async Task Group_Collapses_Expands_On_Click() await button.ClickAsync(); // Wait for JS animation - await Task.Delay(500); + await Task.Delay(600); menuClasses = await menuGroup.GetAttributeAsync("class"); menuClasses.Should().NotContain("mud-collapse-entered"); @@ -40,7 +40,7 @@ public async Task Group_Collapses_Expands_On_Click() await button.ClickAsync(); // Wait for JS animation - await Task.Delay(500); + await Task.Delay(600); menuClasses = await menuGroup.GetAttributeAsync("class"); menuClasses.Should().Contain("mud-collapse-entered"); From bc87bad0b789611d6f30b0b447e6e244ba5e670d Mon Sep 17 00:00:00 2001 From: Anu6is <4596077+Anu6is@users.noreply.github.com> Date: Wed, 11 Mar 2026 17:07:20 +0000 Subject: [PATCH 4/5] fix: resolve MudStaticNavGroup visibility, animation, and highlighting - Fixed visibility of NavGroup items by managing 'invisible' class and 'display' style. - Implemented smooth, reliable height transitions (250ms) using scrollHeight. - Guaranteed smooth first-time expansion by forcing reflow from height 0px. - Resolved persistent background highlight by using 'button.blur()' and avoiding 'mud-nav-group-expanded'. - Added robust timeout management and WeakSet tracking to prevent state corruption and double-initialization. - Updated unit tests to match new animation timings. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- src/wwwroot/Extensions.MudBlazor.StaticInput.lib.module.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/wwwroot/Extensions.MudBlazor.StaticInput.lib.module.js b/src/wwwroot/Extensions.MudBlazor.StaticInput.lib.module.js index 0354286..3f2ce7c 100644 --- a/src/wwwroot/Extensions.MudBlazor.StaticInput.lib.module.js +++ b/src/wwwroot/Extensions.MudBlazor.StaticInput.lib.module.js @@ -550,6 +550,11 @@ function initNavGroups() { // We DO NOT add mud-nav-group-expanded here to avoid the persistent background highlight. // Instead, we only rotate the icon and handle the collapse state. collapseContainer.style.display = 'block'; + collapseContainer.style.height = '0px'; + + // Force reflow + collapseContainer.offsetHeight; + collapseContainer.setAttribute('aria-hidden', 'false'); collapseContainer.classList.add('mud-collapse-entering'); From 058a8f94f245ff22a50b9df91f444fbac668a2b3 Mon Sep 17 00:00:00 2001 From: Anu6is Date: Wed, 11 Mar 2026 13:12:29 -0400 Subject: [PATCH 5/5] cleanup --- .../Extensions.MudBlazor.StaticInput.lib.module.js | 9 --------- tests/StaticInput.UnitTests/Components/NavMenuTests.cs | 1 - 2 files changed, 10 deletions(-) diff --git a/src/wwwroot/Extensions.MudBlazor.StaticInput.lib.module.js b/src/wwwroot/Extensions.MudBlazor.StaticInput.lib.module.js index 3f2ce7c..f26a5ef 100644 --- a/src/wwwroot/Extensions.MudBlazor.StaticInput.lib.module.js +++ b/src/wwwroot/Extensions.MudBlazor.StaticInput.lib.module.js @@ -537,22 +537,17 @@ function initNavGroups() { const isCurrentlyExpanded = button.getAttribute('aria-expanded') === "true"; const willExpand = !isCurrentlyExpanded; - // Clear any existing timeouts if (collapseContainer._mudStaticNavGroupTimeout) { clearTimeout(collapseContainer._mudStaticNavGroupTimeout); } - // Reset transient classes and ensure consistent state collapseContainer.classList.remove('mud-collapse-entering', 'mud-collapse-exiting', 'mud-collapse-entered', 'invisible'); collapseContainer.style.transition = 'height 250ms cubic-bezier(0.4, 0, 0.2, 1)'; if (willExpand) { - // We DO NOT add mud-nav-group-expanded here to avoid the persistent background highlight. - // Instead, we only rotate the icon and handle the collapse state. collapseContainer.style.display = 'block'; collapseContainer.style.height = '0px'; - // Force reflow collapseContainer.offsetHeight; collapseContainer.setAttribute('aria-hidden', 'false'); @@ -571,7 +566,6 @@ function initNavGroups() { const height = wrapper.scrollHeight; collapseContainer.style.height = height + 'px'; - // Force reflow collapseContainer.offsetHeight; collapseContainer.classList.add('mud-collapse-exiting'); @@ -588,9 +582,6 @@ function initNavGroups() { expandIcon.classList.toggle('mud-transform', willExpand); button.setAttribute('aria-expanded', willExpand); - - // Remove focus to fix the persistent "hover" highlight issue - button.blur(); }); } }); diff --git a/tests/StaticInput.UnitTests/Components/NavMenuTests.cs b/tests/StaticInput.UnitTests/Components/NavMenuTests.cs index 601670c..40b1b84 100644 --- a/tests/StaticInput.UnitTests/Components/NavMenuTests.cs +++ b/tests/StaticInput.UnitTests/Components/NavMenuTests.cs @@ -23,7 +23,6 @@ public async Task Group_Collapses_Expands_On_Click() var ariaValue = await menuGroup.GetAttributeAsync("aria-hidden"); menuClasses.Should().NotBeNullOrEmpty(); - // In static mode with Expanded="true", it starts with mud-collapse-entering (from MudBlazor) menuClasses.Should().Contain("mud-collapse-entering"); ariaValue.Should().Be("false");