@@ -31,12 +31,12 @@ import {
3131 booleanAttribute ,
3232 ANIMATION_MODULE_TYPE ,
3333 inject ,
34+ NgZone ,
3435} from '@angular/core' ;
3536import { _IdGenerator } from '@angular/cdk/a11y' ;
3637import { Subject } from 'rxjs' ;
3738import { filter , startWith , take } from 'rxjs/operators' ;
3839import { MatAccordionBase , MatAccordionTogglePosition , MAT_ACCORDION } from './accordion-base' ;
39- import { matExpansionAnimations } from './expansion-animations' ;
4040import { MAT_EXPANSION_PANEL } from './expansion-panel-base' ;
4141import { MatExpansionPanelContent } from './expansion-panel-content' ;
4242
@@ -76,7 +76,6 @@ export const MAT_EXPANSION_PANEL_DEFAULT_OPTIONS =
7676 templateUrl : 'expansion-panel.html' ,
7777 encapsulation : ViewEncapsulation . None ,
7878 changeDetection : ChangeDetectionStrategy . OnPush ,
79- animations : [ matExpansionAnimations . bodyExpansion ] ,
8079 providers : [
8180 // Provide MatAccordion as undefined to prevent nested expansion panels from registering
8281 // to the same accordion.
@@ -86,7 +85,6 @@ export const MAT_EXPANSION_PANEL_DEFAULT_OPTIONS =
8685 host : {
8786 'class' : 'mat-expansion-panel' ,
8887 '[class.mat-expanded]' : 'expanded' ,
89- '[class._mat-animation-noopable]' : '_animationsDisabled' ,
9088 '[class.mat-expansion-panel-spacing]' : '_hasSpacing()' ,
9189 } ,
9290 imports : [ CdkPortalOutlet ] ,
@@ -96,10 +94,11 @@ export class MatExpansionPanel
9694 implements AfterContentInit , OnChanges , OnDestroy
9795{
9896 private _viewContainerRef = inject ( ViewContainerRef ) ;
99- _animationMode = inject ( ANIMATION_MODULE_TYPE , { optional : true } ) ;
100-
101- protected _animationsDisabled : boolean ;
97+ private readonly _animationsDisabled =
98+ inject ( ANIMATION_MODULE_TYPE , { optional : true } ) === 'NoopAnimations' ;
10299 private _document = inject ( DOCUMENT ) ;
100+ private _ngZone = inject ( NgZone ) ;
101+ private _elementRef = inject < ElementRef < HTMLElement > > ( ElementRef ) ;
103102
104103 /** Whether the toggle indicator should be hidden. */
105104 @Input ( { transform : booleanAttribute } )
@@ -139,6 +138,10 @@ export class MatExpansionPanel
139138 /** Element containing the panel's user-provided content. */
140139 @ViewChild ( 'body' ) _body : ElementRef < HTMLElement > ;
141140
141+ /** Element wrapping the panel body. */
142+ @ViewChild ( 'bodyWrapper' )
143+ protected _bodyWrapper : ElementRef < HTMLElement > | undefined ;
144+
142145 /** Portal holding the user's content. */
143146 _portal : TemplatePortal ;
144147
@@ -156,7 +159,6 @@ export class MatExpansionPanel
156159 ) ;
157160
158161 this . _expansionDispatcher = inject ( UniqueSelectionDispatcher ) ;
159- this . _animationsDisabled = this . _animationMode === 'NoopAnimations' ;
160162
161163 if ( defaultOptions ) {
162164 this . hideToggle = defaultOptions . hideToggle ;
@@ -204,6 +206,19 @@ export class MatExpansionPanel
204206 this . _portal = new TemplatePortal ( this . _lazyContent . _template , this . _viewContainerRef ) ;
205207 } ) ;
206208 }
209+
210+ this . _ngZone . runOutsideAngular ( ( ) => {
211+ if ( this . _animationsDisabled ) {
212+ this . opened . subscribe ( ( ) => this . afterExpand . emit ( ) ) ;
213+ this . closed . subscribe ( ( ) => this . afterCollapse . emit ( ) ) ;
214+ } else {
215+ setTimeout ( ( ) => {
216+ const element = this . _elementRef . nativeElement ;
217+ element . addEventListener ( 'transitionend' , this . _transitionEndListener ) ;
218+ element . classList . add ( 'mat-expansion-panel-animations-enabled' ) ;
219+ } , 200 ) ;
220+ }
221+ } ) ;
207222 }
208223
209224 ngOnChanges ( changes : SimpleChanges ) {
@@ -212,6 +227,10 @@ export class MatExpansionPanel
212227
213228 override ngOnDestroy ( ) {
214229 super . ngOnDestroy ( ) ;
230+ this . _bodyWrapper ?. nativeElement . removeEventListener (
231+ 'transitionend' ,
232+ this . _transitionEndListener ,
233+ ) ;
215234 this . _inputChanges . complete ( ) ;
216235 }
217236
@@ -226,36 +245,17 @@ export class MatExpansionPanel
226245 return false ;
227246 }
228247
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- }
248+ private _transitionEndListener = ( { target, propertyName} : TransitionEvent ) => {
249+ if ( target === this . _bodyWrapper ?. nativeElement && propertyName === 'grid-template-rows' ) {
250+ this . _ngZone . run ( ( ) => {
251+ if ( this . expanded ) {
252+ this . afterExpand . emit ( ) ;
253+ } else {
254+ this . afterCollapse . emit ( ) ;
255+ }
256+ } ) ;
252257 }
253- }
254- }
255-
256- /** Checks whether an animation is the initial setup animation. */
257- function isInitialAnimation ( event : AnimationEvent ) : boolean {
258- return event . fromState === 'void' ;
258+ } ;
259259}
260260
261261/**
0 commit comments