Skip to content

Commit ee5b13f

Browse files
authored
fix(material/bottom-sheet): focus first tabbable element by default (#30549)
Switches the bottom sheet to focusing the first tabbale element by default like we do in `MatDialog`. Note that we had a comment saying that the previous behavior was intentional. That seems to be a leftover from #14534 where the underlying cause was that we were delaying focus until the animation is done. We now have better ways of handling it so the comment isn't relevant anymore. Fixes #30483.
1 parent 9b6a51e commit ee5b13f

File tree

4 files changed

+48
-71
lines changed

4 files changed

+48
-71
lines changed

src/material/bottom-sheet/bottom-sheet-config.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,12 @@ export class MatBottomSheetConfig<D = any> {
5858
*/
5959
closeOnNavigation?: boolean = true;
6060

61-
// Note that this is set to 'dialog' by default, because while the a11y recommendations
62-
// are to focus the first focusable element, doing so prevents screen readers from reading out the
63-
// rest of the bottom sheet content.
6461
/**
6562
* Where the bottom sheet should focus on open.
6663
* @breaking-change 14.0.0 Remove boolean option from autoFocus. Use string or
6764
* AutoFocusTarget instead.
6865
*/
69-
autoFocus?: AutoFocusTarget | string | boolean = 'dialog';
66+
autoFocus?: AutoFocusTarget | string | boolean = 'first-tabbable';
7067

7168
/**
7269
* Whether the bottom sheet should restore focus to the

src/material/bottom-sheet/bottom-sheet-container.ts

-6
Original file line numberDiff line numberDiff line change
@@ -137,17 +137,11 @@ export class MatBottomSheetContainer extends CdkDialogContainer implements OnDes
137137
const isEnter = animationName === ENTER_ANIMATION;
138138
const isExit = animationName === EXIT_ANIMATION;
139139

140-
if (isEnter) {
141-
this._trapFocus();
142-
}
143-
144140
if (isEnter || isExit) {
145141
this._animationStateChanged.emit({
146142
toState: isEnter ? 'visible' : 'hidden',
147143
phase: isStart ? 'start' : 'done',
148144
});
149145
}
150146
}
151-
152-
protected override _captureInitialFocus(): void {}
153147
}

src/material/bottom-sheet/bottom-sheet.spec.ts

+47-59
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,7 @@ describe('MatBottomSheet', () => {
622622
beforeEach(() => document.body.appendChild(overlayContainerElement));
623623
afterEach(() => overlayContainerElement.remove());
624624

625-
it('should focus the bottom sheet container by default', fakeAsync(() => {
625+
it('should focus the first tabbable element by default', fakeAsync(() => {
626626
bottomSheet.open(PizzaMsg, {
627627
viewContainerRef: testViewContainerRef,
628628
});
@@ -631,9 +631,7 @@ describe('MatBottomSheet', () => {
631631
flush();
632632
viewContainerFixture.detectChanges();
633633

634-
expect(document.activeElement!.tagName)
635-
.withContext('Expected bottom sheet container to be focused.')
636-
.toBe('MAT-BOTTOM-SHEET-CONTAINER');
634+
expect(document.activeElement!.tagName).toBe('INPUT');
637635
}));
638636

639637
it('should create a focus trap if autoFocus is disabled', fakeAsync(() => {
@@ -669,72 +667,62 @@ describe('MatBottomSheet', () => {
669667
},
670668
);
671669

672-
it(
673-
'should focus the bottom sheet element on open when autoFocus is set to ' +
674-
'"dialog" (the default)',
675-
fakeAsync(() => {
676-
bottomSheet.open(PizzaMsg, {
677-
viewContainerRef: testViewContainerRef,
678-
});
670+
it('should focus the bottom sheet element on open when autoFocus is set to "dialog"', fakeAsync(() => {
671+
bottomSheet.open(PizzaMsg, {
672+
viewContainerRef: testViewContainerRef,
673+
autoFocus: 'dialog',
674+
});
679675

680-
viewContainerFixture.detectChanges();
681-
flush();
682-
viewContainerFixture.detectChanges();
676+
viewContainerFixture.detectChanges();
677+
flush();
678+
viewContainerFixture.detectChanges();
683679

684-
let container = overlayContainerElement.querySelector(
685-
'.mat-bottom-sheet-container',
686-
) as HTMLInputElement;
680+
let container = overlayContainerElement.querySelector(
681+
'.mat-bottom-sheet-container',
682+
) as HTMLInputElement;
687683

688-
expect(document.activeElement)
689-
.withContext('Expected container to be focused on open')
690-
.toBe(container);
691-
}),
692-
);
684+
expect(document.activeElement)
685+
.withContext('Expected container to be focused on open')
686+
.toBe(container);
687+
}));
693688

694-
it(
695-
'should focus the bottom sheet element on open when autoFocus is set to ' + '"first-heading"',
696-
fakeAsync(() => {
697-
bottomSheet.open(ContentElementDialog, {
698-
viewContainerRef: testViewContainerRef,
699-
autoFocus: 'first-heading',
700-
});
689+
it('should focus the bottom sheet element on open when autoFocus is set to "first-heading"', fakeAsync(() => {
690+
bottomSheet.open(ContentElementDialog, {
691+
viewContainerRef: testViewContainerRef,
692+
autoFocus: 'first-heading',
693+
});
701694

702-
viewContainerFixture.detectChanges();
703-
flush();
704-
viewContainerFixture.detectChanges();
695+
viewContainerFixture.detectChanges();
696+
flush();
697+
viewContainerFixture.detectChanges();
705698

706-
let firstHeader = overlayContainerElement.querySelector(
707-
'h1[tabindex="-1"]',
708-
) as HTMLInputElement;
699+
let firstHeader = overlayContainerElement.querySelector(
700+
'h1[tabindex="-1"]',
701+
) as HTMLInputElement;
709702

710-
expect(document.activeElement)
711-
.withContext('Expected first header to be focused on open')
712-
.toBe(firstHeader);
713-
}),
714-
);
703+
expect(document.activeElement)
704+
.withContext('Expected first header to be focused on open')
705+
.toBe(firstHeader);
706+
}));
715707

716-
it(
717-
'should focus the first element that matches the css selector on open when ' +
718-
'autoFocus is set to a css selector',
719-
fakeAsync(() => {
720-
bottomSheet.open(ContentElementDialog, {
721-
viewContainerRef: testViewContainerRef,
722-
autoFocus: 'p',
723-
});
708+
it('should focus the first element that matches the css selector on open when autoFocus is set to a css selector', fakeAsync(() => {
709+
bottomSheet.open(ContentElementDialog, {
710+
viewContainerRef: testViewContainerRef,
711+
autoFocus: 'p',
712+
});
724713

725-
viewContainerFixture.detectChanges();
726-
flush();
727-
viewContainerFixture.detectChanges();
714+
viewContainerFixture.detectChanges();
715+
flush();
716+
viewContainerFixture.detectChanges();
728717

729-
let firstParagraph = overlayContainerElement.querySelector(
730-
'p[tabindex="-1"]',
731-
) as HTMLInputElement;
718+
let firstParagraph = overlayContainerElement.querySelector(
719+
'p[tabindex="-1"]',
720+
) as HTMLInputElement;
732721

733-
expect(document.activeElement)
734-
.withContext('Expected first paragraph to be focused on open')
735-
.toBe(firstParagraph);
736-
}),
737-
);
722+
expect(document.activeElement)
723+
.withContext('Expected first paragraph to be focused on open')
724+
.toBe(firstParagraph);
725+
}));
738726

739727
it('should re-focus trigger element when bottom sheet closes', fakeAsync(() => {
740728
const button = document.createElement('button');

tools/public_api_guard/material/bottom-sheet.md

-2
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,6 @@ export class MatBottomSheetContainer extends CdkDialogContainer implements OnDes
8181
toState: "visible" | "hidden";
8282
phase: "start" | "done";
8383
}>;
84-
// (undocumented)
85-
protected _captureInitialFocus(): void;
8684
enter(): void;
8785
exit(): void;
8886
// (undocumented)

0 commit comments

Comments
 (0)