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;