Skip to content

Commit e33d614

Browse files
zarendamysorto
authored andcommitted
fix(material/datepicker): fix improper focus trapping with VoiceOver and ChromeVox (angular#24300)
Fixes focus trapping on the datepicker dialog by putting `role="dialog"` `aria-modal="true"` and `cdkTrapFocus` all on the same element. This aligns the datepicker with how MatDialog does focus trapping. Without having them all on the same element, users could exit out of the focus trapping using screenreader specific navigation with VoiceOver and ChromeVox. Fixes angular#2345
1 parent 33d0373 commit e33d614

File tree

5 files changed

+16
-16
lines changed

5 files changed

+16
-16
lines changed

src/material/datepicker/date-range-input.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ describe('MatDateRangeInput', () => {
208208
fixture.detectChanges();
209209
tick();
210210

211-
const popup = document.querySelector('.cdk-overlay-pane')!;
211+
const popup = document.querySelector('.cdk-overlay-pane .mat-datepicker-content-container')!;
212212
expect(popup).toBeTruthy();
213213
expect(popup.getAttribute('aria-labelledby')).toBe(label.getAttribute('id'));
214214
}));

src/material/datepicker/datepicker-base.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@ export class MatDatepickerContent<S, D = ExtractDateTypeFromSelection<S>>
123123
{
124124
private _subscriptions = new Subscription();
125125
private _model: MatDateSelectionModel<S, D>;
126-
127126
/** Reference to the internal calendar component. */
128127
@ViewChild(MatCalendar) _calendar: MatCalendar<D>;
129128

@@ -154,6 +153,9 @@ export class MatDatepickerContent<S, D = ExtractDateTypeFromSelection<S>>
154153
/** Portal with projected action buttons. */
155154
_actionsPortal: TemplatePortal | null = null;
156155

156+
/** Id of the label for the `role="dialog"` element. */
157+
_dialogLabelId: string | null;
158+
157159
constructor(
158160
elementRef: ElementRef,
159161
private _changeDetectorRef: ChangeDetectorRef,
@@ -622,14 +624,14 @@ export abstract class MatDatepickerBase<
622624
instance.datepicker = this;
623625
instance.color = this.color;
624626
instance._actionsPortal = this._actionsPortal;
627+
instance._dialogLabelId = this.datepickerInput.getOverlayLabelId();
625628
}
626629

627630
/** Opens the overlay with the calendar. */
628631
private _openOverlay(): void {
629632
this._destroyOverlay();
630633

631634
const isDialog = this.touchUi;
632-
const labelId = this.datepickerInput.getOverlayLabelId();
633635
const portal = new ComponentPortal<MatDatepickerContent<S, D>>(
634636
MatDatepickerContent,
635637
this._viewContainerRef,
@@ -647,16 +649,6 @@ export abstract class MatDatepickerBase<
647649
panelClass: `mat-datepicker-${isDialog ? 'dialog' : 'popup'}`,
648650
}),
649651
));
650-
const overlayElement = overlayRef.overlayElement;
651-
overlayElement.setAttribute('role', 'dialog');
652-
653-
if (labelId) {
654-
overlayElement.setAttribute('aria-labelledby', labelId);
655-
}
656-
657-
if (isDialog) {
658-
overlayElement.setAttribute('aria-modal', 'true');
659-
}
660652

661653
this._getCloseStream(overlayRef).subscribe(event => {
662654
if (event) {

src/material/datepicker/datepicker-content.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
<div
22
cdkTrapFocus
3+
role="dialog"
4+
[attr.aria-modal]="true"
5+
[attr.aria-labelledby]="_dialogLabelId ?? undefined"
36
class="mat-datepicker-content-container"
47
[class.mat-datepicker-content-container-with-custom-header]="datepicker.calendarHeaderComponent"
58
[class.mat-datepicker-content-container-with-actions]="_actionsPortal">

src/material/datepicker/datepicker.spec.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ describe('MatDatepicker', () => {
236236
fixture.detectChanges();
237237
flush();
238238

239-
const popup = document.querySelector('.cdk-overlay-pane')!;
239+
const popup = document.querySelector('.mat-datepicker-content-container')!;
240240
expect(popup).toBeTruthy();
241241
expect(popup.getAttribute('role')).toBe('dialog');
242242
}));
@@ -254,7 +254,9 @@ describe('MatDatepicker', () => {
254254
fixture.detectChanges();
255255
flush();
256256

257-
const popup = document.querySelector('.cdk-overlay-pane')!;
257+
const popup = document.querySelector(
258+
'.cdk-overlay-pane .mat-datepicker-content-container',
259+
)!;
258260
expect(popup).toBeTruthy();
259261
expect(popup.getAttribute('aria-labelledby')).toBe('test-label');
260262
}),
@@ -1405,7 +1407,9 @@ describe('MatDatepicker', () => {
14051407
fixture.detectChanges();
14061408
flush();
14071409

1408-
const popup = document.querySelector('.cdk-overlay-pane')!;
1410+
const popup = document.querySelector(
1411+
'.cdk-overlay-pane .mat-datepicker-content-container',
1412+
)!;
14091413
expect(popup).toBeTruthy();
14101414
expect(popup.getAttribute('aria-labelledby')).toBe(label.getAttribute('id'));
14111415
}));

tools/public_api_guard/material/datepicker.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,7 @@ export class MatDatepickerContent<S, D = ExtractDateTypeFromSelection<S>> extend
433433
comparisonEnd: D | null;
434434
comparisonStart: D | null;
435435
datepicker: MatDatepickerBase<any, S, D>;
436+
_dialogLabelId: string | null;
436437
// (undocumented)
437438
_getSelected(): D | DateRange<D> | null;
438439
// (undocumented)

0 commit comments

Comments
 (0)