Skip to content

Commit 3d1cfd4

Browse files
committed
fix(material/expansion): prevent focus issue during collapse animation
This commit addresses an issue where mat-expansion-panels were breaking when a user attempted to quickly close and tab away from a panel after closing it. The issue occurred because the tab key was being pressed between the collapsing animation, causing the panel body to remain visible and receive focus. To resolve this, we manually set the panel's visibility to hidden when the tab key is pressed during the collapsing animation. This ensures that the next element in the tab order receives focus as expected. Fixes #27430.
1 parent 5fca612 commit 3d1cfd4

File tree

3 files changed

+31
-4
lines changed

3 files changed

+31
-4
lines changed

src/material/expansion/expansion-panel-header.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import {FocusableOption, FocusMonitor, FocusOrigin} from '@angular/cdk/a11y';
10-
import {ENTER, hasModifierKey, SPACE} from '@angular/cdk/keycodes';
10+
import {ENTER, hasModifierKey, SPACE, TAB} from '@angular/cdk/keycodes';
1111
import {
1212
AfterViewInit,
1313
Attribute,
@@ -179,6 +179,13 @@ export class MatExpansionPanelHeader
179179
return null;
180180
}
181181

182+
/** Handle default for keydown event. */
183+
private _handleDefaultKeydown(event: KeyboardEvent) {
184+
if (this.panel.accordion) {
185+
this.panel.accordion._handleHeaderKeydown(event);
186+
}
187+
}
188+
182189
/** Handle keydown event calling to toggle() if appropriate. */
183190
_keydown(event: KeyboardEvent) {
184191
switch (event.keyCode) {
@@ -191,10 +198,15 @@ export class MatExpansionPanelHeader
191198
}
192199

193200
break;
194-
default:
195-
if (this.panel.accordion) {
196-
this.panel.accordion._handleHeaderKeydown(event);
201+
case TAB:
202+
if (this.panel.collapsingAnimation) {
203+
this.panel._body.nativeElement.setAttribute('style', 'visibility: hidden');
197204
}
205+
this._handleDefaultKeydown(event);
206+
207+
break;
208+
default:
209+
this._handleDefaultKeydown(event);
198210

199211
return;
200212
}

src/material/expansion/expansion-panel.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<div class="mat-expansion-panel-content"
33
role="region"
44
[@bodyExpansion]="_getExpandedState()"
5+
(@bodyExpansion.start)="_bodyAnimationStart($event)"
56
(@bodyExpansion.done)="_bodyAnimationDone.next($event)"
67
[attr.aria-labelledby]="_headerId"
78
[id]="id"

src/material/expansion/expansion-panel.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ export class MatExpansionPanel
104104
private _document: Document;
105105
private _hideToggle = false;
106106
private _togglePosition: MatAccordionTogglePosition;
107+
private _collapsingAnimation = false;
108+
109+
/** whether the element is currently undergoing the collapsing animation */
110+
get collapsingAnimation(): boolean {
111+
return this._collapsingAnimation;
112+
}
107113

108114
/** Whether the toggle indicator should be hidden. */
109115
@Input()
@@ -174,6 +180,7 @@ export class MatExpansionPanel
174180
}),
175181
)
176182
.subscribe(event => {
183+
this._collapsingAnimation = false;
177184
if (event.fromState !== 'void') {
178185
if (event.toState === 'expanded') {
179186
this.afterExpand.emit();
@@ -188,6 +195,13 @@ export class MatExpansionPanel
188195
}
189196
}
190197

198+
/** Check and determine if the element is currently undergoing a collapsing animation. */
199+
_bodyAnimationStart(event: AnimationEvent) {
200+
if (event.toState === 'collapsed') {
201+
this._collapsingAnimation = true;
202+
}
203+
}
204+
191205
/** Determines whether the expansion panel should have spacing between it and its siblings. */
192206
_hasSpacing(): boolean {
193207
if (this.accordion) {

0 commit comments

Comments
 (0)