Skip to content

Commit 39e4e24

Browse files
authored
fix(material/button-toggle): fix ChromeVox focus issue on button toggle (#21046)
* fix(material/button-toggle): fix ChromeVox focus issue on button toggle * fix(cdk/a11y): change focus trap to find focusable child element if cdkFocusInitial element isn't focusable
1 parent 8ee56a5 commit 39e4e24

File tree

3 files changed

+17
-13
lines changed

3 files changed

+17
-13
lines changed

src/cdk/a11y/focus-trap/focus-trap.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export class FocusTrap {
130130
* Waits for the zone to stabilize, then either focuses the first element that the
131131
* user specified, or the first tabbable element.
132132
* @returns Returns a promise that resolves with a boolean, depending
133-
* on whether focus was moved successfuly.
133+
* on whether focus was moved successfully.
134134
*/
135135
focusInitialElementWhenReady(): Promise<boolean> {
136136
return new Promise<boolean>(resolve => {
@@ -142,7 +142,7 @@ export class FocusTrap {
142142
* Waits for the zone to stabilize, then focuses
143143
* the first tabbable element within the focus trap region.
144144
* @returns Returns a promise that resolves with a boolean, depending
145-
* on whether focus was moved successfuly.
145+
* on whether focus was moved successfully.
146146
*/
147147
focusFirstTabbableElementWhenReady(): Promise<boolean> {
148148
return new Promise<boolean>(resolve => {
@@ -154,7 +154,7 @@ export class FocusTrap {
154154
* Waits for the zone to stabilize, then focuses
155155
* the last tabbable element within the focus trap region.
156156
* @returns Returns a promise that resolves with a boolean, depending
157-
* on whether focus was moved successfuly.
157+
* on whether focus was moved successfully.
158158
*/
159159
focusLastTabbableElementWhenReady(): Promise<boolean> {
160160
return new Promise<boolean>(resolve => {
@@ -195,7 +195,7 @@ export class FocusTrap {
195195

196196
/**
197197
* Focuses the element that should be focused when the focus trap is initialized.
198-
* @returns Whether focus was moved successfuly.
198+
* @returns Whether focus was moved successfully.
199199
*/
200200
focusInitialElement(): boolean {
201201
// Contains the deprecated version of selector, for temporary backwards comparability.
@@ -217,6 +217,12 @@ export class FocusTrap {
217217
console.warn(`Element matching '[cdkFocusInitial]' is not focusable.`, redirectToElement);
218218
}
219219

220+
if (!this._checker.isFocusable(redirectToElement)) {
221+
const focusableChild = this._getFirstTabbableElement(redirectToElement) as HTMLElement;
222+
focusableChild?.focus();
223+
return !!focusableChild;
224+
}
225+
220226
redirectToElement.focus();
221227
return true;
222228
}
@@ -226,7 +232,7 @@ export class FocusTrap {
226232

227233
/**
228234
* Focuses the first tabbable element within the focus trap region.
229-
* @returns Whether focus was moved successfuly.
235+
* @returns Whether focus was moved successfully.
230236
*/
231237
focusFirstTabbableElement(): boolean {
232238
const redirectToElement = this._getRegionBoundary('start');
@@ -240,7 +246,7 @@ export class FocusTrap {
240246

241247
/**
242248
* Focuses the last tabbable element within the focus trap region.
243-
* @returns Whether focus was moved successfuly.
249+
* @returns Whether focus was moved successfully.
244250
*/
245251
focusLastTabbableElement(): boolean {
246252
const redirectToElement = this._getRegionBoundary('end');
@@ -253,7 +259,7 @@ export class FocusTrap {
253259
}
254260

255261
/**
256-
* Checks whether the focus trap has successfuly been attached.
262+
* Checks whether the focus trap has successfully been attached.
257263
*/
258264
hasAttached(): boolean {
259265
return this._hasAttached;
@@ -396,7 +402,7 @@ export class CdkTrapFocus implements OnDestroy, AfterContentInit, OnChanges, DoC
396402
set enabled(value: boolean) { this.focusTrap.enabled = coerceBooleanProperty(value); }
397403

398404
/**
399-
* Whether the directive should automatially move focus into the trapped region upon
405+
* Whether the directive should automatically move focus into the trapped region upon
400406
* initialization and return focus to the previous activeElement upon destruction.
401407
*/
402408
@Input('cdkTrapFocusAutoCapture')

src/material/button-toggle/button-toggle.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -800,13 +800,13 @@ describe('MatButtonToggle without forms', () => {
800800
expect(button.getAttribute('tabindex')).toBe('3');
801801
});
802802

803-
it('should clear the tabindex from the host element', () => {
803+
it('should have role "presentation"', () => {
804804
const fixture = TestBed.createComponent(ButtonToggleWithTabindex);
805805
fixture.detectChanges();
806806

807807
const host = fixture.nativeElement.querySelector('.mat-button-toggle');
808808

809-
expect(host.getAttribute('tabindex')).toBe('-1');
809+
expect(host.getAttribute('role')).toBe('presentation');
810810
});
811811

812812
it('should forward focus to the underlying button when the host is focused', () => {

src/material/button-toggle/button-toggle.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -399,14 +399,12 @@ const _MatButtonToggleMixinBase: CanDisableRippleCtor & typeof MatButtonToggleBa
399399
'[class.mat-button-toggle-disabled]': 'disabled',
400400
'[class.mat-button-toggle-appearance-standard]': 'appearance === "standard"',
401401
'class': 'mat-button-toggle',
402-
// Always reset the tabindex to -1 so it doesn't conflict with the one on the `button`,
403-
// but can still receive focus from things like cdkFocusInitial.
404-
'[attr.tabindex]': '-1',
405402
'[attr.aria-label]': 'null',
406403
'[attr.aria-labelledby]': 'null',
407404
'[attr.id]': 'id',
408405
'[attr.name]': 'null',
409406
'(focus)': 'focus()',
407+
'role': 'presentation',
410408
}
411409
})
412410
export class MatButtonToggle extends _MatButtonToggleMixinBase implements OnInit, AfterViewInit,

0 commit comments

Comments
 (0)