Skip to content

Commit

Permalink
refactor(material/datepicker): focus active cell after change detection
Browse files Browse the repository at this point in the history
When receiving keyboard events on the month view, ensure that change
detection runs before focusing the active cell.
  • Loading branch information
zarend committed Jan 26, 2022
1 parent b8e4183 commit 25fb01d
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 4 deletions.
3 changes: 2 additions & 1 deletion src/material/datepicker/calendar-body.html
Original file line number Diff line number Diff line change
Expand Up @@ -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)="_cellFocused(item, $event)">
<div class="mat-calendar-body-cell-content mat-focus-indicator"
[class.mat-calendar-body-selected]="_isSelected(item.compareValue)"
[class.mat-calendar-body-comparison-identical]="_isComparisonIdentical(item.compareValue)"
Expand Down
34 changes: 32 additions & 2 deletions src/material/datepicker/calendar-body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
OnChanges,
SimpleChanges,
OnDestroy,
AfterViewChecked,
} from '@angular/core';
import {take} from 'rxjs/operators';

Expand Down Expand Up @@ -67,13 +68,18 @@ export interface MatCalendarUserEvent<D> {
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;

Expand All @@ -96,7 +102,20 @@ export class MatCalendarBody implements OnChanges, OnDestroy {
@Input() numCols: number = 7;

/** The cell number of the active cell in the table. */
@Input() activeCell: number = 0;
@Input() get activeCell(): number {
return this._activeCell;
}
set activeCell(activeCell: number) {
this._activeCell = activeCell;
}
private _activeCell: number = 0;

ngAfterViewChecked() {
if (this._focusActiveCellAfterViewChecked) {
this._focusActiveCell();
this._focusActiveCellAfterViewChecked = false;
}
}

/** Whether a range is being selected. */
@Input() isRange: boolean = false;
Expand Down Expand Up @@ -153,6 +172,12 @@ export class MatCalendarBody implements OnChanges, OnDestroy {
}
}

_cellFocused(cell: MatCalendarCell, event: FocusEvent): void {
if (cell.enabled) {
// TODO: make argument cell the active date
}
}

/** Returns whether a cell should be marked as selected. */
_isSelected(value: number) {
return this.startValue === value || this.endValue === value;
Expand Down Expand Up @@ -214,6 +239,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);
Expand Down
7 changes: 6 additions & 1 deletion src/material/datepicker/month-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ export class MatMonthView<D> implements AfterContentInit, OnChanges, OnDestroy {
this.activeDateChange.emit(this.activeDate);
}

this._focusActiveCell();
this._focusActiveCellAfterViewChecked();
// Prevent unexpected default actions such as form submission.
event.preventDefault();
}
Expand Down Expand Up @@ -376,6 +376,11 @@ export class MatMonthView<D> 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<MatCalendarCell<D> | null>) {
if (this._rangeStrategy) {
Expand Down

0 comments on commit 25fb01d

Please sign in to comment.