diff --git a/src/material/expansion/expansion-panel.html b/src/material/expansion/expansion-panel.html index 81b6153001fe..cdf99435966a 100644 --- a/src/material/expansion/expansion-panel.html +++ b/src/material/expansion/expansion-panel.html @@ -2,7 +2,8 @@
diff --git a/src/material/expansion/expansion-panel.ts b/src/material/expansion/expansion-panel.ts index d4878a24d91b..19c6966d918b 100644 --- a/src/material/expansion/expansion-panel.ts +++ b/src/material/expansion/expansion-panel.ts @@ -36,7 +36,7 @@ import { ANIMATION_MODULE_TYPE, } from '@angular/core'; import {Subject} from 'rxjs'; -import {distinctUntilChanged, filter, startWith, take} from 'rxjs/operators'; +import {filter, startWith, take} from 'rxjs/operators'; import {MatAccordionBase, MatAccordionTogglePosition, MAT_ACCORDION} from './accordion-base'; import {matExpansionAnimations} from './expansion-animations'; import {MAT_EXPANSION_PANEL} from './expansion-panel-base'; @@ -147,9 +147,6 @@ export class MatExpansionPanel /** ID for the associated header element. Used for a11y labelling. */ _headerId = `mat-expansion-panel-header-${uniqueId++}`; - /** Stream of body animation done events. */ - readonly _bodyAnimationDone = new Subject(); - constructor( @Optional() @SkipSelf() @Inject(MAT_ACCORDION) accordion: MatAccordionBase, _changeDetectorRef: ChangeDetectorRef, @@ -165,24 +162,6 @@ export class MatExpansionPanel this.accordion = accordion; this._document = _document; - // We need a Subject with distinctUntilChanged, because the `done` event - // fires twice on some browsers. See https://github.com/angular/angular/issues/24084 - this._bodyAnimationDone - .pipe( - distinctUntilChanged((x, y) => { - return x.fromState === y.fromState && x.toState === y.toState; - }), - ) - .subscribe(event => { - if (event.fromState !== 'void') { - if (event.toState === 'expanded') { - this.afterExpand.emit(); - } else if (event.toState === 'collapsed') { - this.afterCollapse.emit(); - } - } - }); - if (defaultOptions) { this.hideToggle = defaultOptions.hideToggle; } @@ -237,7 +216,6 @@ export class MatExpansionPanel override ngOnDestroy() { super.ngOnDestroy(); - this._bodyAnimationDone.complete(); this._inputChanges.complete(); } @@ -251,6 +229,35 @@ export class MatExpansionPanel return false; } + + /** Called when the expansion animation has started. */ + protected _animationStarted(event: AnimationEvent) { + if (!isInitialAnimation(event)) { + // Prevent the user from tabbing into the content while it's animating. + // TODO(crisbeto): maybe use `inert` to prevent focus from entering while closed as well + // instead of `visibility`? Will allow us to clean up some code but needs more testing. + this._body?.nativeElement.setAttribute('inert', ''); + } + } + + /** Called when the expansion animation has finished. */ + protected _animationDone(event: AnimationEvent) { + if (!isInitialAnimation(event)) { + if (event.toState === 'expanded') { + this.afterExpand.emit(); + } else if (event.toState === 'collapsed') { + this.afterCollapse.emit(); + } + + // Re-enabled tabbing once the animation is finished. + this._body?.nativeElement.removeAttribute('inert'); + } + } +} + +/** Checks whether an animation is the initial setup animation. */ +function isInitialAnimation(event: AnimationEvent): boolean { + return event.fromState === 'void'; } /** diff --git a/tools/public_api_guard/material/expansion.md b/tools/public_api_guard/material/expansion.md index a3a42fa79b63..e2ed014b7b7f 100644 --- a/tools/public_api_guard/material/expansion.md +++ b/tools/public_api_guard/material/expansion.md @@ -101,10 +101,11 @@ export class MatExpansionPanel extends CdkAccordionItem implements AfterContentI accordion: MatAccordionBase; readonly afterCollapse: EventEmitter; readonly afterExpand: EventEmitter; + protected _animationDone(event: AnimationEvent_2): void; // (undocumented) _animationMode: string; + protected _animationStarted(event: AnimationEvent_2): void; _body: ElementRef; - readonly _bodyAnimationDone: Subject; close(): void; _containsFocus(): boolean; _getExpandedState(): MatExpansionPanelState;