From e82f7a6fd3a07a5758bdb7ac42a71df0f98f9d1a Mon Sep 17 00:00:00 2001 From: Zach Arend Date: Tue, 25 Jan 2022 22:03:38 +0000 Subject: [PATCH] fix(material/datepicker): update active date on focusing a calendar cell When a a date cell on the calendar recieves focus, set the active date to that cell. This ensures that the active date matches the date with browser focus. Previously, we set the active date on keydown and click, but that was problematic for screenreaders. That's because many screenreaders trigger a focus event instead of a keydown event when using screenreader specific navigation (VoiceOver, Chromevox, NVDA). Fixes #23483 --- src/material/datepicker/calendar-body.html | 3 +- src/material/datepicker/calendar-body.ts | 28 ++++++++- src/material/datepicker/month-view.html | 1 + src/material/datepicker/month-view.spec.ts | 24 ++++++++ src/material/datepicker/month-view.ts | 44 +++++++++++-- src/material/datepicker/multi-year-view.html | 1 + .../datepicker/multi-year-view.spec.ts | 30 ++++++++- src/material/datepicker/multi-year-view.ts | 61 +++++++++++++++---- src/material/datepicker/year-view.html | 1 + src/material/datepicker/year-view.spec.ts | 24 ++++++++ src/material/datepicker/year-view.ts | 61 +++++++++++++++---- tools/public_api_guard/material/datepicker.md | 17 +++++- 12 files changed, 262 insertions(+), 33 deletions(-) diff --git a/src/material/datepicker/calendar-body.html b/src/material/datepicker/calendar-body.html index 1b0f5f0b1a4a..25db33ecbee5 100644 --- a/src/material/datepicker/calendar-body.html +++ b/src/material/datepicker/calendar-body.html @@ -63,7 +63,8 @@ [attr.aria-disabled]="!item.enabled || null" [attr.aria-pressed]="_isSelected(item.compareValue)" [attr.aria-current]="todayValue === item.compareValue ? 'date' : null" - (click)="_cellClicked(item, $event)"> + (click)="_cellClicked(item, $event)" + (focus)="_emitActiveDateChange(item, $event)">
{ encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class MatCalendarBody implements OnChanges, OnDestroy { +export class MatCalendarBody implements OnChanges, OnDestroy, AfterViewChecked { /** * Used to skip the next focus event when rendering the preview range. * We need a flag like this, because some browsers fire focus events asynchronously. */ private _skipNextFocus: boolean; + /** + * Used to focus the active cell after change detection has run. + */ + private _focusActiveCellAfterViewChecked = false; + /** The label for the table. (e.g. "Jan 2017"). */ @Input() label: string; @@ -98,6 +104,13 @@ export class MatCalendarBody implements OnChanges, OnDestroy { /** The cell number of the active cell in the table. */ @Input() activeCell: number = 0; + ngAfterViewChecked() { + if (this._focusActiveCellAfterViewChecked) { + this._focusActiveCell(); + this._focusActiveCellAfterViewChecked = false; + } + } + /** Whether a range is being selected. */ @Input() isRange: boolean = false; @@ -127,6 +140,8 @@ export class MatCalendarBody implements OnChanges, OnDestroy { MatCalendarUserEvent >(); + @Output() readonly activeDateChange = new EventEmitter>(); + /** The number of blank cells to put at the beginning for the first row. */ _firstRowOffset: number; @@ -153,6 +168,12 @@ export class MatCalendarBody implements OnChanges, OnDestroy { } } + _emitActiveDateChange(cell: MatCalendarCell, event: FocusEvent): void { + if (cell.enabled) { + this.activeDateChange.emit({value: cell.value, event}); + } + } + /** Returns whether a cell should be marked as selected. */ _isSelected(value: number) { return this.startValue === value || this.endValue === value; @@ -214,6 +235,11 @@ export class MatCalendarBody implements OnChanges, OnDestroy { }); } + /** Focuses the active cell after change detection has run and the microtask queue is empty. */ + _scheduleFocusActiveCellAfterViewChecked() { + this._focusActiveCellAfterViewChecked = true; + } + /** Gets whether a value is the start of the main range. */ _isRangeStart(value: number) { return isStart(value, this.startValue, this.endValue); diff --git a/src/material/datepicker/month-view.html b/src/material/datepicker/month-view.html index 736d7eff7cd1..3ccafd6d2e94 100644 --- a/src/material/datepicker/month-view.html +++ b/src/material/datepicker/month-view.html @@ -21,6 +21,7 @@ [labelMinRequiredCells]="3" [activeCell]="_dateAdapter.getDate(activeDate) - 1" (selectedValueChange)="_dateSelected($event)" + (activeDateChange)="_updateActiveDate($event)" (previewChange)="_previewChanged($event)" (keyup)="_handleCalendarBodyKeyup($event)" (keydown)="_handleCalendarBodyKeydown($event)"> diff --git a/src/material/datepicker/month-view.spec.ts b/src/material/datepicker/month-view.spec.ts index 7a4909f8efbb..27bada546455 100644 --- a/src/material/datepicker/month-view.spec.ts +++ b/src/material/datepicker/month-view.spec.ts @@ -520,6 +520,30 @@ describe('MatMonthView', () => { ); }, ); + + it('should go to month that is focused', () => { + const jan11Cell = fixture.debugElement.nativeElement.querySelector( + '[data-mat-row="1"][data-mat-col="3"] button', + ) as HTMLElement; + + dispatchFakeEvent(jan11Cell, 'focus'); + fixture.detectChanges(); + + expect(calendarInstance.date).toEqual(new Date(2017, JAN, 11)); + }); + + it('should not call `.focus()` when the active date is focused', () => { + const jan5Cell = fixture.debugElement.nativeElement.querySelector( + '[data-mat-row="0"][data-mat-col="4"] button', + ) as HTMLElement; + const focusSpy = (jan5Cell.focus = jasmine.createSpy('cellFocused')); + + dispatchFakeEvent(jan5Cell, 'focus'); + fixture.detectChanges(); + + expect(calendarInstance.date).toEqual(new Date(2017, JAN, 5)); + expect(focusSpy).not.toHaveBeenCalled(); + }); }); }); }); diff --git a/src/material/datepicker/month-view.ts b/src/material/datepicker/month-view.ts index a46abfb4dfbd..03fdfa9dc0eb 100644 --- a/src/material/datepicker/month-view.ts +++ b/src/material/datepicker/month-view.ts @@ -230,9 +230,7 @@ export class MatMonthView implements AfterContentInit, OnChanges, OnDestroy { /** Handles when a new date is selected. */ _dateSelected(event: MatCalendarUserEvent) { const date = event.value; - const selectedYear = this._dateAdapter.getYear(this.activeDate); - const selectedMonth = this._dateAdapter.getMonth(this.activeDate); - const selectedDate = this._dateAdapter.createDate(selectedYear, selectedMonth, date); + const selectedDate = this._getDateFromDayOfMonth(date); let rangeStartDate: number | null; let rangeEndDate: number | null; @@ -252,6 +250,26 @@ export class MatMonthView implements AfterContentInit, OnChanges, OnDestroy { this._changeDetectorRef.markForCheck(); } + /** + * Takes the index of a calendar body cell wrapped in in an event as argument. For the date that + * corresponds to the given cell, set `activeDate` to that date and fire `activeDateChange` with + * that date. + * + * This fucntion is used to match each component's model of the active date with the calendar + * body cell that was focused. It updates its value of `activeDate` synchronously and updates the + * parent's value asynchonously via the `activeDateChange` event. The child component receives an + * updated value asynchronously via the `activeCell` Input. + */ + _updateActiveDate(event: MatCalendarUserEvent) { + const month = event.value; + const oldActiveDate = this._activeDate; + this.activeDate = this._getDateFromDayOfMonth(month); + + if (this._dateAdapter.compareDate(oldActiveDate, this.activeDate)) { + this.activeDateChange.emit(this._activeDate); + } + } + /** Handles keydown events on the calendar body when calendar is in month view. */ _handleCalendarBodyKeydown(event: KeyboardEvent): void { // TODO(mmalerba): We currently allow keyboard navigation to disabled dates, but just prevent @@ -327,9 +345,10 @@ export class MatMonthView implements AfterContentInit, OnChanges, OnDestroy { if (this._dateAdapter.compareDate(oldActiveDate, this.activeDate)) { this.activeDateChange.emit(this.activeDate); + + this._focusActiveCellAfterViewChecked(); } - this._focusActiveCell(); // Prevent unexpected default actions such as form submission. event.preventDefault(); } @@ -376,6 +395,11 @@ export class MatMonthView implements AfterContentInit, OnChanges, OnDestroy { this._matCalendarBody._focusActiveCell(movePreview); } + /** Focuses the active cell after change detection has run and the microtask queue is empty. */ + _focusActiveCellAfterViewChecked() { + this._matCalendarBody._scheduleFocusActiveCellAfterViewChecked(); + } + /** Called when the user has activated a new cell and the preview needs to be updated. */ _previewChanged({event, value: cell}: MatCalendarUserEvent | null>) { if (this._rangeStrategy) { @@ -398,6 +422,18 @@ export class MatMonthView implements AfterContentInit, OnChanges, OnDestroy { } } + /** + * Takes a day of the month and returns a new date in the same month and year as the currently + * active date. The returned date will have the same day of the month as the argument date. + */ + private _getDateFromDayOfMonth(dayOfMonth: number): D { + return this._dateAdapter.createDate( + this._dateAdapter.getYear(this.activeDate), + this._dateAdapter.getMonth(this.activeDate), + dayOfMonth, + ); + } + /** Initializes the weekdays. */ private _initWeekdays() { const firstDayOfWeek = this._dateAdapter.getFirstDayOfWeek(); diff --git a/src/material/datepicker/multi-year-view.html b/src/material/datepicker/multi-year-view.html index ee12a9e67d29..84220c8fa95f 100644 --- a/src/material/datepicker/multi-year-view.html +++ b/src/material/datepicker/multi-year-view.html @@ -11,6 +11,7 @@ [cellAspectRatio]="4 / 7" [activeCell]="_getActiveCell()" (selectedValueChange)="_yearSelected($event)" + (activeDateChange)="_updateActiveDate($event)" (keyup)="_handleCalendarBodyKeyup($event)" (keydown)="_handleCalendarBodyKeydown($event)"> diff --git a/src/material/datepicker/multi-year-view.spec.ts b/src/material/datepicker/multi-year-view.spec.ts index 61c14607eb26..47e56f395d1a 100644 --- a/src/material/datepicker/multi-year-view.spec.ts +++ b/src/material/datepicker/multi-year-view.spec.ts @@ -13,7 +13,7 @@ import {dispatchFakeEvent, dispatchKeyboardEvent} from '../../cdk/testing/privat import {Component, ViewChild} from '@angular/core'; import {waitForAsync, ComponentFixture, TestBed} from '@angular/core/testing'; import {MatNativeDateModule} from '@angular/material/core'; -import {JAN} from '../testing'; +import {JAN, MAR} from '../testing'; import {By} from '@angular/platform-browser'; import {MatCalendarBody} from './calendar-body'; import {MatMultiYearView, yearsPerPage, yearsPerRow} from './multi-year-view'; @@ -216,6 +216,34 @@ describe('MatMultiYearView', () => { expect(calendarInstance.date).toEqual(new Date(2017 + yearsPerPage * 2, JAN, 1)); }); + + it('should go to the year that is focused', () => { + fixture.componentInstance.date = new Date(2017, MAR, 5); + fixture.detectChanges(); + expect(calendarInstance.date).toEqual(new Date(2017, MAR, 5)); + + const year2022Cell = fixture.debugElement.nativeElement.querySelector( + '[data-mat-row="1"][data-mat-col="2"] button', + ) as HTMLElement; + + dispatchFakeEvent(year2022Cell, 'focus'); + fixture.detectChanges(); + + expect(calendarInstance.date).toEqual(new Date(2022, MAR, 5)); + }); + + it('should not call `.focus()` when the active date is focused', () => { + const year2017Cell = fixture.debugElement.nativeElement.querySelector( + '[data-mat-row="0"][data-mat-col="1"] button', + ) as HTMLElement; + const focusSpy = (year2017Cell.focus = jasmine.createSpy('cellFocused')); + + dispatchFakeEvent(year2017Cell, 'focus'); + fixture.detectChanges(); + + expect(calendarInstance.date).toEqual(new Date(2017, JAN, 1)); + expect(focusSpy).not.toHaveBeenCalled(); + }); }); }); }); diff --git a/src/material/datepicker/multi-year-view.ts b/src/material/datepicker/multi-year-view.ts index da7206057bdc..4d1d6b133255 100644 --- a/src/material/datepicker/multi-year-view.ts +++ b/src/material/datepicker/multi-year-view.ts @@ -204,18 +204,31 @@ export class MatMultiYearView implements AfterContentInit, OnDestroy { /** Handles when a new year is selected. */ _yearSelected(event: MatCalendarUserEvent) { const year = event.value; - this.yearSelected.emit(this._dateAdapter.createDate(year, 0, 1)); - let month = this._dateAdapter.getMonth(this.activeDate); - let daysInMonth = this._dateAdapter.getNumDaysInMonth( - this._dateAdapter.createDate(year, month, 1), - ); - this.selectedChange.emit( - this._dateAdapter.createDate( - year, - month, - Math.min(this._dateAdapter.getDate(this.activeDate), daysInMonth), - ), - ); + const selectedYear = this._dateAdapter.createDate(year, 0, 1); + const selectedDate = this._getDateFromYear(year); + + this.yearSelected.emit(selectedYear); + this.selectedChange.emit(selectedDate); + } + + /** + * Takes the index of a calendar body cell wrapped in in an event as argument. For the date that + * corresponds to the given cell, set `activeDate` to that date and fire `activeDateChange` with + * that date. + * + * This fucntion is used to match each component's model of the active date with the calendar + * body cell that was focused. It updates its value of `activeDate` synchronously and updates the + * parent's value asynchonously via the `activeDateChange` event. The child component receives an + * updated value asynchronously via the `activeCell` Input. + */ + _updateActiveDate(event: MatCalendarUserEvent) { + const year = event.value; + const oldActiveDate = this._activeDate; + + this.activeDate = this._getDateFromYear(year); + if (this._dateAdapter.compareDate(oldActiveDate, this.activeDate)) { + this.activeDateChange.emit(this.activeDate); + } } /** Handles keydown events on the calendar body when calendar is in multi-year view. */ @@ -278,7 +291,7 @@ export class MatMultiYearView implements AfterContentInit, OnDestroy { this.activeDateChange.emit(this.activeDate); } - this._focusActiveCell(); + this._focusActiveCellAfterViewChecked(); // Prevent unexpected default actions such as form submission. event.preventDefault(); } @@ -303,6 +316,28 @@ export class MatMultiYearView implements AfterContentInit, OnDestroy { this._matCalendarBody._focusActiveCell(); } + /** Focuses the active cell after change detection has run and the microtask queue is empty. */ + _focusActiveCellAfterViewChecked() { + this._matCalendarBody._scheduleFocusActiveCellAfterViewChecked(); + } + + /** + * Takes a year and returns a new date on the same day and month as the currently active date + * The returned date will have the same year as the argument date. + */ + private _getDateFromYear(year: number) { + const activeMonth = this._dateAdapter.getMonth(this.activeDate); + const daysInMonth = this._dateAdapter.getNumDaysInMonth( + this._dateAdapter.createDate(year, activeMonth, 1), + ); + const normalizedDate = this._dateAdapter.createDate( + year, + activeMonth, + Math.min(this._dateAdapter.getDate(this.activeDate), daysInMonth), + ); + return normalizedDate; + } + /** Creates an MatCalendarCell for the given year. */ private _createCellForYear(year: number) { const date = this._dateAdapter.createDate(year, 0, 1); diff --git a/src/material/datepicker/year-view.html b/src/material/datepicker/year-view.html index dae81c5e2a27..afb9821dd416 100644 --- a/src/material/datepicker/year-view.html +++ b/src/material/datepicker/year-view.html @@ -13,6 +13,7 @@ [cellAspectRatio]="4 / 7" [activeCell]="_dateAdapter.getMonth(activeDate)" (selectedValueChange)="_monthSelected($event)" + (activeDateChange)="_updateActiveDate($event)" (keyup)="_handleCalendarBodyKeyup($event)" (keydown)="_handleCalendarBodyKeydown($event)"> diff --git a/src/material/datepicker/year-view.spec.ts b/src/material/datepicker/year-view.spec.ts index 99e6ca6783f9..2fb07ef50423 100644 --- a/src/material/datepicker/year-view.spec.ts +++ b/src/material/datepicker/year-view.spec.ts @@ -292,6 +292,30 @@ describe('MatYearView', () => { expect(calendarInstance.date).toEqual(new Date(2018, FEB, 28)); }); + + it('should go to date that is focused', () => { + const juneCell = fixture.debugElement.nativeElement.querySelector( + '[data-mat-row="1"][data-mat-col="1"] button', + ) as HTMLElement; + + dispatchFakeEvent(juneCell, 'focus'); + fixture.detectChanges(); + + expect(calendarInstance.date).toEqual(new Date(2017, JUN, 5)); + }); + + it('should not call `.focus()` when the active date is focused', () => { + const janCell = fixture.debugElement.nativeElement.querySelector( + '[data-mat-row="0"][data-mat-col="0"] button', + ) as HTMLElement; + const focusSpy = (janCell.focus = jasmine.createSpy('cellFocused')); + + dispatchFakeEvent(janCell, 'focus'); + fixture.detectChanges(); + + expect(calendarInstance.date).toEqual(new Date(2017, JAN, 5)); + expect(focusSpy).not.toHaveBeenCalled(); + }); }); }); }); diff --git a/src/material/datepicker/year-view.ts b/src/material/datepicker/year-view.ts index 2e121f75cc15..b69c98bba7f9 100644 --- a/src/material/datepicker/year-view.ts +++ b/src/material/datepicker/year-view.ts @@ -179,23 +179,37 @@ export class MatYearView implements AfterContentInit, OnDestroy { /** Handles when a new month is selected. */ _monthSelected(event: MatCalendarUserEvent) { const month = event.value; - const normalizedDate = this._dateAdapter.createDate( + + const selectedMonth = this._dateAdapter.createDate( this._dateAdapter.getYear(this.activeDate), month, 1, ); + this.monthSelected.emit(selectedMonth); - this.monthSelected.emit(normalizedDate); + const selectedDate = this._getDateFromMonth(month); + this.selectedChange.emit(selectedDate); + } - const daysInMonth = this._dateAdapter.getNumDaysInMonth(normalizedDate); + /** + * Takes the index of a calendar body cell wrapped in in an event as argument. For the date that + * corresponds to the given cell, set `activeDate` to that date and fire `activeDateChange` with + * that date. + * + * This fucntion is used to match each component's model of the active date with the calendar + * body cell that was focused. It updates its value of `activeDate` synchronously and updates the + * parent's value asynchonously via the `activeDateChange` event. The child component receives an + * updated value asynchronously via the `activeCell` Input. + */ + _updateActiveDate(event: MatCalendarUserEvent) { + const month = event.value; + const oldActiveDate = this._activeDate; - this.selectedChange.emit( - this._dateAdapter.createDate( - this._dateAdapter.getYear(this.activeDate), - month, - Math.min(this._dateAdapter.getDate(this.activeDate), daysInMonth), - ), - ); + this.activeDate = this._getDateFromMonth(month); + + if (this._dateAdapter.compareDate(oldActiveDate, this.activeDate)) { + this.activeDateChange.emit(this.activeDate); + } } /** Handles keydown events on the calendar body when calendar is in year view. */ @@ -259,9 +273,9 @@ export class MatYearView implements AfterContentInit, OnDestroy { if (this._dateAdapter.compareDate(oldActiveDate, this.activeDate)) { this.activeDateChange.emit(this.activeDate); + this._focusActiveCellAfterViewChecked(); } - this._focusActiveCell(); // Prevent unexpected default actions such as form submission. event.preventDefault(); } @@ -298,6 +312,11 @@ export class MatYearView implements AfterContentInit, OnDestroy { this._matCalendarBody._focusActiveCell(); } + /** Schedules the matCalendarBody to focus the active cell after change detection has run */ + _focusActiveCellAfterViewChecked() { + this._matCalendarBody._scheduleFocusActiveCellAfterViewChecked(); + } + /** * Gets the month in this year that the given Date falls on. * Returns null if the given Date is in another year. @@ -308,6 +327,26 @@ export class MatYearView implements AfterContentInit, OnDestroy { : null; } + /** + * Takes a month and returns a new date in the same day and year as the currently active date. + * The returned date will have the same month as the argument date. + */ + private _getDateFromMonth(month: number) { + const normalizedDate = this._dateAdapter.createDate( + this._dateAdapter.getYear(this.activeDate), + month, + 1, + ); + + const daysInMonth = this._dateAdapter.getNumDaysInMonth(normalizedDate); + + return this._dateAdapter.createDate( + this._dateAdapter.getYear(this.activeDate), + month, + Math.min(this._dateAdapter.getDate(this.activeDate), daysInMonth), + ); + } + /** Creates an MatCalendarCell for the given month. */ private _createCellForMonth(month: number, monthName: string) { const date = this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate), month, 1); diff --git a/tools/public_api_guard/material/datepicker.md b/tools/public_api_guard/material/datepicker.md index c8fb85e4fca7..2ac076f96518 100644 --- a/tools/public_api_guard/material/datepicker.md +++ b/tools/public_api_guard/material/datepicker.md @@ -197,15 +197,19 @@ export class MatCalendar implements AfterContentInit, AfterViewChecked, OnDes } // @public -export class MatCalendarBody implements OnChanges, OnDestroy { +export class MatCalendarBody implements OnChanges, OnDestroy, AfterViewChecked { constructor(_elementRef: ElementRef, _ngZone: NgZone); activeCell: number; + // (undocumented) + readonly activeDateChange: EventEmitter>; cellAspectRatio: number; _cellClicked(cell: MatCalendarCell, event: MouseEvent): void; _cellPadding: string; _cellWidth: string; comparisonEnd: number | null; comparisonStart: number | null; + // (undocumented) + _emitActiveDateChange(cell: MatCalendarCell, event: FocusEvent): void; endValue: number; _firstRowOffset: number; _focusActiveCell(movePreview?: boolean): void; @@ -227,6 +231,8 @@ export class MatCalendarBody implements OnChanges, OnDestroy { label: string; labelMinRequiredCells: number; // (undocumented) + ngAfterViewChecked(): void; + // (undocumented) ngOnChanges(changes: SimpleChanges): void; // (undocumented) ngOnDestroy(): void; @@ -235,11 +241,12 @@ export class MatCalendarBody implements OnChanges, OnDestroy { previewEnd: number | null; previewStart: number | null; rows: MatCalendarCell[][]; + _scheduleFocusActiveCellAfterViewChecked(): void; readonly selectedValueChange: EventEmitter>; startValue: number; todayValue: number; // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } @@ -764,6 +771,7 @@ export class MatMonthView implements AfterContentInit, OnChanges, OnDestroy { _dateSelected(event: MatCalendarUserEvent): void; _firstWeekOffset: number; _focusActiveCell(movePreview?: boolean): void; + _focusActiveCellAfterViewChecked(): void; _handleCalendarBodyKeydown(event: KeyboardEvent): void; _handleCalendarBodyKeyup(event: KeyboardEvent): void; _init(): void; @@ -789,6 +797,7 @@ export class MatMonthView implements AfterContentInit, OnChanges, OnDestroy { set selected(value: DateRange | D | null); readonly selectedChange: EventEmitter; _todayDate: number | null; + _updateActiveDate(event: MatCalendarUserEvent): void; readonly _userSelection: EventEmitter>; _weekdays: { long: string; @@ -812,6 +821,7 @@ export class MatMultiYearView implements AfterContentInit, OnDestroy { dateClass: MatCalendarCellClassFunction; dateFilter: (date: D) => boolean; _focusActiveCell(): void; + _focusActiveCellAfterViewChecked(): void; // (undocumented) _getActiveCell(): number; _handleCalendarBodyKeydown(event: KeyboardEvent): void; @@ -831,6 +841,7 @@ export class MatMultiYearView implements AfterContentInit, OnDestroy { readonly selectedChange: EventEmitter; _selectedYear: number | null; _todayYear: number; + _updateActiveDate(event: MatCalendarUserEvent): void; _years: MatCalendarCell[][]; readonly yearSelected: EventEmitter; _yearSelected(event: MatCalendarUserEvent): void; @@ -899,6 +910,7 @@ export class MatYearView implements AfterContentInit, OnDestroy { dateClass: MatCalendarCellClassFunction; dateFilter: (date: D) => boolean; _focusActiveCell(): void; + _focusActiveCellAfterViewChecked(): void; _handleCalendarBodyKeydown(event: KeyboardEvent): void; _handleCalendarBodyKeyup(event: KeyboardEvent): void; _init(): void; @@ -919,6 +931,7 @@ export class MatYearView implements AfterContentInit, OnDestroy { readonly selectedChange: EventEmitter; _selectedMonth: number | null; _todayMonth: number | null; + _updateActiveDate(event: MatCalendarUserEvent): void; _yearLabel: string; // (undocumented) static ɵcmp: i0.ɵɵComponentDeclaration, "mat-year-view", ["matYearView"], { "activeDate": "activeDate"; "selected": "selected"; "minDate": "minDate"; "maxDate": "maxDate"; "dateFilter": "dateFilter"; "dateClass": "dateClass"; }, { "selectedChange": "selectedChange"; "monthSelected": "monthSelected"; "activeDateChange": "activeDateChange"; }, never, never>;