diff --git a/src/material/autocomplete/animations.ts b/src/material/autocomplete/animations.ts
deleted file mode 100644
index 4b1d3ae671f3..000000000000
--- a/src/material/autocomplete/animations.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.dev/license
- */
-
-import {
- animate,
- AnimationTriggerMetadata,
- group,
- state,
- style,
- transition,
- trigger,
-} from '@angular/animations';
-
-// Animation values come from
-// TODO(mmalerba): Ideally find a way to import the values from MDC's code.
-export const panelAnimation: AnimationTriggerMetadata = trigger('panelAnimation', [
- state(
- 'void, hidden',
- style({
- opacity: 0,
- transform: 'scaleY(0.8)',
- }),
- ),
- transition(':enter, hidden => visible', [
- group([
- animate('0.03s linear', style({opacity: 1})),
- animate('0.12s cubic-bezier(0, 0, 0.2, 1)', style({transform: 'scaleY(1)'})),
- ]),
- ]),
- transition(':leave, visible => hidden', [animate('0.075s linear', style({opacity: 0}))]),
-]);
diff --git a/src/material/autocomplete/autocomplete-trigger.ts b/src/material/autocomplete/autocomplete-trigger.ts
index 287bd8c4e835..d9334f84c499 100644
--- a/src/material/autocomplete/autocomplete-trigger.ts
+++ b/src/material/autocomplete/autocomplete-trigger.ts
@@ -736,13 +736,7 @@ export class MatAutocompleteTrigger
) {
this._clearPreviousSelectedOption(null);
this._assignOptionValue(null);
- // Wait for the animation to finish before clearing the form control value, otherwise
- // the options might change while the animation is running which looks glitchy.
- if (panel._animationDone) {
- panel._animationDone.pipe(take(1)).subscribe(() => this._onChange(null));
- } else {
- this._onChange(null);
- }
+ this._onChange(null);
}
this.closePanel();
diff --git a/src/material/autocomplete/autocomplete.html b/src/material/autocomplete/autocomplete.html
index 73178da35a6f..6f62099ca795 100644
--- a/src/material/autocomplete/autocomplete.html
+++ b/src/material/autocomplete/autocomplete.html
@@ -6,13 +6,12 @@
[class]="_classList"
[class.mat-mdc-autocomplete-visible]="showPanel"
[class.mat-mdc-autocomplete-hidden]="!showPanel"
+ [class.mat-autocomplete-panel-animations-enabled]="!_animationsDisabled"
[class.mat-primary]="_color === 'primary'"
[class.mat-accent]="_color === 'accent'"
[class.mat-warn]="_color === 'warn'"
[attr.aria-label]="ariaLabel || null"
[attr.aria-labelledby]="_getPanelAriaLabelledby(formFieldId)"
- [@panelAnimation]="isOpen ? 'visible' : 'hidden'"
- (@panelAnimation.done)="_animationDone.next($event)"
#panel>
diff --git a/src/material/autocomplete/autocomplete.scss b/src/material/autocomplete/autocomplete.scss
index 0bd237c70422..064b07fc8412 100644
--- a/src/material/autocomplete/autocomplete.scss
+++ b/src/material/autocomplete/autocomplete.scss
@@ -50,6 +50,25 @@ div.mat-mdc-autocomplete-panel {
}
}
+@keyframes _mat-autocomplete-enter {
+ from {
+ opacity: 0;
+ transform: scaleY(0.8);
+ }
+
+ to {
+ opacity: 1;
+ transform: none;
+ }
+}
+
+// Note: the autocomplete intentionally only implements an enter animation.
+// The exit animation can look glitchy, because usually selecting an option
+// causes the list to change and jump around while it's animating.
+.mat-autocomplete-panel-animations-enabled {
+ animation: _mat-autocomplete-enter 120ms cubic-bezier(0, 0, 0.2, 1);
+}
+
// Prevent the overlay host node from affecting its surrounding layout.
mat-autocomplete {
display: none;
diff --git a/src/material/autocomplete/autocomplete.spec.ts b/src/material/autocomplete/autocomplete.spec.ts
index e12dec3b59b7..0c4faee25baa 100644
--- a/src/material/autocomplete/autocomplete.spec.ts
+++ b/src/material/autocomplete/autocomplete.spec.ts
@@ -3849,6 +3849,8 @@ describe('MatAutocomplete', () => {
dispatchFakeEvent(document.querySelector('mat-option')!, 'click');
fixture.detectChanges();
+ fixture.componentInstance.trigger.openPanel();
+ fixture.detectChanges();
const selectedOption = document.querySelector('mat-option[aria-selected="true"]');
expect(selectedOption).withContext('Expected an option to be selected.').not.toBeNull();
@@ -3875,6 +3877,8 @@ describe('MatAutocomplete', () => {
dispatchFakeEvent(document.querySelector('mat-option')!, 'click');
fixture.detectChanges();
+ fixture.componentInstance.trigger.openPanel();
+ fixture.detectChanges();
const selectedOption = document.querySelector('mat-option[aria-selected="true"]');
expect(selectedOption).withContext('Expected an option to be selected.').not.toBeNull();
diff --git a/src/material/autocomplete/autocomplete.ts b/src/material/autocomplete/autocomplete.ts
index 390183a1ffc2..705b08879fb5 100644
--- a/src/material/autocomplete/autocomplete.ts
+++ b/src/material/autocomplete/autocomplete.ts
@@ -7,6 +7,7 @@
*/
import {
+ ANIMATION_MODULE_TYPE,
AfterContentInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
@@ -25,7 +26,6 @@ import {
booleanAttribute,
inject,
} from '@angular/core';
-import {AnimationEvent} from '@angular/animations';
import {
MAT_OPTGROUP,
MAT_OPTION_PARENT_COMPONENT,
@@ -35,7 +35,6 @@ import {
} from '@angular/material/core';
import {_IdGenerator, ActiveDescendantKeyManager} from '@angular/cdk/a11y';
import {Platform} from '@angular/cdk/platform';
-import {panelAnimation} from './animations';
import {Subscription} from 'rxjs';
/** Event object that is emitted when an autocomplete option is selected. */
@@ -109,18 +108,15 @@ export function MAT_AUTOCOMPLETE_DEFAULT_OPTIONS_FACTORY(): MatAutocompleteDefau
'class': 'mat-mdc-autocomplete',
},
providers: [{provide: MAT_OPTION_PARENT_COMPONENT, useExisting: MatAutocomplete}],
- animations: [panelAnimation],
})
export class MatAutocomplete implements AfterContentInit, OnDestroy {
private _changeDetectorRef = inject(ChangeDetectorRef);
private _elementRef = inject>(ElementRef);
protected _defaults = inject(MAT_AUTOCOMPLETE_DEFAULT_OPTIONS);
-
+ protected _animationsDisabled =
+ inject(ANIMATION_MODULE_TYPE, {optional: true}) === 'NoopAnimations';
private _activeOptionChanges = Subscription.EMPTY;
- /** Emits when the panel animation is done. Null if the panel doesn't animate. */
- _animationDone = new EventEmitter();
-
/** Manages active item in option list based on key events. */
_keyManager: ActiveDescendantKeyManager;
@@ -282,7 +278,6 @@ export class MatAutocomplete implements AfterContentInit, OnDestroy {
ngOnDestroy() {
this._keyManager?.destroy();
this._activeOptionChanges.unsubscribe();
- this._animationDone.complete();
}
/**
diff --git a/tools/public_api_guard/material/autocomplete.md b/tools/public_api_guard/material/autocomplete.md
index cbe39d11f711..a893df244e7a 100644
--- a/tools/public_api_guard/material/autocomplete.md
+++ b/tools/public_api_guard/material/autocomplete.md
@@ -7,7 +7,6 @@
import { ActiveDescendantKeyManager } from '@angular/cdk/a11y';
import { AfterContentInit } from '@angular/core';
import { AfterViewInit } from '@angular/core';
-import { AnimationEvent as AnimationEvent_2 } from '@angular/animations';
import { ControlValueAccessor } from '@angular/forms';
import { ElementRef } from '@angular/core';
import { EventEmitter } from '@angular/core';
@@ -57,7 +56,8 @@ export const MAT_AUTOCOMPLETE_VALUE_ACCESSOR: any;
// @public
export class MatAutocomplete implements AfterContentInit, OnDestroy {
constructor(...args: unknown[]);
- _animationDone: EventEmitter;
+ // (undocumented)
+ protected _animationsDisabled: boolean;
ariaLabel: string;
ariaLabelledby: string;
autoActiveFirstOption: boolean;