Skip to content

Commit

Permalink
fix(material/select): overlay not detached on time after exit animati…
Browse files Browse the repository at this point in the history
…on in some cases (#30456)

Fixes an issue that was reported internally where in some setups the select would animate away, but wouldn't be detached after the exit animation until the next change detection.
  • Loading branch information
crisbeto authored Feb 10, 2025
1 parent 3d91f61 commit 24c2567
Showing 1 changed file with 29 additions and 25 deletions.
54 changes: 29 additions & 25 deletions src/material/select/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ import {
HostAttributeToken,
ANIMATION_MODULE_TYPE,
Renderer2,
NgZone,
} from '@angular/core';
import {
AbstractControl,
Expand Down Expand Up @@ -216,7 +215,6 @@ export class MatSelect
private _dir = inject(Directionality, {optional: true});
private _idGenerator = inject(_IdGenerator);
private _renderer = inject(Renderer2);
private _ngZone = inject(NgZone);
protected _parentFormField = inject<MatFormField>(MAT_FORM_FIELD, {optional: true});
ngControl = inject(NgControl, {self: true, optional: true})!;
private _liveAnnouncer = inject(LiveAnnouncer);
Expand Down Expand Up @@ -845,35 +843,41 @@ export class MatSelect
/** Triggers the exit animation and detaches the overlay at the end. */
private _exitAndDetach() {
if (this._animationsDisabled || !this.panel) {
this._overlayDir.detachOverlay();
this._detachOverlay();
return;
}

this._ngZone.runOutsideAngular(() => {
this._cleanupDetach?.();
this._cleanupDetach = () => {
cleanupEvent();
clearTimeout(exitFallbackTimer);
this._cleanupDetach = undefined;
};
this._cleanupDetach?.();
this._cleanupDetach = () => {
cleanupEvent();
clearTimeout(exitFallbackTimer);
this._cleanupDetach = undefined;
};

const panel: HTMLElement = this.panel.nativeElement;
const cleanupEvent = this._renderer.listen(panel, 'animationend', (event: AnimationEvent) => {
if (event.animationName === '_mat-select-exit') {
this._cleanupDetach?.();
this._detachOverlay();
}
});

const panel: HTMLElement = this.panel.nativeElement;
const cleanupEvent = this._renderer.listen(panel, 'animationend', (event: AnimationEvent) => {
if (event.animationName === '_mat-select-exit') {
this._cleanupDetach?.();
this._overlayDir.detachOverlay();
}
});
// Since closing the overlay depends on the animation, we have a fallback in case the panel
// doesn't animate. This can happen in some internal tests that do `* {animation: none}`.
const exitFallbackTimer = setTimeout(() => {
this._cleanupDetach?.();
this._detachOverlay();
}, 200);

// Since closing the overlay depends on the animation, we have a fallback in case the panel
// doesn't animate. This can happen in some internal tests that do `* {animation: none}`.
const exitFallbackTimer = setTimeout(() => {
this._cleanupDetach?.();
this._overlayDir.detachOverlay();
}, 200);
panel.classList.add('mat-select-panel-exit');
}

panel.classList.add('mat-select-panel-exit');
});
/** Detaches the current overlay directive. */
private _detachOverlay() {
this._overlayDir.detachOverlay();
// Some of the overlay detachment logic depends on change detection.
// Mark for check to ensure that things get picked up in a timely manner.
this._changeDetectorRef.markForCheck();
}

/**
Expand Down

0 comments on commit 24c2567

Please sign in to comment.