From e33d614ec85d97cb2db62ddc0eed3bcd4153f826 Mon Sep 17 00:00:00 2001 From: Zach Arend Date: Mon, 7 Feb 2022 14:17:12 -0800 Subject: [PATCH] fix(material/datepicker): fix improper focus trapping with VoiceOver and ChromeVox (#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 #2345 --- src/material/datepicker/date-range-input.spec.ts | 2 +- src/material/datepicker/datepicker-base.ts | 16 ++++------------ src/material/datepicker/datepicker-content.html | 3 +++ src/material/datepicker/datepicker.spec.ts | 10 +++++++--- tools/public_api_guard/material/datepicker.md | 1 + 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/material/datepicker/date-range-input.spec.ts b/src/material/datepicker/date-range-input.spec.ts index 26c5680670b7..a9ba94dd7d16 100644 --- a/src/material/datepicker/date-range-input.spec.ts +++ b/src/material/datepicker/date-range-input.spec.ts @@ -208,7 +208,7 @@ describe('MatDateRangeInput', () => { fixture.detectChanges(); tick(); - const popup = document.querySelector('.cdk-overlay-pane')!; + const popup = document.querySelector('.cdk-overlay-pane .mat-datepicker-content-container')!; expect(popup).toBeTruthy(); expect(popup.getAttribute('aria-labelledby')).toBe(label.getAttribute('id')); })); diff --git a/src/material/datepicker/datepicker-base.ts b/src/material/datepicker/datepicker-base.ts index 2982e852c15e..d09208929e90 100644 --- a/src/material/datepicker/datepicker-base.ts +++ b/src/material/datepicker/datepicker-base.ts @@ -123,7 +123,6 @@ export class MatDatepickerContent> { private _subscriptions = new Subscription(); private _model: MatDateSelectionModel; - /** Reference to the internal calendar component. */ @ViewChild(MatCalendar) _calendar: MatCalendar; @@ -154,6 +153,9 @@ export class MatDatepickerContent> /** Portal with projected action buttons. */ _actionsPortal: TemplatePortal | null = null; + /** Id of the label for the `role="dialog"` element. */ + _dialogLabelId: string | null; + constructor( elementRef: ElementRef, private _changeDetectorRef: ChangeDetectorRef, @@ -622,6 +624,7 @@ export abstract class MatDatepickerBase< instance.datepicker = this; instance.color = this.color; instance._actionsPortal = this._actionsPortal; + instance._dialogLabelId = this.datepickerInput.getOverlayLabelId(); } /** Opens the overlay with the calendar. */ @@ -629,7 +632,6 @@ export abstract class MatDatepickerBase< this._destroyOverlay(); const isDialog = this.touchUi; - const labelId = this.datepickerInput.getOverlayLabelId(); const portal = new ComponentPortal>( MatDatepickerContent, this._viewContainerRef, @@ -647,16 +649,6 @@ export abstract class MatDatepickerBase< panelClass: `mat-datepicker-${isDialog ? 'dialog' : 'popup'}`, }), )); - const overlayElement = overlayRef.overlayElement; - overlayElement.setAttribute('role', 'dialog'); - - if (labelId) { - overlayElement.setAttribute('aria-labelledby', labelId); - } - - if (isDialog) { - overlayElement.setAttribute('aria-modal', 'true'); - } this._getCloseStream(overlayRef).subscribe(event => { if (event) { diff --git a/src/material/datepicker/datepicker-content.html b/src/material/datepicker/datepicker-content.html index 182377885cf7..2c4b931305fd 100644 --- a/src/material/datepicker/datepicker-content.html +++ b/src/material/datepicker/datepicker-content.html @@ -1,5 +1,8 @@