From ba9e136cb382d73e27cf618f29465037bf67dd2b Mon Sep 17 00:00:00 2001 From: Zach Arend Date: Mon, 13 Dec 2021 19:02:47 +0000 Subject: [PATCH] fix(material/datepicker): change calendar cell bodies to buttons Changes the structure of date cells to use buttons nested inside a gridcell. Previously the `td` tag would handle interaction, but this was problematic because Voiceover (and potentially other screenreaders) were not announcing that the gridcells were clickable bug #23476. Changes the DOM structure to nest a button role element inside the `td` and have the button handle interaction instead of the `td` Previous dom: ```
...
``` Updated dom: ```
...
``` Work in progress. fixes #23476 --- src/material/datepicker/calendar-body.html | 20 ++++++++------- src/material/datepicker/calendar-body.ts | 2 +- src/material/datepicker/calendar.spec.ts | 30 +++++++++++----------- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/material/datepicker/calendar-body.html b/src/material/datepicker/calendar-body.html index a59bd39774d8..31d6998bf6f3 100644 --- a/src/material/datepicker/calendar-body.html +++ b/src/material/datepicker/calendar-body.html @@ -30,7 +30,6 @@ role="gridcell" class="mat-calendar-body-cell" [ngClass]="item.cssClasses" - [tabindex]="_isActiveCell(rowIndex, colIndex) ? 0 : -1" [attr.data-mat-row]="rowIndex" [attr.data-mat-col]="colIndex" [class.mat-calendar-body-disabled]="!item.enabled" @@ -46,20 +45,23 @@ [class.mat-calendar-body-preview-start]="_isPreviewStart(item.compareValue)" [class.mat-calendar-body-preview-end]="_isPreviewEnd(item.compareValue)" [class.mat-calendar-body-in-preview]="_isInPreview(item.compareValue)" - [attr.aria-label]="item.ariaLabel" - [attr.aria-disabled]="!item.enabled || null" - [attr.aria-selected]="_isSelected(item.compareValue)" - [attr.aria-current]="todayValue === item.compareValue ? 'date' : null" - (click)="_cellClicked(item, $event)" [style.width]="_cellWidth" [style.paddingTop]="_cellPadding" [style.paddingBottom]="_cellPadding"> -
+ [class.mat-calendar-body-today]="todayValue === item.compareValue" + (click)="_cellClicked(item, $event)" + [tabindex]="_isActiveCell(rowIndex, colIndex) ? 0 : -1" + [attr.aria-label]="item.ariaLabel" + [attr.aria-current]="todayValue === item.compareValue ? 'date' : null" + [attr.aria-disabled]="!item.enabled || null" + [attr.aria-pressed]="_isSelected(item.compareValue)" + [attr.aria-selected]="_isSelected(item.compareValue)" + > {{item.displayValue}}
-
+ diff --git a/src/material/datepicker/calendar-body.ts b/src/material/datepicker/calendar-body.ts index 895db4b41472..6c39df23662c 100644 --- a/src/material/datepicker/calendar-body.ts +++ b/src/material/datepicker/calendar-body.ts @@ -200,7 +200,7 @@ export class MatCalendarBody implements OnChanges, OnDestroy { this._ngZone.runOutsideAngular(() => { this._ngZone.onStable.pipe(take(1)).subscribe(() => { const activeCell: HTMLElement | null = this._elementRef.nativeElement.querySelector( - '.mat-calendar-body-active', + '.mat-calendar-body-active button,[role="button"]', ); if (activeCell) { diff --git a/src/material/datepicker/calendar.spec.ts b/src/material/datepicker/calendar.spec.ts index 64c7cb18f836..fb181670ddee 100644 --- a/src/material/datepicker/calendar.spec.ts +++ b/src/material/datepicker/calendar.spec.ts @@ -15,7 +15,7 @@ import {MatCalendar} from './calendar'; import {MatDatepickerIntl} from './datepicker-intl'; import {MatDatepickerModule} from './datepicker-module'; -describe('MatCalendar', () => { +fdescribe('MatCalendar', () => { let zone: MockNgZone; beforeEach( @@ -86,8 +86,8 @@ describe('MatCalendar', () => { }); it('should select date in month view', () => { - let monthCells = calendarElement.querySelectorAll('.mat-calendar-body-cell'); - (monthCells[monthCells.length - 1] as HTMLElement).click(); + let monthCellButtons = calendarElement.querySelectorAll('.mat-calendar-body-cell button,[role="button"]'); + (monthCellButtons[monthCellButtons.length - 1] as HTMLElement).click(); fixture.detectChanges(); expect(calendarInstance.currentView).toBe('month'); @@ -101,13 +101,13 @@ describe('MatCalendar', () => { expect(calendarInstance.currentView).toBe('multi-year'); expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31)); - (calendarElement.querySelector('.mat-calendar-body-active') as HTMLElement).click(); + (calendarElement.querySelector('.mat-calendar-body-active button,[role="button"]') as HTMLElement).click(); fixture.detectChanges(); expect(calendarInstance.currentView).toBe('year'); - (calendarElement.querySelector('.mat-calendar-body-active') as HTMLElement).click(); + (calendarElement.querySelector('.mat-calendar-body-active button,[role="button"]') as HTMLElement).click(); const normalizedMonth: Date = fixture.componentInstance.selectedMonth; expect(normalizedMonth.getMonth()).toEqual(0); @@ -120,7 +120,7 @@ describe('MatCalendar', () => { expect(calendarInstance.currentView).toBe('multi-year'); expect(calendarInstance.activeDate).toEqual(new Date(2017, JAN, 31)); - (calendarElement.querySelector('.mat-calendar-body-active') as HTMLElement).click(); + (calendarElement.querySelector('.mat-calendar-body-active button,[role="button"]') as HTMLElement).click(); fixture.detectChanges(); @@ -195,7 +195,7 @@ describe('MatCalendar', () => { fixture.detectChanges(); const activeCell = calendarBodyEl.querySelector( - '.mat-calendar-body-active', + '.mat-calendar-body-active button,[role="button"]', )! as HTMLElement; spyOn(activeCell, 'focus').and.callThrough(); @@ -211,7 +211,7 @@ describe('MatCalendar', () => { expect(calendarInstance.currentView).toBe('multi-year'); - (calendarBodyEl.querySelector('.mat-calendar-body-active') as HTMLElement).click(); + (calendarBodyEl.querySelector('.mat-calendar-body-active button,[role="button"]') as HTMLElement).click(); fixture.detectChanges(); expect(calendarInstance.currentView).toBe('year'); @@ -312,7 +312,7 @@ describe('MatCalendar', () => { periodButton.click(); fixture.detectChanges(); - (calendarElement.querySelector('.mat-calendar-body-active') as HTMLElement).click(); + (calendarElement.querySelector('.mat-calendar-body-active button,[role="button"]') as HTMLElement).click(); fixture.detectChanges(); spyOn(calendarInstance.yearView, '_init').and.callThrough(); @@ -443,7 +443,7 @@ describe('MatCalendar', () => { periodButton.click(); fixture.detectChanges(); - (calendarElement.querySelector('.mat-calendar-body-active') as HTMLElement).click(); + (calendarElement.querySelector('.mat-calendar-body-active button,[role="button"]') as HTMLElement).click(); fixture.detectChanges(); spyOn(calendarInstance.yearView, '_init').and.callThrough(); @@ -462,7 +462,7 @@ describe('MatCalendar', () => { periodButton.click(); fixture.detectChanges(); - (calendarElement.querySelector('.mat-calendar-body-active') as HTMLElement).click(); + (calendarElement.querySelector('.mat-calendar-body-active button,[role="button"]') as HTMLElement).click(); fixture.detectChanges(); spyOn(calendarInstance.yearView, '_init').and.callThrough(); @@ -526,7 +526,7 @@ describe('MatCalendar', () => { .withContext('Expected dates after the 10th to be enabled.') .toBe(false); - (cells[14] as HTMLElement).click(); + (cells[14].querySelector('button, [role="button"]') as HTMLElement)?.click(); dynamicFixture.detectChanges(); cells = Array.from(calendarElement.querySelectorAll('.mat-calendar-body-cell')); @@ -558,12 +558,12 @@ describe('MatCalendar', () => { it('should disable and prevent selection of filtered dates', () => { let cells = calendarElement.querySelectorAll('.mat-calendar-body-cell'); - (cells[0] as HTMLElement).click(); + (cells[0]?.querySelector('button [role="button"]') as HTMLElement)?.click(); fixture.detectChanges(); expect(testComponent.selected).toBeFalsy(); - (cells[1] as HTMLElement).click(); + (cells[1]?.querySelector('button [role="button"]') as HTMLElement)?.click(); fixture.detectChanges(); expect(testComponent.selected).toEqual(new Date(2017, JAN, 2)); @@ -597,7 +597,7 @@ describe('MatCalendar', () => { dispatchMouseEvent(periodButton, 'click'); fixture.detectChanges(); - (calendarElement.querySelector('.mat-calendar-body-active') as HTMLElement).click(); + (calendarElement.querySelector('.mat-calendar-body-active')?.querySelector('button, [role="button"]') as HTMLElement)?.click(); fixture.detectChanges(); calendarInstance.activeDate = new Date(2017, NOV, 1);