Skip to content

Commit 4bf8ebf

Browse files
authored
fix(material/chips): focus not moved on destroy (#31653)
We have some logic that moves focus if a chip is destroyed so that it doesn't go back to the `body`. The removal timing seems to have changed at some point which broke the focus restoration, because the element is blurred before it is destroyed. These changes make the logic a bit more robust by checking the key manager for the removed chip.
1 parent 845a691 commit 4bf8ebf

File tree

3 files changed

+20
-4
lines changed

3 files changed

+20
-4
lines changed

goldens/material/chips/index.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export class MatChip implements OnInit, AfterViewInit, AfterContentInit, DoCheck
7979
focus(): void;
8080
_getActions(): MatChipAction[];
8181
_getSourceAction(target: Node): MatChipAction | undefined;
82+
_hadFocusOnRemove: boolean;
8283
_handleKeydown(event: KeyboardEvent): void;
8384
_handlePrimaryActionInteraction(): void;
8485
// (undocumented)

src/material/chips/chip-set.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -288,14 +288,25 @@ export class MatChipSet implements AfterViewInit, OnDestroy {
288288
/** Starts tracking the destroyed chips in order to capture the focused one. */
289289
private _trackDestroyedFocusedChip() {
290290
this.chipDestroyedChanges.pipe(takeUntil(this._destroyed)).subscribe((event: MatChipEvent) => {
291-
const chipArray = this._chips.toArray();
292-
const chipIndex = chipArray.indexOf(event.chip);
293-
294291
// If the focused chip is destroyed, save its index so that we can move focus to the next
295292
// chip. We only save the index here, rather than move the focus immediately, because we want
296293
// to wait until the chip is removed from the chip list before focusing the next one. This
297294
// allows us to keep focus on the same index if the chip gets swapped out.
298-
if (this._isValidIndex(chipIndex) && event.chip._hasFocus()) {
295+
const chipArray = this._chips.toArray();
296+
const chipIndex = chipArray.indexOf(event.chip);
297+
const hasFocus = event.chip._hasFocus();
298+
const wasLastFocused =
299+
event.chip._hadFocusOnRemove &&
300+
this._keyManager.activeItem &&
301+
event.chip._getActions().includes(this._keyManager.activeItem);
302+
303+
// Note that depending on the timing, the chip might've already lost focus by the
304+
// time we check this. We need the `wasLastFocused` as a fallback to detect such cases.
305+
// In `wasLastFocused` we also need to ensure that the chip actually had focus when it was
306+
// deleted so that we don't steal away the user's focus after they've moved on from the chip.
307+
const shouldMoveFocus = hasFocus || wasLastFocused;
308+
309+
if (this._isValidIndex(chipIndex) && shouldMoveFocus) {
299310
this._lastDestroyedFocusedChipIndex = chipIndex;
300311
}
301312
});

src/material/chips/chip.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,9 @@ export class MatChip implements OnInit, AfterViewInit, AfterContentInit, DoCheck
172172
/** Whether the chip list is disabled. */
173173
_chipListDisabled: boolean = false;
174174

175+
/** Whether the chip was focused when it was removed. */
176+
_hadFocusOnRemove = false;
177+
175178
private _textElement!: HTMLElement;
176179

177180
/**
@@ -316,6 +319,7 @@ export class MatChip implements OnInit, AfterViewInit, AfterContentInit, DoCheck
316319
*/
317320
remove(): void {
318321
if (this.removable) {
322+
this._hadFocusOnRemove = this._hasFocus();
319323
this.removed.emit({chip: this});
320324
}
321325
}

0 commit comments

Comments
 (0)