fix(ui): Reconcile sidebar state when crossing the responsive breakpoint

SideNav classified the viewport only during construction. Resizing past
the `lg` breakpoint therefore left the desktop sidebar in its previous
mobile state, and the menu group containing the current page remained
collapsed until the pointer entered the sidebar.

Use the existing desktop media query as an event source. Apply the
persisted pin preference directly to the body attributes during
initialization and on each breakpoint transition, then reinitialize the
active menu section.

Avoid calling `pin()` or `unpin()` during responsive transitions so
Bootstrap retains ownership of its collapse state and the stored
preference is not rewritten unnecessarily.

Also remove the `show` class from a menu group toggle when collapsing
its section to keep the toggle and menu state synchronized.

Fixes #23035
This commit is contained in:
Martin Hauser 2026-08-28 16:03:43 +02:00 committed by Jeremy Stretch
parent 88f90dc8ca
commit fe194e7003
3 changed files with 28 additions and 14 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -66,22 +66,34 @@ class SideNav {
toggler.addEventListener('click', event => this.onMobileToggle(event));
}
if (window.matchMedia(SIDENAV_DESKTOP_MEDIA).matches) {
if (this.state.get('pinned')) {
this.pin();
} else {
this.unpin();
}
} else {
this.bodyRemove('hide');
this.bodyAdd('hidden');
}
const desktopMedia = window.matchMedia(SIDENAV_DESKTOP_MEDIA);
this.setResponsiveState(desktopMedia.matches);
desktopMedia.addEventListener('change', event => {
this.setResponsiveState(event.matches);
this.initLinks();
});
window.addEventListener('resize', () => this.onResize());
this.base.addEventListener('mouseenter', () => this.onEnter());
this.base.addEventListener('mouseleave', () => this.onLeave());
}
/**
* Apply the appropriate sidenav state for the current responsive layout.
*/
private setResponsiveState(isDesktop: boolean): void {
this.bodyRemove('hide');
if (isDesktop && this.state.get('pinned')) {
this.bodyRemove('hidden');
this.bodyAdd('show', 'pinned');
} else {
this.bodyRemove('show', 'pinned');
this.bodyAdd('hidden');
}
}
/**
* If the sidenav is shown, expand active nav links. Otherwise, collapse them.
*/
@ -153,12 +165,14 @@ class SideNav {
switch (action) {
case 'expand':
groupLink.setAttribute('aria-expanded', 'true');
groupLink.classList.add('show');
groupItem.classList.add('active');
dropdownMenu.classList.add('show');
link.classList.add('active');
break;
case 'collapse':
groupLink.setAttribute('aria-expanded', 'false');
groupLink.classList.remove('show');
groupItem.classList.remove('active');
dropdownMenu.classList.remove('show');
link.classList.remove('active');