Skip to content

Commit e0fd08c

Browse files
authored
fix(material/sidenav): prevent drawer from getting stuck when toggled rapidly (#33726)
Fixes that the drawer can get stuck in the wrong state if it is toggled rapidly using the `open`/`close` methods. Fixes #33722.
1 parent 5006b41 commit e0fd08c

1 file changed

Lines changed: 35 additions & 15 deletions

File tree

src/material/sidenav/drawer.ts

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ export class MatDrawer implements AfterViewInit, OnDestroy {
198198
private _renderer = inject(Renderer2);
199199
private readonly _interactivityChecker = inject(InteractivityChecker);
200200
private _doc = inject(DOCUMENT);
201+
private _isAnimating = false;
201202
_container? = inject<MatDrawerContainer>(MAT_DRAWER_CONTAINER, {optional: true});
202203

203204
private _focusTrap: FocusTrap | null = null;
@@ -450,9 +451,16 @@ export class MatDrawer implements AfterViewInit, OnDestroy {
450451
case 'first-tabbable':
451452
afterNextRender(
452453
() => {
453-
const hasMovedFocus = this._focusTrap!.focusInitialElement();
454+
// If we try to capture focus mid-animation, we can end up shifting the page,
455+
// if the drawer starts off from the end so prevent scrolling in this case.
456+
// This should mostly happen in edge cases where the drawer is toggled rapidly.
457+
const focusOptions: FocusOptions | undefined = this._isAnimating
458+
? {preventScroll: true}
459+
: undefined;
460+
461+
const hasMovedFocus = this._focusTrap!.focusInitialElement(focusOptions);
454462
if (!hasMovedFocus && typeof element.focus === 'function') {
455-
element.focus();
463+
element.focus(focusOptions);
456464
}
457465
},
458466
{injector: this._injector},
@@ -587,20 +595,22 @@ export class MatDrawer implements AfterViewInit, OnDestroy {
587595
this._getContent()?._drawerToggled(this);
588596

589597
if (this._container?._transitionsEnabled) {
590-
// Note: it's important to set this as early as possible,
591-
// otherwise the animation can look glitchy in some cases.
592-
this._setIsAnimating(true);
593-
594-
// Previously we dispatched this in a `transitionrun` event, but it might not fire
595-
// if the element is hidden (see #32992). Since this event is load-bearing for the
596-
// margin calculations, we need it to fire consistently.
597-
setTimeout(() => this._animationStarted.next());
598+
if (this._isAnimating) {
599+
this._setIsAnimating(false);
600+
this._simulateAnimation();
601+
} else {
602+
// Note: it's important to set this as early as possible,
603+
// otherwise the animation can look glitchy in some cases.
604+
this._setIsAnimating(true);
605+
606+
// Previously we dispatched this in a `transitionrun` event, but it might not fire
607+
// if the element is hidden (see #32992). Since this event is load-bearing for the
608+
// margin calculations, we need it to fire consistently.
609+
setTimeout(() => this._animationStarted.next());
610+
}
598611
} else {
599612
// Simulate the animation events if animations are disabled.
600-
setTimeout(() => {
601-
this._animationStarted.next();
602-
this._animationEnd.next();
603-
});
613+
this._simulateAnimation();
604614
}
605615

606616
this._elementRef.nativeElement.classList.toggle('mat-drawer-opened', isOpen);
@@ -625,7 +635,17 @@ export class MatDrawer implements AfterViewInit, OnDestroy {
625635

626636
/** Toggles whether the drawer is currently animating. */
627637
private _setIsAnimating(isAnimating: boolean) {
628-
this._elementRef.nativeElement.classList.toggle('mat-drawer-animating', isAnimating);
638+
if (isAnimating !== this._isAnimating) {
639+
this._isAnimating = isAnimating;
640+
this._elementRef.nativeElement.classList.toggle('mat-drawer-animating', isAnimating);
641+
}
642+
}
643+
644+
private _simulateAnimation() {
645+
setTimeout(() => {
646+
this._animationStarted.next();
647+
this._animationEnd.next();
648+
});
629649
}
630650

631651
_getWidth(): number {

0 commit comments

Comments
 (0)