6
6
* found in the LICENSE file at https://angular.dev/license
7
7
*/
8
8
9
- import { AnimationEvent } from '@angular/animations' ;
10
9
import { CdkAccordionItem } from '@angular/cdk/accordion' ;
11
10
import { UniqueSelectionDispatcher } from '@angular/cdk/collections' ;
12
11
import { CdkPortalOutlet , TemplatePortal } from '@angular/cdk/portal' ;
@@ -31,12 +30,12 @@ import {
31
30
booleanAttribute ,
32
31
ANIMATION_MODULE_TYPE ,
33
32
inject ,
33
+ NgZone ,
34
34
} from '@angular/core' ;
35
35
import { _IdGenerator } from '@angular/cdk/a11y' ;
36
36
import { Subject } from 'rxjs' ;
37
37
import { filter , startWith , take } from 'rxjs/operators' ;
38
38
import { MatAccordionBase , MatAccordionTogglePosition , MAT_ACCORDION } from './accordion-base' ;
39
- import { matExpansionAnimations } from './expansion-animations' ;
40
39
import { MAT_EXPANSION_PANEL } from './expansion-panel-base' ;
41
40
import { MatExpansionPanelContent } from './expansion-panel-content' ;
42
41
@@ -76,7 +75,6 @@ export const MAT_EXPANSION_PANEL_DEFAULT_OPTIONS =
76
75
templateUrl : 'expansion-panel.html' ,
77
76
encapsulation : ViewEncapsulation . None ,
78
77
changeDetection : ChangeDetectionStrategy . OnPush ,
79
- animations : [ matExpansionAnimations . bodyExpansion ] ,
80
78
providers : [
81
79
// Provide MatAccordion as undefined to prevent nested expansion panels from registering
82
80
// to the same accordion.
@@ -86,7 +84,6 @@ export const MAT_EXPANSION_PANEL_DEFAULT_OPTIONS =
86
84
host : {
87
85
'class' : 'mat-expansion-panel' ,
88
86
'[class.mat-expanded]' : 'expanded' ,
89
- '[class._mat-animation-noopable]' : '_animationsDisabled' ,
90
87
'[class.mat-expansion-panel-spacing]' : '_hasSpacing()' ,
91
88
} ,
92
89
imports : [ CdkPortalOutlet ] ,
@@ -96,10 +93,11 @@ export class MatExpansionPanel
96
93
implements AfterContentInit , OnChanges , OnDestroy
97
94
{
98
95
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' ;
102
98
private _document = inject ( DOCUMENT ) ;
99
+ private _ngZone = inject ( NgZone ) ;
100
+ private _elementRef = inject < ElementRef < HTMLElement > > ( ElementRef ) ;
103
101
104
102
/** Whether the toggle indicator should be hidden. */
105
103
@Input ( { transform : booleanAttribute } )
@@ -139,6 +137,10 @@ export class MatExpansionPanel
139
137
/** Element containing the panel's user-provided content. */
140
138
@ViewChild ( 'body' ) _body : ElementRef < HTMLElement > ;
141
139
140
+ /** Element wrapping the panel body. */
141
+ @ViewChild ( 'bodyWrapper' )
142
+ protected _bodyWrapper : ElementRef < HTMLElement > | undefined ;
143
+
142
144
/** Portal holding the user's content. */
143
145
_portal : TemplatePortal ;
144
146
@@ -156,7 +158,6 @@ export class MatExpansionPanel
156
158
) ;
157
159
158
160
this . _expansionDispatcher = inject ( UniqueSelectionDispatcher ) ;
159
- this . _animationsDisabled = this . _animationMode === 'NoopAnimations' ;
160
161
161
162
if ( defaultOptions ) {
162
163
this . hideToggle = defaultOptions . hideToggle ;
@@ -204,6 +205,8 @@ export class MatExpansionPanel
204
205
this . _portal = new TemplatePortal ( this . _lazyContent . _template , this . _viewContainerRef ) ;
205
206
} ) ;
206
207
}
208
+
209
+ this . _setupAnimationEvents ( ) ;
207
210
}
208
211
209
212
ngOnChanges ( changes : SimpleChanges ) {
@@ -212,6 +215,10 @@ export class MatExpansionPanel
212
215
213
216
override ngOnDestroy ( ) {
214
217
super . ngOnDestroy ( ) ;
218
+ this . _bodyWrapper ?. nativeElement . removeEventListener (
219
+ 'transitionend' ,
220
+ this . _transitionEndListener ,
221
+ ) ;
215
222
this . _inputChanges . complete ( ) ;
216
223
}
217
224
@@ -226,38 +233,36 @@ export class MatExpansionPanel
226
233
return false ;
227
234
}
228
235
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
+ private _transitionEndListener = ( { target, propertyName} : TransitionEvent ) => {
237
+ if ( target === this . _bodyWrapper ?. nativeElement && propertyName === 'grid-template-rows' ) {
238
+ this . _ngZone . run ( ( ) => {
239
+ if ( this . expanded ) {
240
+ this . afterExpand . emit ( ) ;
241
+ } else {
242
+ this . afterCollapse . emit ( ) ;
243
+ }
244
+ } ) ;
236
245
}
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
+ protected _setupAnimationEvents ( ) {
249
+ // This method is defined separately, because we need to
250
+ // disable this logic in some internal components.
251
+ this . _ngZone . runOutsideAngular ( ( ) => {
252
+ if ( this . _animationsDisabled ) {
253
+ this . opened . subscribe ( ( ) => this . _ngZone . run ( ( ) => this . afterExpand . emit ( ) ) ) ;
254
+ this . closed . subscribe ( ( ) => this . _ngZone . run ( ( ) => this . afterCollapse . emit ( ) ) ) ;
255
+ } else {
256
+ setTimeout ( ( ) => {
257
+ const element = this . _elementRef . nativeElement ;
258
+ element . addEventListener ( 'transitionend' , this . _transitionEndListener ) ;
259
+ element . classList . add ( 'mat-expansion-panel-animations-enabled' ) ;
260
+ } , 200 ) ;
246
261
}
247
-
248
- // Re-enable tabbing once the animation is finished.
249
- if ( ! this . _animationsDisabled && this . _body ) {
250
- this . _body . nativeElement . removeAttribute ( 'inert' ) ;
251
- }
252
- }
262
+ } ) ;
253
263
}
254
264
}
255
265
256
- /** Checks whether an animation is the initial setup animation. */
257
- function isInitialAnimation ( event : AnimationEvent ) : boolean {
258
- return event . fromState === 'void' ;
259
- }
260
-
261
266
/**
262
267
* Actions of a `<mat-expansion-panel>`.
263
268
*/
0 commit comments