Skip to content

Commit b8d8511

Browse files
MudStaticNavGroup: Fix Visibility and Animation
* 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> * 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> * 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> * 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> * cleanup --------- Co-authored-by: Anu6is <[email protected]> Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
1 parent dc26ec0 commit b8d8511

2 files changed

Lines changed: 81 additions & 15 deletions

File tree

src/wwwroot/Extensions.MudBlazor.StaticInput.lib.module.js

Lines changed: 74 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11

2+
const initializedElements = new WeakSet();
3+
24
export function afterWebStarted(blazor) {
35
if (blazor) {
46
blazor.addEventListener('enhancedload', () => {
@@ -35,8 +37,11 @@ function initialize() {
3537
}
3638

3739
function initTextFields() {
38-
const textFields = document.querySelectorAll('[data-mud-static-type="text-field"]:not([data-mud-static-initialized="true"])');
40+
const textFields = document.querySelectorAll('[data-mud-static-type="text-field"]');
3941
textFields.forEach(inputElement => {
42+
if (initializedElements.has(inputElement)) return;
43+
initializedElements.add(inputElement);
44+
4045
inputElement.setAttribute('data-mud-static-initialized', 'true');
4146
const shrinkLabel = inputElement.getAttribute('data-mud-static-shrink') === 'true';
4247
const showOnFocus = inputElement.getAttribute('data-mud-static-helper-focus') === 'true';
@@ -103,8 +108,11 @@ function initTextFields() {
103108
}
104109

105110
function initCheckBoxes() {
106-
const checkBoxes = document.querySelectorAll('[data-mud-static-type="checkbox"]:not([data-mud-static-initialized="true"])');
111+
const checkBoxes = document.querySelectorAll('[data-mud-static-type="checkbox"]');
107112
checkBoxes.forEach(checkbox => {
113+
if (initializedElements.has(checkbox)) return;
114+
initializedElements.add(checkbox);
115+
108116
checkbox.setAttribute('data-mud-static-initialized', 'true');
109117
const name = checkbox.getAttribute('data-mud-static-name');
110118
const parent = checkbox.closest('.mud-input-control');
@@ -132,8 +140,11 @@ function initCheckBoxes() {
132140
}
133141

134142
function initSwitches() {
135-
const switches = document.querySelectorAll('[data-mud-static-type="switch"]:not([data-mud-static-initialized="true"])');
143+
const switches = document.querySelectorAll('[data-mud-static-type="switch"]');
136144
switches.forEach(switchToggle => {
145+
if (initializedElements.has(switchToggle)) return;
146+
initializedElements.add(switchToggle);
147+
137148
switchToggle.setAttribute('data-mud-static-initialized', 'true');
138149
const name = switchToggle.getAttribute('data-mud-static-name');
139150
const label = switchToggle.closest('label');
@@ -180,8 +191,11 @@ function initSwitches() {
180191
}
181192

182193
function initRadios() {
183-
const radios = document.querySelectorAll('[data-mud-static-type="radio"]:not([data-mud-static-initialized="true"])');
194+
const radios = document.querySelectorAll('[data-mud-static-type="radio"]');
184195
radios.forEach(radio => {
196+
if (initializedElements.has(radio)) return;
197+
initializedElements.add(radio);
198+
185199
radio.setAttribute('data-mud-static-initialized', 'true');
186200
radio.addEventListener('change', function () {
187201
const parentGroup = radio.closest('[data-mud-static-type="radio-group"]');
@@ -223,8 +237,11 @@ function initRadios() {
223237
}
224238

225239
function initDrawers() {
226-
const drawerToggleElements = document.querySelectorAll('[data-mud-static-type="drawer-toggle"]:not([data-mud-static-initialized="true"])');
240+
const drawerToggleElements = document.querySelectorAll('[data-mud-static-type="drawer-toggle"]');
227241
drawerToggleElements.forEach(element => {
242+
if (initializedElements.has(element)) return;
243+
initializedElements.add(element);
244+
228245
element.setAttribute('data-mud-static-initialized', 'true');
229246
element.removeEventListener('click', onDrawerToggleClick);
230247
element.addEventListener('click', onDrawerToggleClick);
@@ -500,8 +517,11 @@ function autoExpand(mudDrawer, variant, breakpoint, position) {
500517
}
501518

502519
function initNavGroups() {
503-
const navGroups = document.querySelectorAll('[data-mud-static-type="nav-group"]:not([data-mud-static-initialized="true"])');
520+
const navGroups = document.querySelectorAll('[data-mud-static-type="nav-group"]');
504521
navGroups.forEach(navGroup => {
522+
if (initializedElements.has(navGroup)) return;
523+
initializedElements.add(navGroup);
524+
505525
navGroup.setAttribute('data-mud-static-initialized', 'true');
506526
const button = navGroup.querySelector('button');
507527
if (button) {
@@ -510,18 +530,58 @@ function initNavGroups() {
510530
if (!navElement) return;
511531
const collapseContainer = navElement.querySelector('.mud-collapse-container');
512532
const expandIcon = navElement.querySelector('.mud-nav-link-expand-icon');
533+
const wrapper = collapseContainer ? collapseContainer.querySelector('.mud-collapse-wrapper') : null;
534+
535+
if (!collapseContainer || !expandIcon || !wrapper) return;
513536

514-
if (!collapseContainer || !expandIcon) return;
537+
const isCurrentlyExpanded = button.getAttribute('aria-expanded') === "true";
538+
const willExpand = !isCurrentlyExpanded;
539+
540+
if (collapseContainer._mudStaticNavGroupTimeout) {
541+
clearTimeout(collapseContainer._mudStaticNavGroupTimeout);
542+
}
515543

516-
const isExpanded = button.getAttribute('aria-expanded') === "true";
544+
collapseContainer.classList.remove('mud-collapse-entering', 'mud-collapse-exiting', 'mud-collapse-entered', 'invisible');
545+
collapseContainer.style.transition = 'height 250ms cubic-bezier(0.4, 0, 0.2, 1)';
517546

518-
collapseContainer.classList.toggle('mud-collapse-entered', !isExpanded);
519-
collapseContainer.classList.toggle('mud-navgroup-collapse', true);
520-
collapseContainer.classList.remove('mud-collapse-entering');
521-
collapseContainer.setAttribute('aria-hidden', isExpanded);
547+
if (willExpand) {
548+
collapseContainer.style.display = 'block';
549+
collapseContainer.style.height = '0px';
550+
551+
collapseContainer.offsetHeight;
552+
553+
collapseContainer.setAttribute('aria-hidden', 'false');
554+
collapseContainer.classList.add('mud-collapse-entering');
555+
556+
const height = wrapper.scrollHeight;
557+
collapseContainer.style.height = height + 'px';
558+
559+
collapseContainer._mudStaticNavGroupTimeout = setTimeout(() => {
560+
collapseContainer.classList.remove('mud-collapse-entering');
561+
collapseContainer.classList.add('mud-collapse-entered');
562+
collapseContainer.style.height = 'auto';
563+
delete collapseContainer._mudStaticNavGroupTimeout;
564+
}, 250);
565+
} else {
566+
const height = wrapper.scrollHeight;
567+
collapseContainer.style.height = height + 'px';
568+
569+
collapseContainer.offsetHeight;
570+
571+
collapseContainer.classList.add('mud-collapse-exiting');
572+
collapseContainer.style.height = '0px';
573+
574+
collapseContainer._mudStaticNavGroupTimeout = setTimeout(() => {
575+
collapseContainer.classList.remove('mud-collapse-exiting');
576+
collapseContainer.classList.add('invisible');
577+
collapseContainer.style.display = '';
578+
collapseContainer.setAttribute('aria-hidden', 'true');
579+
delete collapseContainer._mudStaticNavGroupTimeout;
580+
}, 250);
581+
}
522582

523-
expandIcon.classList.toggle('mud-transform', !isExpanded);
524-
button.setAttribute('aria-expanded', !isExpanded);
583+
expandIcon.classList.toggle('mud-transform', willExpand);
584+
button.setAttribute('aria-expanded', willExpand);
525585
});
526586
}
527587
});

tests/StaticInput.UnitTests/Components/NavMenuTests.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,23 @@ public async Task Group_Collapses_Expands_On_Click()
2727
ariaValue.Should().Be("false");
2828

2929
await button.ClickAsync();
30+
// Wait for JS animation
31+
await Task.Delay(600);
3032

3133
menuClasses = await menuGroup.GetAttributeAsync("class");
32-
menuClasses.Should().NotContain("mud-collapse-entered").And.NotContain("mud-collapse-entering");
34+
menuClasses.Should().NotContain("mud-collapse-entered");
35+
menuClasses.Should().Contain("invisible");
3336

3437
ariaValue = await menuGroup.GetAttributeAsync("aria-hidden");
3538
ariaValue.Should().Be("true");
3639

3740
await button.ClickAsync();
41+
// Wait for JS animation
42+
await Task.Delay(600);
3843

3944
menuClasses = await menuGroup.GetAttributeAsync("class");
4045
menuClasses.Should().Contain("mud-collapse-entered");
46+
menuClasses.Should().NotContain("invisible");
4147

4248
ariaValue = await menuGroup.GetAttributeAsync("aria-hidden");
4349
ariaValue.Should().Be("false");

0 commit comments

Comments
 (0)