@@ -36,7 +36,7 @@ import {
3636 ANIMATION_MODULE_TYPE ,
3737} from '@angular/core' ;
3838import { Subject } from 'rxjs' ;
39- import { distinctUntilChanged , filter , startWith , take } from 'rxjs/operators' ;
39+ import { filter , startWith , take } from 'rxjs/operators' ;
4040import { MatAccordionBase , MatAccordionTogglePosition , MAT_ACCORDION } from './accordion-base' ;
4141import { matExpansionAnimations } from './expansion-animations' ;
4242import { MAT_EXPANSION_PANEL } from './expansion-panel-base' ;
@@ -147,9 +147,6 @@ export class MatExpansionPanel
147147 /** ID for the associated header element. Used for a11y labelling. */
148148 _headerId = `mat-expansion-panel-header-${ uniqueId ++ } ` ;
149149
150- /** Stream of body animation done events. */
151- readonly _bodyAnimationDone = new Subject < AnimationEvent > ( ) ;
152-
153150 constructor (
154151 @Optional ( ) @SkipSelf ( ) @Inject ( MAT_ACCORDION ) accordion : MatAccordionBase ,
155152 _changeDetectorRef : ChangeDetectorRef ,
@@ -165,24 +162,6 @@ export class MatExpansionPanel
165162 this . accordion = accordion ;
166163 this . _document = _document ;
167164
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- } ) ;
185-
186165 if ( defaultOptions ) {
187166 this . hideToggle = defaultOptions . hideToggle ;
188167 }
@@ -237,7 +216,6 @@ export class MatExpansionPanel
237216
238217 override ngOnDestroy ( ) {
239218 super . ngOnDestroy ( ) ;
240- this . _bodyAnimationDone . complete ( ) ;
241219 this . _inputChanges . complete ( ) ;
242220 }
243221
@@ -251,6 +229,35 @@ export class MatExpansionPanel
251229
252230 return false ;
253231 }
232+
233+ /** Called when the expansion animation has started. */
234+ protected _animationStarted ( event : AnimationEvent ) {
235+ if ( ! isInitialAnimation ( event ) ) {
236+ // Prevent the user from tabbing into the content while it's animating.
237+ // TODO(crisbeto): maybe use `inert` to prevent focus from entering while closed as well
238+ // instead of `visibility`? Will allow us to clean up some code but needs more testing.
239+ this . _body ?. nativeElement . setAttribute ( 'inert' , '' ) ;
240+ }
241+ }
242+
243+ /** Called when the expansion animation has finished. */
244+ protected _animationDone ( event : AnimationEvent ) {
245+ if ( ! isInitialAnimation ( event ) ) {
246+ if ( event . toState === 'expanded' ) {
247+ this . afterExpand . emit ( ) ;
248+ } else if ( event . toState === 'collapsed' ) {
249+ this . afterCollapse . emit ( ) ;
250+ }
251+
252+ // Re-enabled tabbing once the animation is finished.
253+ this . _body ?. nativeElement . removeAttribute ( 'inert' ) ;
254+ }
255+ }
256+ }
257+
258+ /** Checks whether an animation is the initial setup animation. */
259+ function isInitialAnimation ( event : AnimationEvent ) : boolean {
260+ return event . fromState === 'void' ;
254261}
255262
256263/**
0 commit comments