66 * found in the LICENSE file at https://angular.dev/license
77 */
88
9- import { AnimationEvent } from '@angular/animations' ;
109import { CdkAccordionItem } from '@angular/cdk/accordion' ;
1110import { UniqueSelectionDispatcher } from '@angular/cdk/collections' ;
1211import { CdkPortalOutlet , TemplatePortal } from '@angular/cdk/portal' ;
@@ -31,12 +30,12 @@ import {
3130 booleanAttribute ,
3231 ANIMATION_MODULE_TYPE ,
3332 inject ,
33+ NgZone ,
3434} from '@angular/core' ;
3535import { _IdGenerator } from '@angular/cdk/a11y' ;
3636import { Subject } from 'rxjs' ;
3737import { filter , startWith , take } from 'rxjs/operators' ;
3838import { MatAccordionBase , MatAccordionTogglePosition , MAT_ACCORDION } from './accordion-base' ;
39- import { matExpansionAnimations } from './expansion-animations' ;
4039import { MAT_EXPANSION_PANEL } from './expansion-panel-base' ;
4140import { MatExpansionPanelContent } from './expansion-panel-content' ;
4241
@@ -76,7 +75,6 @@ export const MAT_EXPANSION_PANEL_DEFAULT_OPTIONS =
7675 templateUrl : 'expansion-panel.html' ,
7776 encapsulation : ViewEncapsulation . None ,
7877 changeDetection : ChangeDetectionStrategy . OnPush ,
79- animations : [ matExpansionAnimations . bodyExpansion ] ,
8078 providers : [
8179 // Provide MatAccordion as undefined to prevent nested expansion panels from registering
8280 // to the same accordion.
@@ -86,7 +84,6 @@ export const MAT_EXPANSION_PANEL_DEFAULT_OPTIONS =
8684 host : {
8785 'class' : 'mat-expansion-panel' ,
8886 '[class.mat-expanded]' : 'expanded' ,
89- '[class._mat-animation-noopable]' : '_animationsDisabled' ,
9087 '[class.mat-expansion-panel-spacing]' : '_hasSpacing()' ,
9188 } ,
9289 imports : [ CdkPortalOutlet ] ,
@@ -96,10 +93,11 @@ export class MatExpansionPanel
9693 implements AfterContentInit , OnChanges , OnDestroy
9794{
9895 private _viewContainerRef = inject ( ViewContainerRef ) ;
99- _animationMode = inject ( ANIMATION_MODULE_TYPE , { optional : true } ) ;
100-
101- protected _animationsDisabled : boolean ;
96+ private readonly _animationsDisabled =
97+ inject ( ANIMATION_MODULE_TYPE , { optional : true } ) === 'NoopAnimations' ;
10298 private _document = inject ( DOCUMENT ) ;
99+ private _ngZone = inject ( NgZone ) ;
100+ private _elementRef = inject < ElementRef < HTMLElement > > ( ElementRef ) ;
103101
104102 /** Whether the toggle indicator should be hidden. */
105103 @Input ( { transform : booleanAttribute } )
@@ -139,6 +137,10 @@ export class MatExpansionPanel
139137 /** Element containing the panel's user-provided content. */
140138 @ViewChild ( 'body' ) _body : ElementRef < HTMLElement > ;
141139
140+ /** Element wrapping the panel body. */
141+ @ViewChild ( 'bodyWrapper' )
142+ protected _bodyWrapper : ElementRef < HTMLElement > | undefined ;
143+
142144 /** Portal holding the user's content. */
143145 _portal : TemplatePortal ;
144146
@@ -156,7 +158,6 @@ export class MatExpansionPanel
156158 ) ;
157159
158160 this . _expansionDispatcher = inject ( UniqueSelectionDispatcher ) ;
159- this . _animationsDisabled = this . _animationMode === 'NoopAnimations' ;
160161
161162 if ( defaultOptions ) {
162163 this . hideToggle = defaultOptions . hideToggle ;
@@ -204,6 +205,19 @@ export class MatExpansionPanel
204205 this . _portal = new TemplatePortal ( this . _lazyContent . _template , this . _viewContainerRef ) ;
205206 } ) ;
206207 }
208+
209+ this . _ngZone . runOutsideAngular ( ( ) => {
210+ if ( this . _animationsDisabled ) {
211+ this . opened . subscribe ( ( ) => this . afterExpand . emit ( ) ) ;
212+ this . closed . subscribe ( ( ) => this . afterCollapse . emit ( ) ) ;
213+ } else {
214+ setTimeout ( ( ) => {
215+ const element = this . _elementRef . nativeElement ;
216+ element . addEventListener ( 'transitionend' , this . _transitionEndListener ) ;
217+ element . classList . add ( 'mat-expansion-panel-animations-enabled' ) ;
218+ } , 200 ) ;
219+ }
220+ } ) ;
207221 }
208222
209223 ngOnChanges ( changes : SimpleChanges ) {
@@ -212,6 +226,10 @@ export class MatExpansionPanel
212226
213227 override ngOnDestroy ( ) {
214228 super . ngOnDestroy ( ) ;
229+ this . _bodyWrapper ?. nativeElement . removeEventListener (
230+ 'transitionend' ,
231+ this . _transitionEndListener ,
232+ ) ;
215233 this . _inputChanges . complete ( ) ;
216234 }
217235
@@ -226,36 +244,17 @@ export class MatExpansionPanel
226244 return false ;
227245 }
228246
229- /** Called when the expansion animation has started. */
230- protected _animationStarted ( event : AnimationEvent ) {
231- if ( ! isInitialAnimation ( event ) && ! this . _animationsDisabled && this . _body ) {
232- // Prevent the user from tabbing into the content while it's animating.
233- // TODO(crisbeto): maybe use `inert` to prevent focus from entering while closed as well
234- // instead of `visibility`? Will allow us to clean up some code but needs more testing.
235- this . _body ?. nativeElement . setAttribute ( 'inert' , '' ) ;
236- }
237- }
238-
239- /** Called when the expansion animation has finished. */
240- protected _animationDone ( event : AnimationEvent ) {
241- if ( ! isInitialAnimation ( event ) ) {
242- if ( event . toState === 'expanded' ) {
243- this . afterExpand . emit ( ) ;
244- } else if ( event . toState === 'collapsed' ) {
245- this . afterCollapse . emit ( ) ;
246- }
247-
248- // Re-enable tabbing once the animation is finished.
249- if ( ! this . _animationsDisabled && this . _body ) {
250- this . _body . nativeElement . removeAttribute ( 'inert' ) ;
251- }
247+ private _transitionEndListener = ( { target, propertyName} : TransitionEvent ) => {
248+ if ( target === this . _bodyWrapper ?. nativeElement && propertyName === 'grid-template-rows' ) {
249+ this . _ngZone . run ( ( ) => {
250+ if ( this . expanded ) {
251+ this . afterExpand . emit ( ) ;
252+ } else {
253+ this . afterCollapse . emit ( ) ;
254+ }
255+ } ) ;
252256 }
253- }
254- }
255-
256- /** Checks whether an animation is the initial setup animation. */
257- function isInitialAnimation ( event : AnimationEvent ) : boolean {
258- return event . fromState === 'void' ;
257+ } ;
259258}
260259
261260/**
0 commit comments