@@ -36,7 +36,7 @@ import {
36
36
ANIMATION_MODULE_TYPE ,
37
37
} from '@angular/core' ;
38
38
import { Subject } from 'rxjs' ;
39
- import { distinctUntilChanged , filter , startWith , take } from 'rxjs/operators' ;
39
+ import { filter , startWith , take } from 'rxjs/operators' ;
40
40
import { MatAccordionBase , MatAccordionTogglePosition , MAT_ACCORDION } from './accordion-base' ;
41
41
import { matExpansionAnimations } from './expansion-animations' ;
42
42
import { MAT_EXPANSION_PANEL } from './expansion-panel-base' ;
@@ -91,7 +91,7 @@ export const MAT_EXPANSION_PANEL_DEFAULT_OPTIONS =
91
91
host : {
92
92
'class' : 'mat-expansion-panel' ,
93
93
'[class.mat-expanded]' : 'expanded' ,
94
- '[class._mat-animation-noopable]' : '_animationMode === "NoopAnimations" ' ,
94
+ '[class._mat-animation-noopable]' : '_animationsDisabled ' ,
95
95
'[class.mat-expansion-panel-spacing]' : '_hasSpacing()' ,
96
96
} ,
97
97
standalone : true ,
@@ -101,6 +101,7 @@ export class MatExpansionPanel
101
101
extends CdkAccordionItem
102
102
implements AfterContentInit , OnChanges , OnDestroy
103
103
{
104
+ protected _animationsDisabled : boolean ;
104
105
private _document : Document ;
105
106
106
107
/** Whether the toggle indicator should be hidden. */
@@ -147,9 +148,6 @@ export class MatExpansionPanel
147
148
/** ID for the associated header element. Used for a11y labelling. */
148
149
_headerId = `mat-expansion-panel-header-${ uniqueId ++ } ` ;
149
150
150
- /** Stream of body animation done events. */
151
- readonly _bodyAnimationDone = new Subject < AnimationEvent > ( ) ;
152
-
153
151
constructor (
154
152
@Optional ( ) @SkipSelf ( ) @Inject ( MAT_ACCORDION ) accordion : MatAccordionBase ,
155
153
_changeDetectorRef : ChangeDetectorRef ,
@@ -164,24 +162,7 @@ export class MatExpansionPanel
164
162
super ( accordion , _changeDetectorRef , _uniqueSelectionDispatcher ) ;
165
163
this . accordion = accordion ;
166
164
this . _document = _document ;
167
-
168
- // We need a Subject with distinctUntilChanged, because the `done` event
169
- // fires twice on some browsers. See https://github.com/angular/angular/issues/24084
170
- this . _bodyAnimationDone
171
- . pipe (
172
- distinctUntilChanged ( ( x , y ) => {
173
- return x . fromState === y . fromState && x . toState === y . toState ;
174
- } ) ,
175
- )
176
- . subscribe ( event => {
177
- if ( event . fromState !== 'void' ) {
178
- if ( event . toState === 'expanded' ) {
179
- this . afterExpand . emit ( ) ;
180
- } else if ( event . toState === 'collapsed' ) {
181
- this . afterCollapse . emit ( ) ;
182
- }
183
- }
184
- } ) ;
165
+ this . _animationsDisabled = _animationMode === 'NoopAnimations' ;
185
166
186
167
if ( defaultOptions ) {
187
168
this . hideToggle = defaultOptions . hideToggle ;
@@ -237,7 +218,6 @@ export class MatExpansionPanel
237
218
238
219
override ngOnDestroy ( ) {
239
220
super . ngOnDestroy ( ) ;
240
- this . _bodyAnimationDone . complete ( ) ;
241
221
this . _inputChanges . complete ( ) ;
242
222
}
243
223
@@ -251,6 +231,37 @@ export class MatExpansionPanel
251
231
252
232
return false ;
253
233
}
234
+
235
+ /** Called when the expansion animation has started. */
236
+ protected _animationStarted ( event : AnimationEvent ) {
237
+ if ( ! isInitialAnimation ( event ) && ! this . _animationsDisabled && this . _body ) {
238
+ // Prevent the user from tabbing into the content while it's animating.
239
+ // TODO(crisbeto): maybe use `inert` to prevent focus from entering while closed as well
240
+ // instead of `visibility`? Will allow us to clean up some code but needs more testing.
241
+ this . _body ?. nativeElement . setAttribute ( 'inert' , '' ) ;
242
+ }
243
+ }
244
+
245
+ /** Called when the expansion animation has finished. */
246
+ protected _animationDone ( event : AnimationEvent ) {
247
+ if ( ! isInitialAnimation ( event ) ) {
248
+ if ( event . toState === 'expanded' ) {
249
+ this . afterExpand . emit ( ) ;
250
+ } else if ( event . toState === 'collapsed' ) {
251
+ this . afterCollapse . emit ( ) ;
252
+ }
253
+
254
+ // Re-enable tabbing once the animation is finished.
255
+ if ( ! this . _animationsDisabled && this . _body ) {
256
+ this . _body . nativeElement . removeAttribute ( 'inert' ) ;
257
+ }
258
+ }
259
+ }
260
+ }
261
+
262
+ /** Checks whether an animation is the initial setup animation. */
263
+ function isInitialAnimation ( event : AnimationEvent ) : boolean {
264
+ return event . fromState === 'void' ;
254
265
}
255
266
256
267
/**
0 commit comments