mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
refactor(datepicker): move keyboard events handlers into the views (#9775)
This commit is contained in:
@@ -9,12 +9,14 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
ViewEncapsulation
|
||||
ViewEncapsulation,
|
||||
NgZone,
|
||||
} from '@angular/core';
|
||||
|
||||
import {take} from 'rxjs/operators/take';
|
||||
|
||||
/**
|
||||
* An internal class that represents the data corresponding to a single calendar cell.
|
||||
@@ -81,6 +83,8 @@ export class MatCalendarBody {
|
||||
/** Emits when a new value is selected. */
|
||||
@Output() readonly selectedValueChange: EventEmitter<number> = new EventEmitter<number>();
|
||||
|
||||
constructor(private _elementRef: ElementRef, private _ngZone: NgZone) { }
|
||||
|
||||
_cellClicked(cell: MatCalendarCell): void {
|
||||
if (!this.allowDisabledSelection && !cell.enabled) {
|
||||
return;
|
||||
@@ -104,4 +108,13 @@ export class MatCalendarBody {
|
||||
|
||||
return cellNumber == this.activeCell;
|
||||
}
|
||||
|
||||
/** Focuses the active cell after the microtask queue is empty. */
|
||||
_focusActiveCell() {
|
||||
this._ngZone.runOutsideAngular(() => {
|
||||
this._ngZone.onStable.asObservable().pipe(take(1)).subscribe(() => {
|
||||
this._elementRef.nativeElement.querySelector('.mat-calendar-body-active').focus();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mat-calendar-content" (keydown)="_handleCalendarBodyKeydown($event)"
|
||||
[ngSwitch]="_currentView" cdkMonitorSubtreeFocus tabindex="-1">
|
||||
<div class="mat-calendar-content" [ngSwitch]="_currentView" cdkMonitorSubtreeFocus tabindex="-1">
|
||||
<mat-month-view
|
||||
*ngSwitchCase="'month'"
|
||||
[activeDate]="_activeDate"
|
||||
[(activeDate)]="_activeDate"
|
||||
[selected]="selected"
|
||||
[dateFilter]="dateFilter"
|
||||
[maxDate]="maxDate"
|
||||
|
||||
@@ -1,31 +1,11 @@
|
||||
import {
|
||||
DOWN_ARROW,
|
||||
END,
|
||||
ENTER,
|
||||
HOME,
|
||||
LEFT_ARROW,
|
||||
PAGE_DOWN,
|
||||
PAGE_UP,
|
||||
RIGHT_ARROW,
|
||||
UP_ARROW,
|
||||
} from '@angular/cdk/keycodes';
|
||||
import {dispatchFakeEvent, dispatchKeyboardEvent, dispatchMouseEvent} from '@angular/cdk/testing';
|
||||
import {Component} from '@angular/core';
|
||||
import {async, ComponentFixture, inject, TestBed} from '@angular/core/testing';
|
||||
import {
|
||||
AUG,
|
||||
DEC,
|
||||
FEB,
|
||||
JAN,
|
||||
JUL,
|
||||
JUN,
|
||||
MAR,
|
||||
MatNativeDateModule,
|
||||
MAY,
|
||||
NOV,
|
||||
OCT,
|
||||
SEP,
|
||||
} from '@angular/material/core';
|
||||
import {ComponentFixture, TestBed, async, inject} from '@angular/core/testing';
|
||||
import {DEC, FEB, JAN, MatNativeDateModule, NOV} from '@angular/material/core';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {Direction, Directionality} from '@angular/cdk/bidi';
|
||||
import {MatButtonModule} from '../button/index';
|
||||
@@ -33,7 +13,7 @@ import {MatCalendar} from './calendar';
|
||||
import {MatCalendarBody} from './calendar-body';
|
||||
import {MatDatepickerIntl} from './datepicker-intl';
|
||||
import {MatMonthView} from './month-view';
|
||||
import {MatMultiYearView, yearsPerPage, yearsPerRow} from './multi-year-view';
|
||||
import {MatMultiYearView, yearsPerPage} from './multi-year-view';
|
||||
import {MatYearView} from './year-view';
|
||||
|
||||
|
||||
@@ -240,7 +220,9 @@ describe('MatCalendar', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(button.getAttribute('aria-label')).toBe('Go to multi-year view?');
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
describe('a11y', () => {
|
||||
describe('calendar body', () => {
|
||||
@@ -262,155 +244,6 @@ describe('MatCalendar', () => {
|
||||
expect(calendarBodyEl.getAttribute('tabindex')).toBe('-1');
|
||||
});
|
||||
|
||||
describe('month view', () => {
|
||||
it('should decrement date on left arrow press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 30));
|
||||
|
||||
calendarInstance._activeDate = new Date(2017, JAN, 1);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2016, DEC, 31));
|
||||
});
|
||||
|
||||
it('should increment date on left arrow press in rtl', () => {
|
||||
dir.value = 'rtl';
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, FEB, 1));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, FEB, 2));
|
||||
});
|
||||
|
||||
it('should increment date on right arrow press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, FEB, 1));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, FEB, 2));
|
||||
});
|
||||
|
||||
it('should decrement date on right arrow press in rtl', () => {
|
||||
dir.value = 'rtl';
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 30));
|
||||
|
||||
calendarInstance._activeDate = new Date(2017, JAN, 1);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2016, DEC, 31));
|
||||
});
|
||||
|
||||
it('should go up a row on up arrow press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', UP_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 24));
|
||||
|
||||
calendarInstance._activeDate = new Date(2017, JAN, 7);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', UP_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2016, DEC, 31));
|
||||
});
|
||||
|
||||
it('should go down a row on down arrow press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', DOWN_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, FEB, 7));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', DOWN_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, FEB, 14));
|
||||
});
|
||||
|
||||
it('should go to beginning of the month on home press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', HOME);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 1));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', HOME);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 1));
|
||||
});
|
||||
|
||||
it('should go to end of the month on end press', () => {
|
||||
calendarInstance._activeDate = new Date(2017, JAN, 10);
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', END);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', END);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
});
|
||||
|
||||
it('should go back one month on page up press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_UP);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2016, DEC, 31));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_UP);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2016, NOV, 30));
|
||||
});
|
||||
|
||||
it('should go forward one month on page down press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_DOWN);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, FEB, 28));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_DOWN);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, MAR, 28));
|
||||
});
|
||||
|
||||
it('should select active date on enter', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(testComponent.selected).toBeUndefined();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', ENTER);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(testComponent.selected).toEqual(new Date(2017, JAN, 30));
|
||||
});
|
||||
});
|
||||
|
||||
describe('year view', () => {
|
||||
beforeEach(() => {
|
||||
dispatchMouseEvent(periodButton, 'click');
|
||||
@@ -424,166 +257,13 @@ describe('MatCalendar', () => {
|
||||
expect(calendarInstance._currentView).toBe('year');
|
||||
});
|
||||
|
||||
it('should decrement month on left arrow press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2016, DEC, 31));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2016, NOV, 30));
|
||||
});
|
||||
|
||||
it('should increment month on left arrow press in rtl', () => {
|
||||
dir.value = 'rtl';
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, FEB, 28));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, MAR, 28));
|
||||
});
|
||||
|
||||
it('should increment month on right arrow press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, FEB, 28));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, MAR, 28));
|
||||
});
|
||||
|
||||
it('should decrement month on right arrow press in rtl', () => {
|
||||
dir.value = 'rtl';
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2016, DEC, 31));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2016, NOV, 30));
|
||||
});
|
||||
|
||||
it('should go up a row on up arrow press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', UP_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2016, SEP, 30));
|
||||
|
||||
calendarInstance._activeDate = new Date(2017, JUL, 1);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', UP_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, MAR, 1));
|
||||
|
||||
calendarInstance._activeDate = new Date(2017, DEC, 10);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', UP_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, AUG, 10));
|
||||
});
|
||||
|
||||
it('should go down a row on down arrow press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', DOWN_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, MAY, 31));
|
||||
|
||||
calendarInstance._activeDate = new Date(2017, JUN, 1);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', DOWN_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, OCT, 1));
|
||||
|
||||
calendarInstance._activeDate = new Date(2017, SEP, 30);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', DOWN_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2018, JAN, 30));
|
||||
});
|
||||
|
||||
it('should go to first month of the year on home press', () => {
|
||||
calendarInstance._activeDate = new Date(2017, SEP, 30);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', HOME);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 30));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', HOME);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 30));
|
||||
});
|
||||
|
||||
it('should go to last month of the year on end press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', END);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, DEC, 31));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', END);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, DEC, 31));
|
||||
});
|
||||
|
||||
it('should go back one year on page up press', () => {
|
||||
calendarInstance._activeDate = new Date(2016, FEB, 29);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_UP);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2015, FEB, 28));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_UP);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2014, FEB, 28));
|
||||
});
|
||||
|
||||
it('should go forward one year on page down press', () => {
|
||||
calendarInstance._activeDate = new Date(2016, FEB, 29);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_DOWN);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, FEB, 28));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_DOWN);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2018, FEB, 28));
|
||||
});
|
||||
|
||||
it('should return to month view on enter', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW);
|
||||
const tableBodyEl = calendarBodyEl.querySelector('.mat-calendar-body') as HTMLElement;
|
||||
|
||||
dispatchKeyboardEvent(tableBodyEl, 'keydown', RIGHT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', ENTER);
|
||||
dispatchKeyboardEvent(tableBodyEl, 'keydown', ENTER);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('month');
|
||||
@@ -600,109 +280,13 @@ describe('MatCalendar', () => {
|
||||
expect(calendarInstance._currentView).toBe('multi-year');
|
||||
});
|
||||
|
||||
it('should decrement year on left arrow press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2016, JAN, 31));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2015, JAN, 31));
|
||||
});
|
||||
|
||||
it('should increment year on right arrow press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2018, JAN, 31));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2019, JAN, 31));
|
||||
});
|
||||
|
||||
it('should go up a row on up arrow press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', UP_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017 - yearsPerRow, JAN, 31));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', UP_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017 - yearsPerRow * 2, JAN, 31));
|
||||
});
|
||||
|
||||
it('should go down a row on down arrow press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', DOWN_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017 + yearsPerRow, JAN, 31));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', DOWN_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017 + yearsPerRow * 2, JAN, 31));
|
||||
});
|
||||
|
||||
it('should go to first year in current range on home press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', HOME);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2016, JAN, 31));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', HOME);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2016, JAN, 31));
|
||||
});
|
||||
|
||||
it('should go to last year in current range on end press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', END);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2039, JAN, 31));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', END);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2039, JAN, 31));
|
||||
});
|
||||
|
||||
it('should go to same index in previous year range page up press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_UP);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017 - yearsPerPage, JAN, 31));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_UP);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate)
|
||||
.toEqual(new Date(2017 - yearsPerPage * 2, JAN, 31));
|
||||
});
|
||||
|
||||
it('should go to same index in next year range on page down press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_DOWN);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017 + yearsPerPage, JAN, 31));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_DOWN);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate)
|
||||
.toEqual(new Date(2017 + yearsPerPage * 2, JAN, 31));
|
||||
});
|
||||
|
||||
it('should go to year view on enter', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW);
|
||||
const tableBodyEl = calendarBodyEl.querySelector('.mat-calendar-body') as HTMLElement;
|
||||
|
||||
dispatchKeyboardEvent(tableBodyEl, 'keydown', RIGHT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', ENTER);
|
||||
dispatchKeyboardEvent(tableBodyEl, 'keydown', ENTER);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('year');
|
||||
@@ -710,8 +294,10 @@ describe('MatCalendar', () => {
|
||||
expect(testComponent.selected).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('calendar with min and max date', () => {
|
||||
@@ -904,13 +490,13 @@ describe('MatCalendar', () => {
|
||||
});
|
||||
|
||||
describe('a11y', () => {
|
||||
let calendarBodyEl: HTMLElement;
|
||||
let tableBodyEl: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
calendarBodyEl = calendarElement.querySelector('.mat-calendar-content') as HTMLElement;
|
||||
expect(calendarBodyEl).not.toBeNull();
|
||||
tableBodyEl = calendarElement.querySelector('.mat-calendar-body') as HTMLElement;
|
||||
expect(tableBodyEl).not.toBeNull();
|
||||
|
||||
dispatchFakeEvent(calendarBodyEl, 'focus');
|
||||
dispatchFakeEvent(tableBodyEl, 'focus');
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
@@ -918,7 +504,7 @@ describe('MatCalendar', () => {
|
||||
expect(calendarInstance._currentView).toBe('month');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 1));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', ENTER);
|
||||
dispatchKeyboardEvent(tableBodyEl, 'keydown', ENTER);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(testComponent.selected).toBeUndefined();
|
||||
@@ -938,13 +524,15 @@ describe('MatCalendar', () => {
|
||||
|
||||
expect(calendarInstance._currentView).toBe('year');
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', ENTER);
|
||||
tableBodyEl = calendarElement.querySelector('.mat-calendar-body') as HTMLElement;
|
||||
dispatchKeyboardEvent(tableBodyEl, 'keydown', ENTER);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('month');
|
||||
expect(testComponent.selected).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -989,6 +577,6 @@ class CalendarWithDateFilter {
|
||||
startDate = new Date(2017, JAN, 1);
|
||||
|
||||
dateFilter (date: Date) {
|
||||
return date.getDate() % 2 == 0 && date.getMonth() != NOV;
|
||||
return !(date.getDate() % 2) && date.getMonth() !== NOV;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,27 +6,14 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {
|
||||
DOWN_ARROW,
|
||||
END,
|
||||
ENTER,
|
||||
HOME,
|
||||
LEFT_ARROW,
|
||||
PAGE_DOWN,
|
||||
PAGE_UP,
|
||||
RIGHT_ARROW,
|
||||
UP_ARROW,
|
||||
} from '@angular/cdk/keycodes';
|
||||
import {
|
||||
AfterContentInit,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
Inject,
|
||||
Input,
|
||||
NgZone,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
Optional,
|
||||
@@ -36,14 +23,12 @@ import {
|
||||
ViewEncapsulation,
|
||||
} from '@angular/core';
|
||||
import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '@angular/material/core';
|
||||
import {take} from 'rxjs/operators/take';
|
||||
import {Subscription} from 'rxjs/Subscription';
|
||||
import {createMissingDateImplError} from './datepicker-errors';
|
||||
import {MatDatepickerIntl} from './datepicker-intl';
|
||||
import {MatMonthView} from './month-view';
|
||||
import {MatMultiYearView, yearsPerPage, yearsPerRow} from './multi-year-view';
|
||||
import {MatMultiYearView, yearsPerPage} from './multi-year-view';
|
||||
import {MatYearView} from './year-view';
|
||||
import {Directionality} from '@angular/cdk/bidi';
|
||||
|
||||
|
||||
/**
|
||||
@@ -184,13 +169,10 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
|
||||
}[this._currentView];
|
||||
}
|
||||
|
||||
constructor(private _elementRef: ElementRef,
|
||||
private _intl: MatDatepickerIntl,
|
||||
private _ngZone: NgZone,
|
||||
constructor(private _intl: MatDatepickerIntl,
|
||||
@Optional() private _dateAdapter: DateAdapter<D>,
|
||||
@Optional() @Inject(MAT_DATE_FORMATS) private _dateFormats: MatDateFormats,
|
||||
changeDetectorRef: ChangeDetectorRef,
|
||||
@Optional() private _dir?: Directionality) {
|
||||
changeDetectorRef: ChangeDetectorRef) {
|
||||
|
||||
if (!this._dateAdapter) {
|
||||
throw createMissingDateImplError('DateAdapter');
|
||||
@@ -205,7 +187,6 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
|
||||
|
||||
ngAfterContentInit() {
|
||||
this._activeDate = this.startAt || this._dateAdapter.today();
|
||||
this._focusActiveCell();
|
||||
this._currentView = this.startView;
|
||||
}
|
||||
|
||||
@@ -246,7 +227,7 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
|
||||
this._userSelection.emit();
|
||||
}
|
||||
|
||||
/** Handles month selection in the multi-year view. */
|
||||
/** Handles year/month selection in the multi-year/year views. */
|
||||
_goToDateInView(date: D, view: 'month' | 'year' | 'multi-year'): void {
|
||||
this._activeDate = date;
|
||||
this._currentView = view;
|
||||
@@ -286,29 +267,6 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
|
||||
return !this.maxDate || !this._isSameView(this._activeDate, this.maxDate);
|
||||
}
|
||||
|
||||
/** Handles keydown events on the calendar body. */
|
||||
_handleCalendarBodyKeydown(event: KeyboardEvent): void {
|
||||
// TODO(mmalerba): We currently allow keyboard navigation to disabled dates, but just prevent
|
||||
// disabled ones from being selected. This may not be ideal, we should look into whether
|
||||
// navigation should skip over disabled dates, and if so, how to implement that efficiently.
|
||||
if (this._currentView == 'month') {
|
||||
this._handleCalendarBodyKeydownInMonthView(event);
|
||||
} else if (this._currentView == 'year') {
|
||||
this._handleCalendarBodyKeydownInYearView(event);
|
||||
} else {
|
||||
this._handleCalendarBodyKeydownInMultiYearView(event);
|
||||
}
|
||||
}
|
||||
|
||||
/** Focuses the active cell after the microtask queue is empty. */
|
||||
_focusActiveCell() {
|
||||
this._ngZone.runOutsideAngular(() => {
|
||||
this._ngZone.onStable.asObservable().pipe(take(1)).subscribe(() => {
|
||||
this._elementRef.nativeElement.querySelector('.mat-calendar-body-active').focus();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** Whether the two dates represent the same view in the current view mode (month or year). */
|
||||
private _isSameView(date1: D, date2: D): boolean {
|
||||
if (this._currentView == 'month') {
|
||||
@@ -323,152 +281,6 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
|
||||
Math.floor(this._dateAdapter.getYear(date2) / yearsPerPage);
|
||||
}
|
||||
|
||||
/** Handles keydown events on the calendar body when calendar is in month view. */
|
||||
private _handleCalendarBodyKeydownInMonthView(event: KeyboardEvent): void {
|
||||
const isRtl = this._isRtl();
|
||||
|
||||
switch (event.keyCode) {
|
||||
case LEFT_ARROW:
|
||||
this._activeDate = this._dateAdapter.addCalendarDays(this._activeDate, isRtl ? 1 : -1);
|
||||
break;
|
||||
case RIGHT_ARROW:
|
||||
this._activeDate = this._dateAdapter.addCalendarDays(this._activeDate, isRtl ? -1 : 1);
|
||||
break;
|
||||
case UP_ARROW:
|
||||
this._activeDate = this._dateAdapter.addCalendarDays(this._activeDate, -7);
|
||||
break;
|
||||
case DOWN_ARROW:
|
||||
this._activeDate = this._dateAdapter.addCalendarDays(this._activeDate, 7);
|
||||
break;
|
||||
case HOME:
|
||||
this._activeDate = this._dateAdapter.addCalendarDays(this._activeDate,
|
||||
1 - this._dateAdapter.getDate(this._activeDate));
|
||||
break;
|
||||
case END:
|
||||
this._activeDate = this._dateAdapter.addCalendarDays(this._activeDate,
|
||||
(this._dateAdapter.getNumDaysInMonth(this._activeDate) -
|
||||
this._dateAdapter.getDate(this._activeDate)));
|
||||
break;
|
||||
case PAGE_UP:
|
||||
this._activeDate = event.altKey ?
|
||||
this._dateAdapter.addCalendarYears(this._activeDate, -1) :
|
||||
this._dateAdapter.addCalendarMonths(this._activeDate, -1);
|
||||
break;
|
||||
case PAGE_DOWN:
|
||||
this._activeDate = event.altKey ?
|
||||
this._dateAdapter.addCalendarYears(this._activeDate, 1) :
|
||||
this._dateAdapter.addCalendarMonths(this._activeDate, 1);
|
||||
break;
|
||||
case ENTER:
|
||||
if (!this.dateFilter || this.dateFilter(this._activeDate)) {
|
||||
this._dateSelected(this._activeDate);
|
||||
this._userSelected();
|
||||
// Prevent unexpected default actions such as form submission.
|
||||
event.preventDefault();
|
||||
}
|
||||
return;
|
||||
default:
|
||||
// Don't prevent default or focus active cell on keys that we don't explicitly handle.
|
||||
return;
|
||||
}
|
||||
|
||||
this._focusActiveCell();
|
||||
// Prevent unexpected default actions such as form submission.
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
/** Handles keydown events on the calendar body when calendar is in year view. */
|
||||
private _handleCalendarBodyKeydownInYearView(event: KeyboardEvent): void {
|
||||
const isRtl = this._isRtl();
|
||||
|
||||
switch (event.keyCode) {
|
||||
case LEFT_ARROW:
|
||||
this._activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, isRtl ? 1 : -1);
|
||||
break;
|
||||
case RIGHT_ARROW:
|
||||
this._activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, isRtl ? -1 : 1);
|
||||
break;
|
||||
case UP_ARROW:
|
||||
this._activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, -4);
|
||||
break;
|
||||
case DOWN_ARROW:
|
||||
this._activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, 4);
|
||||
break;
|
||||
case HOME:
|
||||
this._activeDate = this._dateAdapter.addCalendarMonths(this._activeDate,
|
||||
-this._dateAdapter.getMonth(this._activeDate));
|
||||
break;
|
||||
case END:
|
||||
this._activeDate = this._dateAdapter.addCalendarMonths(this._activeDate,
|
||||
11 - this._dateAdapter.getMonth(this._activeDate));
|
||||
break;
|
||||
case PAGE_UP:
|
||||
this._activeDate =
|
||||
this._dateAdapter.addCalendarYears(this._activeDate, event.altKey ? -10 : -1);
|
||||
break;
|
||||
case PAGE_DOWN:
|
||||
this._activeDate =
|
||||
this._dateAdapter.addCalendarYears(this._activeDate, event.altKey ? 10 : 1);
|
||||
break;
|
||||
case ENTER:
|
||||
this._goToDateInView(this._activeDate, 'month');
|
||||
break;
|
||||
default:
|
||||
// Don't prevent default or focus active cell on keys that we don't explicitly handle.
|
||||
return;
|
||||
}
|
||||
|
||||
this._focusActiveCell();
|
||||
// Prevent unexpected default actions such as form submission.
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
/** Handles keydown events on the calendar body when calendar is in multi-year view. */
|
||||
private _handleCalendarBodyKeydownInMultiYearView(event: KeyboardEvent): void {
|
||||
switch (event.keyCode) {
|
||||
case LEFT_ARROW:
|
||||
this._activeDate = this._dateAdapter.addCalendarYears(this._activeDate, -1);
|
||||
break;
|
||||
case RIGHT_ARROW:
|
||||
this._activeDate = this._dateAdapter.addCalendarYears(this._activeDate, 1);
|
||||
break;
|
||||
case UP_ARROW:
|
||||
this._activeDate = this._dateAdapter.addCalendarYears(this._activeDate, -yearsPerRow);
|
||||
break;
|
||||
case DOWN_ARROW:
|
||||
this._activeDate = this._dateAdapter.addCalendarYears(this._activeDate, yearsPerRow);
|
||||
break;
|
||||
case HOME:
|
||||
this._activeDate = this._dateAdapter.addCalendarYears(this._activeDate,
|
||||
-this._dateAdapter.getYear(this._activeDate) % yearsPerPage);
|
||||
break;
|
||||
case END:
|
||||
this._activeDate = this._dateAdapter.addCalendarYears(this._activeDate,
|
||||
yearsPerPage - this._dateAdapter.getYear(this._activeDate) % yearsPerPage - 1);
|
||||
break;
|
||||
case PAGE_UP:
|
||||
this._activeDate =
|
||||
this._dateAdapter.addCalendarYears(
|
||||
this._activeDate, event.altKey ? -yearsPerPage * 10 : -yearsPerPage);
|
||||
break;
|
||||
case PAGE_DOWN:
|
||||
this._activeDate =
|
||||
this._dateAdapter.addCalendarYears(
|
||||
this._activeDate, event.altKey ? yearsPerPage * 10 : yearsPerPage);
|
||||
break;
|
||||
case ENTER:
|
||||
this._goToDateInView(this._activeDate, 'year');
|
||||
break;
|
||||
default:
|
||||
// Don't prevent default or focus active cell on keys that we don't explicitly handle.
|
||||
return;
|
||||
}
|
||||
|
||||
this._focusActiveCell();
|
||||
// Prevent unexpected default actions such as form submission.
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param obj The object to check.
|
||||
* @returns The given object if it is both a date instance and valid, otherwise null.
|
||||
@@ -476,9 +288,4 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
|
||||
private _getValidDateOrNull(obj: any): D | null {
|
||||
return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null;
|
||||
}
|
||||
|
||||
/** Determines whether the user has the RTL layout direction. */
|
||||
private _isRtl() {
|
||||
return this._dir && this._dir.value === 'rtl';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ describe('MatDatepicker', () => {
|
||||
expect(document.querySelector('mat-dialog-container')).not.toBeNull();
|
||||
expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 1));
|
||||
|
||||
let calendarBodyEl = document.querySelector('.mat-calendar-content') as HTMLElement;
|
||||
let calendarBodyEl = document.querySelector('.mat-calendar-body') as HTMLElement;
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW);
|
||||
fixture.detectChanges();
|
||||
@@ -279,7 +279,7 @@ describe('MatDatepicker', () => {
|
||||
testComponent.datepicker.open();
|
||||
fixture.detectChanges();
|
||||
|
||||
let calendarBodyEl = document.querySelector('.mat-calendar-content') as HTMLElement;
|
||||
let calendarBodyEl = document.querySelector('.mat-calendar-body') as HTMLElement;
|
||||
expect(calendarBodyEl).not.toBeNull();
|
||||
expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 1));
|
||||
|
||||
@@ -402,7 +402,8 @@ describe('MatDatepicker', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(testComponent.datepicker.opened).toBe(false);
|
||||
})));
|
||||
}))
|
||||
);
|
||||
});
|
||||
|
||||
describe('datepicker with too many inputs', () => {
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
ComponentRef,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
Inject,
|
||||
InjectionToken,
|
||||
@@ -36,18 +37,16 @@ import {
|
||||
ViewChild,
|
||||
ViewContainerRef,
|
||||
ViewEncapsulation,
|
||||
ElementRef,
|
||||
} from '@angular/core';
|
||||
import {DateAdapter} from '@angular/material/core';
|
||||
import {CanColor, DateAdapter, mixinColor, ThemePalette} from '@angular/material/core';
|
||||
import {MatDialog, MatDialogRef} from '@angular/material/dialog';
|
||||
import {DOCUMENT} from '@angular/common';
|
||||
import {Subject} from 'rxjs/Subject';
|
||||
import {Subscription} from 'rxjs/Subscription';
|
||||
import {merge} from 'rxjs/observable/merge';
|
||||
import {MatCalendar} from './calendar';
|
||||
import {createMissingDateImplError} from './datepicker-errors';
|
||||
import {MatDatepickerInput} from './datepicker-input';
|
||||
import {CanColor, mixinColor, ThemePalette} from '@angular/material/core';
|
||||
import {MatCalendar} from './calendar';
|
||||
|
||||
|
||||
/** Used to generate a unique ID for each datepicker instance. */
|
||||
@@ -73,7 +72,7 @@ export const MAT_DATEPICKER_SCROLL_STRATEGY_PROVIDER = {
|
||||
// Boilerplate for applying mixins to MatDatepickerContent.
|
||||
/** @docs-private */
|
||||
export class MatDatepickerContentBase {
|
||||
constructor(public _elementRef: ElementRef) {}
|
||||
constructor(public _elementRef: ElementRef) { }
|
||||
}
|
||||
export const _MatDatepickerContentMixinBase = mixinColor(MatDatepickerContentBase);
|
||||
|
||||
@@ -105,12 +104,21 @@ export class MatDatepickerContent<D> extends _MatDatepickerContentMixinBase
|
||||
|
||||
@ViewChild(MatCalendar) _calendar: MatCalendar<D>;
|
||||
|
||||
constructor(elementRef: ElementRef) {
|
||||
constructor(elementRef: ElementRef, private _ngZone: NgZone) {
|
||||
super(elementRef);
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
this._calendar._focusActiveCell();
|
||||
this._focusActiveCell();
|
||||
}
|
||||
|
||||
/** Focuses the active cell after the microtask queue is empty. */
|
||||
private _focusActiveCell() {
|
||||
this._ngZone.runOutsideAngular(() => {
|
||||
this._ngZone.onStable.asObservable().pipe(take(1)).subscribe(() => {
|
||||
this._elementRef.nativeElement.querySelector('.mat-calendar-body-active').focus();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,20 +378,23 @@ export class MatDatepicker<D> implements OnDestroy, CanColor {
|
||||
|
||||
/** Open the calendar as a dialog. */
|
||||
private _openAsDialog(): void {
|
||||
this._dialogRef = this._dialog.open(MatDatepickerContent, {
|
||||
this._dialogRef = this._dialog.open<MatDatepickerContent<D>>(MatDatepickerContent, {
|
||||
direction: this._dir ? this._dir.value : 'ltr',
|
||||
viewContainerRef: this._viewContainerRef,
|
||||
panelClass: 'mat-datepicker-dialog',
|
||||
});
|
||||
this._dialogRef.afterClosed().subscribe(() => this.close());
|
||||
this._dialogRef.componentInstance.datepicker = this;
|
||||
if (this._dialogRef) {
|
||||
this._dialogRef.afterClosed().subscribe(() => this.close());
|
||||
this._dialogRef.componentInstance.datepicker = this;
|
||||
}
|
||||
this._setColor();
|
||||
}
|
||||
|
||||
/** Open the calendar as a popup. */
|
||||
private _openAsPopup(): void {
|
||||
if (!this._calendarPortal) {
|
||||
this._calendarPortal = new ComponentPortal(MatDatepickerContent, this._viewContainerRef);
|
||||
this._calendarPortal = new ComponentPortal<MatDatepickerContent<D>>(MatDatepickerContent,
|
||||
this._viewContainerRef);
|
||||
}
|
||||
|
||||
if (!this._popupRef) {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
[selectedValue]="_selectedDate"
|
||||
[labelMinRequiredCells]="3"
|
||||
[activeCell]="_dateAdapter.getDate(activeDate) - 1"
|
||||
(selectedValueChange)="_dateSelected($event)">
|
||||
(selectedValueChange)="_dateSelected($event)"
|
||||
(keydown)="_handleCalendarBodyKeydown($event)">
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1,12 +1,27 @@
|
||||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {Component} from '@angular/core';
|
||||
import {
|
||||
DOWN_ARROW,
|
||||
END,
|
||||
ENTER,
|
||||
HOME,
|
||||
LEFT_ARROW,
|
||||
PAGE_DOWN,
|
||||
PAGE_UP,
|
||||
RIGHT_ARROW,
|
||||
UP_ARROW,
|
||||
} from '@angular/cdk/keycodes';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {MatMonthView} from './month-view';
|
||||
import {Component} from '@angular/core';
|
||||
import {Direction, Directionality} from '@angular/cdk/bidi';
|
||||
import {JAN, MAR, NOV, FEB} from '@angular/material/core';
|
||||
import {MatCalendarBody} from './calendar-body';
|
||||
import {MatNativeDateModule} from '@angular/material/core';
|
||||
import {JAN, MAR} from '@angular/material/core';
|
||||
import {MatMonthView} from './month-view';
|
||||
import {MatNativeDateModule, DEC} from '@angular/material/core';
|
||||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {dispatchKeyboardEvent, dispatchFakeEvent} from '@angular/cdk/testing';
|
||||
|
||||
describe('MatMonthView', () => {
|
||||
let dir: {value: Direction};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
@@ -20,6 +35,9 @@ describe('MatMonthView', () => {
|
||||
StandardMonthView,
|
||||
MonthViewWithDateFilter,
|
||||
],
|
||||
providers: [
|
||||
{provide: Directionality, useFactory: () => dir = {value: 'ltr'}}
|
||||
]
|
||||
});
|
||||
|
||||
TestBed.compileComponents();
|
||||
@@ -76,6 +94,170 @@ describe('MatMonthView', () => {
|
||||
expect((cellEls[4] as HTMLElement).innerText.trim()).toBe('5');
|
||||
expect(cellEls[4].classList).toContain('mat-calendar-body-active');
|
||||
});
|
||||
|
||||
describe('a11y', () => {
|
||||
describe('calendar body', () => {
|
||||
let calendarBodyEl: HTMLElement;
|
||||
let calendarInstance: StandardMonthView;
|
||||
|
||||
beforeEach(() => {
|
||||
calendarInstance = fixture.componentInstance;
|
||||
calendarBodyEl =
|
||||
fixture.debugElement.nativeElement.querySelector('.mat-calendar-body') as HTMLElement;
|
||||
expect(calendarBodyEl).not.toBeNull();
|
||||
dir.value = 'ltr';
|
||||
fixture.componentInstance.date = new Date(2017, JAN, 5);
|
||||
dispatchFakeEvent(calendarBodyEl, 'focus');
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should decrement date on left arrow press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW);
|
||||
fixture.detectChanges();
|
||||
expect(calendarInstance.date).toEqual(new Date(2017, JAN, 4));
|
||||
|
||||
calendarInstance.date = new Date(2017, JAN, 1);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.date).toEqual(new Date(2016, DEC, 31));
|
||||
});
|
||||
|
||||
it('should increment date on left arrow press in rtl', () => {
|
||||
dir.value = 'rtl';
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.date).toEqual(new Date(2017, JAN, 6));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.date).toEqual(new Date(2017, JAN, 7));
|
||||
});
|
||||
|
||||
it('should increment date on right arrow press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.date).toEqual(new Date(2017, JAN, 6));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.date).toEqual(new Date(2017, JAN, 7));
|
||||
});
|
||||
|
||||
it('should decrement date on right arrow press in rtl', () => {
|
||||
dir.value = 'rtl';
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.date).toEqual(new Date(2017, JAN, 4));
|
||||
|
||||
calendarInstance.date = new Date(2017, JAN, 1);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.date).toEqual(new Date(2016, DEC, 31));
|
||||
});
|
||||
|
||||
it('should go up a row on up arrow press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', UP_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.date).toEqual(new Date(2016, DEC, 29));
|
||||
|
||||
calendarInstance.date = new Date(2017, JAN, 7);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', UP_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.date).toEqual(new Date(2016, DEC, 31));
|
||||
});
|
||||
|
||||
it('should go down a row on down arrow press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', DOWN_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.date).toEqual(new Date(2017, JAN, 12));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', DOWN_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.date).toEqual(new Date(2017, JAN, 19));
|
||||
});
|
||||
|
||||
it('should go to beginning of the month on home press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', HOME);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.date).toEqual(new Date(2017, JAN, 1));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', HOME);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.date).toEqual(new Date(2017, JAN, 1));
|
||||
});
|
||||
|
||||
it('should go to end of the month on end press', () => {
|
||||
calendarInstance.date = new Date(2017, JAN, 10);
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', END);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.date).toEqual(new Date(2017, JAN, 31));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', END);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.date).toEqual(new Date(2017, JAN, 31));
|
||||
});
|
||||
|
||||
it('should go back one month on page up press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_UP);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.date).toEqual(new Date(2016, DEC, 5));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_UP);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.date).toEqual(new Date(2016, NOV, 5));
|
||||
});
|
||||
|
||||
it('should go forward one month on page down press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_DOWN);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.date).toEqual(new Date(2017, FEB, 5));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_DOWN);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.date).toEqual(new Date(2017, MAR, 5));
|
||||
});
|
||||
|
||||
it('should select active date on enter', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(testComponent.selected).toEqual(new Date(2017, JAN, 10));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', ENTER);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(testComponent.selected).toEqual(new Date(2017, JAN, 4));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('month view with date filter', () => {
|
||||
@@ -102,7 +284,7 @@ describe('MatMonthView', () => {
|
||||
|
||||
|
||||
@Component({
|
||||
template: `<mat-month-view [activeDate]="date" [(selected)]="selected"></mat-month-view>`,
|
||||
template: `<mat-month-view [(activeDate)]="date" [(selected)]="selected"></mat-month-view>`,
|
||||
})
|
||||
class StandardMonthView {
|
||||
date = new Date(2017, JAN, 5);
|
||||
|
||||
@@ -6,9 +6,21 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {
|
||||
DOWN_ARROW,
|
||||
END,
|
||||
ENTER,
|
||||
HOME,
|
||||
LEFT_ARROW,
|
||||
PAGE_DOWN,
|
||||
PAGE_UP,
|
||||
RIGHT_ARROW,
|
||||
UP_ARROW,
|
||||
} from '@angular/cdk/keycodes';
|
||||
import {
|
||||
AfterContentInit,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Inject,
|
||||
@@ -16,10 +28,11 @@ import {
|
||||
Optional,
|
||||
Output,
|
||||
ViewEncapsulation,
|
||||
ChangeDetectorRef,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '@angular/material/core';
|
||||
import {MatCalendarCell} from './calendar-body';
|
||||
import {Directionality} from '@angular/cdk/bidi';
|
||||
import {MatCalendarBody, MatCalendarCell} from './calendar-body';
|
||||
import {createMissingDateImplError} from './datepicker-errors';
|
||||
|
||||
|
||||
@@ -37,7 +50,7 @@ const DAYS_PER_WEEK = 7;
|
||||
exportAs: 'matMonthView',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
preserveWhitespaces: false,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class MatMonthView<D> implements AfterContentInit {
|
||||
/**
|
||||
@@ -46,9 +59,10 @@ export class MatMonthView<D> implements AfterContentInit {
|
||||
@Input()
|
||||
get activeDate(): D { return this._activeDate; }
|
||||
set activeDate(value: D) {
|
||||
let oldActiveDate = this._activeDate;
|
||||
this._activeDate =
|
||||
const oldActiveDate = this._activeDate;
|
||||
const validDate =
|
||||
this._getValidDateOrNull(this._dateAdapter.deserialize(value)) || this._dateAdapter.today();
|
||||
this._activeDate = this._dateAdapter.clampDate(validDate, this.minDate, this.maxDate);
|
||||
if (!this._hasSameMonthAndYear(oldActiveDate, this._activeDate)) {
|
||||
this._init();
|
||||
}
|
||||
@@ -89,6 +103,12 @@ export class MatMonthView<D> implements AfterContentInit {
|
||||
/** Emits when any date is selected. */
|
||||
@Output() readonly _userSelection: EventEmitter<void> = new EventEmitter<void>();
|
||||
|
||||
/** Emits when any date is activated. */
|
||||
@Output() readonly activeDateChange: EventEmitter<D> = new EventEmitter<D>();
|
||||
|
||||
/** The body of calendar table */
|
||||
@ViewChild(MatCalendarBody) _matCalendarBody;
|
||||
|
||||
/** The label for this month (e.g. "January 2017"). */
|
||||
_monthLabel: string;
|
||||
|
||||
@@ -110,9 +130,10 @@ export class MatMonthView<D> implements AfterContentInit {
|
||||
/** The names of the weekdays. */
|
||||
_weekdays: {long: string, narrow: string}[];
|
||||
|
||||
constructor(@Optional() public _dateAdapter: DateAdapter<D>,
|
||||
constructor(private _changeDetectorRef: ChangeDetectorRef,
|
||||
@Optional() @Inject(MAT_DATE_FORMATS) private _dateFormats: MatDateFormats,
|
||||
private _changeDetectorRef: ChangeDetectorRef) {
|
||||
@Optional() public _dateAdapter: DateAdapter<D>,
|
||||
@Optional() private _dir?: Directionality) {
|
||||
if (!this._dateAdapter) {
|
||||
throw createMissingDateImplError('DateAdapter');
|
||||
}
|
||||
@@ -135,6 +156,7 @@ export class MatMonthView<D> implements AfterContentInit {
|
||||
|
||||
ngAfterContentInit() {
|
||||
this._init();
|
||||
this._focusActiveCell();
|
||||
}
|
||||
|
||||
/** Handles when a new date is selected. */
|
||||
@@ -150,6 +172,69 @@ export class MatMonthView<D> implements AfterContentInit {
|
||||
this._userSelection.emit();
|
||||
}
|
||||
|
||||
/** 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
|
||||
// disabled ones from being selected. This may not be ideal, we should look into whether
|
||||
// navigation should skip over disabled dates, and if so, how to implement that efficiently.
|
||||
|
||||
const oldActiveDate = this._activeDate;
|
||||
|
||||
const isRtl = this._isRtl();
|
||||
switch (event.keyCode) {
|
||||
case LEFT_ARROW:
|
||||
this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, isRtl ? 1 : -1);
|
||||
break;
|
||||
case RIGHT_ARROW:
|
||||
this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, isRtl ? -1 : 1);
|
||||
break;
|
||||
case UP_ARROW:
|
||||
this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, -7);
|
||||
break;
|
||||
case DOWN_ARROW:
|
||||
this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate, 7);
|
||||
break;
|
||||
case HOME:
|
||||
this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate,
|
||||
1 - this._dateAdapter.getDate(this._activeDate));
|
||||
break;
|
||||
case END:
|
||||
this.activeDate = this._dateAdapter.addCalendarDays(this._activeDate,
|
||||
(this._dateAdapter.getNumDaysInMonth(this._activeDate) -
|
||||
this._dateAdapter.getDate(this._activeDate)));
|
||||
break;
|
||||
case PAGE_UP:
|
||||
this.activeDate = event.altKey ?
|
||||
this._dateAdapter.addCalendarYears(this._activeDate, -1) :
|
||||
this._dateAdapter.addCalendarMonths(this._activeDate, -1);
|
||||
break;
|
||||
case PAGE_DOWN:
|
||||
this.activeDate = event.altKey ?
|
||||
this._dateAdapter.addCalendarYears(this._activeDate, 1) :
|
||||
this._dateAdapter.addCalendarMonths(this._activeDate, 1);
|
||||
break;
|
||||
case ENTER:
|
||||
if (!this.dateFilter || this.dateFilter(this._activeDate)) {
|
||||
this._dateSelected(this._dateAdapter.getDate(this._activeDate));
|
||||
this._userSelection.emit();
|
||||
// Prevent unexpected default actions such as form submission.
|
||||
event.preventDefault();
|
||||
}
|
||||
return;
|
||||
default:
|
||||
// Don't prevent default or focus active cell on keys that we don't explicitly handle.
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._dateAdapter.compareDate(oldActiveDate, this.activeDate)) {
|
||||
this.activeDateChange.emit(this.activeDate);
|
||||
}
|
||||
|
||||
this._focusActiveCell();
|
||||
// Prevent unexpected default actions such as form submission.
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
/** Initializes this month view. */
|
||||
_init() {
|
||||
this._selectedDate = this._getDateInCurrentMonth(this.selected);
|
||||
@@ -168,6 +253,11 @@ export class MatMonthView<D> implements AfterContentInit {
|
||||
this._changeDetectorRef.markForCheck();
|
||||
}
|
||||
|
||||
/** Focuses the active cell after the microtask queue is empty. */
|
||||
private _focusActiveCell() {
|
||||
this._matCalendarBody._focusActiveCell();
|
||||
}
|
||||
|
||||
/** Creates MatCalendarCells for the dates in this month. */
|
||||
private _createWeekCells() {
|
||||
const daysInMonth = this._dateAdapter.getNumDaysInMonth(this.activeDate);
|
||||
@@ -218,4 +308,9 @@ export class MatMonthView<D> implements AfterContentInit {
|
||||
private _getValidDateOrNull(obj: any): D | null {
|
||||
return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null;
|
||||
}
|
||||
|
||||
/** Determines whether the user has the RTL layout direction. */
|
||||
private _isRtl() {
|
||||
return this._dir && this._dir.value === 'rtl';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
[numCols]="4"
|
||||
[cellAspectRatio]="4 / 7"
|
||||
[activeCell]="_getActiveCell()"
|
||||
(selectedValueChange)="_yearSelected($event)">
|
||||
(selectedValueChange)="_yearSelected($event)"
|
||||
(keydown)="_handleCalendarBodyKeydown($event)">
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
import {Component, ViewChild} from '@angular/core';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {JAN, MatNativeDateModule} from '@angular/material/core';
|
||||
import {
|
||||
DOWN_ARROW,
|
||||
END,
|
||||
HOME,
|
||||
LEFT_ARROW,
|
||||
PAGE_DOWN,
|
||||
PAGE_UP,
|
||||
RIGHT_ARROW,
|
||||
UP_ARROW,
|
||||
} from '@angular/cdk/keycodes';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {Component, ViewChild} from '@angular/core';
|
||||
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
||||
import {Direction, Directionality} from '@angular/cdk/bidi';
|
||||
import {JAN, MatNativeDateModule} from '@angular/material/core';
|
||||
import {MatCalendarBody} from './calendar-body';
|
||||
import {MatMultiYearView, yearsPerPage} from './multi-year-view';
|
||||
import {MatYearView} from './year-view';
|
||||
import {MatMultiYearView, yearsPerPage, yearsPerRow} from './multi-year-view';
|
||||
import {dispatchKeyboardEvent, dispatchFakeEvent} from '@angular/cdk/testing';
|
||||
|
||||
describe('MatMultiYearView', () => {
|
||||
beforeEach(() => {
|
||||
let dir: {value: Direction};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MatNativeDateModule,
|
||||
@@ -20,10 +33,13 @@ describe('MatMultiYearView', () => {
|
||||
StandardMultiYearView,
|
||||
MultiYearViewWithDateFilter,
|
||||
],
|
||||
providers: [
|
||||
{provide: Directionality, useFactory: () => dir = {value: 'ltr'}}
|
||||
]
|
||||
});
|
||||
|
||||
TestBed.compileComponents();
|
||||
});
|
||||
}));
|
||||
|
||||
describe('standard multi-year view', () => {
|
||||
let fixture: ComponentFixture<StandardMultiYearView>;
|
||||
@@ -81,6 +97,130 @@ describe('MatMultiYearView', () => {
|
||||
expect((cellEls[1] as HTMLElement).innerText.trim()).toBe('2017');
|
||||
expect(cellEls[1].classList).toContain('mat-calendar-body-active');
|
||||
});
|
||||
|
||||
describe('a11y', () => {
|
||||
describe('calendar body', () => {
|
||||
let calendarBodyEl: HTMLElement;
|
||||
let calendarInstance: StandardMultiYearView;
|
||||
|
||||
beforeEach(() => {
|
||||
calendarInstance = fixture.componentInstance;
|
||||
calendarBodyEl =
|
||||
fixture.debugElement.nativeElement.querySelector('.mat-calendar-body') as HTMLElement;
|
||||
expect(calendarBodyEl).not.toBeNull();
|
||||
dir.value = 'ltr';
|
||||
fixture.componentInstance.date = new Date(2017, JAN, 1);
|
||||
dispatchFakeEvent(calendarBodyEl, 'focus');
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should decrement year on left arrow press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.multiYearView.activeDate).toEqual(new Date(2016, JAN, 1));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.multiYearView.activeDate).toEqual(new Date(2015, JAN, 1));
|
||||
});
|
||||
|
||||
it('should increment year on right arrow press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.multiYearView.activeDate).toEqual(new Date(2018, JAN, 1));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.multiYearView.activeDate).toEqual(new Date(2019, JAN, 1));
|
||||
});
|
||||
|
||||
it('should go up a row on up arrow press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', UP_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.multiYearView.activeDate)
|
||||
.toEqual(new Date(2017 - yearsPerRow, JAN, 1));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', UP_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.multiYearView.activeDate)
|
||||
.toEqual(new Date(2017 - yearsPerRow * 2, JAN, 1));
|
||||
});
|
||||
|
||||
it('should go down a row on down arrow press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', DOWN_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.multiYearView.activeDate)
|
||||
.toEqual(new Date(2017 + yearsPerRow, JAN, 1));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', DOWN_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.multiYearView.activeDate)
|
||||
.toEqual(new Date(2017 + yearsPerRow * 2, JAN, 1));
|
||||
});
|
||||
|
||||
it('should go to first year in current range on home press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', HOME);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.multiYearView.activeDate).toEqual(new Date(2016, JAN, 1));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', HOME);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.multiYearView.activeDate).toEqual(new Date(2016, JAN, 1));
|
||||
});
|
||||
|
||||
it('should go to last year in current range on end press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', END);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.multiYearView.activeDate).toEqual(new Date(2039, JAN, 1));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', END);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.multiYearView.activeDate).toEqual(new Date(2039, JAN, 1));
|
||||
});
|
||||
|
||||
it('should go to same index in previous year range page up press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_UP);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.multiYearView.activeDate)
|
||||
.toEqual(new Date(2017 - yearsPerPage, JAN, 1));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_UP);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.multiYearView.activeDate)
|
||||
.toEqual(new Date(2017 - yearsPerPage * 2, JAN, 1));
|
||||
});
|
||||
|
||||
it('should go to same index in next year range on page down press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_DOWN);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.multiYearView.activeDate)
|
||||
.toEqual(new Date(2017 + yearsPerPage, JAN, 1));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_DOWN);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.multiYearView.activeDate)
|
||||
.toEqual(new Date(2017 + yearsPerPage * 2, JAN, 1));
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('multi year view with date filter', () => {
|
||||
@@ -116,7 +256,7 @@ class StandardMultiYearView {
|
||||
selected = new Date(2020, JAN, 1);
|
||||
selectedYear: Date;
|
||||
|
||||
@ViewChild(MatYearView) yearView: MatYearView<Date>;
|
||||
@ViewChild(MatMultiYearView) multiYearView: MatMultiYearView<Date>;
|
||||
}
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -6,6 +6,17 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {
|
||||
DOWN_ARROW,
|
||||
END,
|
||||
ENTER,
|
||||
HOME,
|
||||
LEFT_ARROW,
|
||||
PAGE_DOWN,
|
||||
PAGE_UP,
|
||||
RIGHT_ARROW,
|
||||
UP_ARROW,
|
||||
} from '@angular/cdk/keycodes';
|
||||
import {
|
||||
AfterContentInit,
|
||||
ChangeDetectionStrategy,
|
||||
@@ -15,13 +26,14 @@ import {
|
||||
Input,
|
||||
Optional,
|
||||
Output,
|
||||
ViewEncapsulation
|
||||
ViewChild,
|
||||
ViewEncapsulation,
|
||||
} from '@angular/core';
|
||||
import {DateAdapter} from '@angular/material/core';
|
||||
import {MatCalendarCell} from './calendar-body';
|
||||
import {Directionality} from '@angular/cdk/bidi';
|
||||
import {MatCalendarBody, MatCalendarCell} from './calendar-body';
|
||||
import {createMissingDateImplError} from './datepicker-errors';
|
||||
|
||||
|
||||
export const yearsPerPage = 24;
|
||||
|
||||
export const yearsPerRow = 4;
|
||||
@@ -38,7 +50,7 @@ export const yearsPerRow = 4;
|
||||
exportAs: 'matMultiYearView',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
preserveWhitespaces: false,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class MatMultiYearView<D> implements AfterContentInit {
|
||||
/** The date to display in this multi-year view (everything other than the year is ignored). */
|
||||
@@ -46,8 +58,9 @@ export class MatMultiYearView<D> implements AfterContentInit {
|
||||
get activeDate(): D { return this._activeDate; }
|
||||
set activeDate(value: D) {
|
||||
let oldActiveDate = this._activeDate;
|
||||
this._activeDate =
|
||||
const validDate =
|
||||
this._getValidDateOrNull(this._dateAdapter.deserialize(value)) || this._dateAdapter.today();
|
||||
this._activeDate = this._dateAdapter.clampDate(validDate, this.minDate, this.maxDate);
|
||||
if (Math.floor(this._dateAdapter.getYear(oldActiveDate) / yearsPerPage) !=
|
||||
Math.floor(this._dateAdapter.getYear(this._activeDate) / yearsPerPage)) {
|
||||
this._init();
|
||||
@@ -83,12 +96,15 @@ export class MatMultiYearView<D> implements AfterContentInit {
|
||||
/** A function used to filter which dates are selectable. */
|
||||
@Input() dateFilter: (date: D) => boolean;
|
||||
|
||||
/** Emits when a new month is selected. */
|
||||
/** Emits when a new year is selected. */
|
||||
@Output() readonly selectedChange: EventEmitter<D> = new EventEmitter<D>();
|
||||
|
||||
/** Emits the selected year. This doesn't imply a change on the selected date */
|
||||
@Output() readonly yearSelected: EventEmitter<D> = new EventEmitter<D>();
|
||||
|
||||
/** The body of calendar table */
|
||||
@ViewChild(MatCalendarBody) _matCalendarBody;
|
||||
|
||||
/** Grid of calendar cells representing the currently displayed years. */
|
||||
_years: MatCalendarCell[][];
|
||||
|
||||
@@ -98,8 +114,9 @@ export class MatMultiYearView<D> implements AfterContentInit {
|
||||
/** The year of the selected date. Null if the selected date is null. */
|
||||
_selectedYear: number | null;
|
||||
|
||||
constructor(@Optional() public _dateAdapter: DateAdapter<D>,
|
||||
private _changeDetectorRef: ChangeDetectorRef) {
|
||||
constructor(private _changeDetectorRef: ChangeDetectorRef,
|
||||
@Optional() public _dateAdapter: DateAdapter<D>,
|
||||
@Optional() private _dir?: Directionality) {
|
||||
if (!this._dateAdapter) {
|
||||
throw createMissingDateImplError('DateAdapter');
|
||||
}
|
||||
@@ -109,6 +126,7 @@ export class MatMultiYearView<D> implements AfterContentInit {
|
||||
|
||||
ngAfterContentInit() {
|
||||
this._init();
|
||||
this._focusActiveCell();
|
||||
}
|
||||
|
||||
/** Initializes this multi-year view. */
|
||||
@@ -137,10 +155,67 @@ export class MatMultiYearView<D> implements AfterContentInit {
|
||||
Math.min(this._dateAdapter.getDate(this.activeDate), daysInMonth)));
|
||||
}
|
||||
|
||||
/** Handles keydown events on the calendar body when calendar is in multi-year view. */
|
||||
_handleCalendarBodyKeydown(event: KeyboardEvent): void {
|
||||
// TODO(mmalerba): We currently allow keyboard navigation to disabled dates, but just prevent
|
||||
// disabled ones from being selected. This may not be ideal, we should look into whether
|
||||
// navigation should skip over disabled dates, and if so, how to implement that efficiently.
|
||||
|
||||
const isRtl = this._isRtl();
|
||||
|
||||
switch (event.keyCode) {
|
||||
case LEFT_ARROW:
|
||||
this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, isRtl ? 1 : -1);
|
||||
break;
|
||||
case RIGHT_ARROW:
|
||||
this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, isRtl ? -1 : 1);
|
||||
break;
|
||||
case UP_ARROW:
|
||||
this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, -yearsPerRow);
|
||||
break;
|
||||
case DOWN_ARROW:
|
||||
this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate, yearsPerRow);
|
||||
break;
|
||||
case HOME:
|
||||
this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate,
|
||||
-this._dateAdapter.getYear(this._activeDate) % yearsPerPage);
|
||||
break;
|
||||
case END:
|
||||
this.activeDate = this._dateAdapter.addCalendarYears(this._activeDate,
|
||||
yearsPerPage - this._dateAdapter.getYear(this._activeDate) % yearsPerPage - 1);
|
||||
break;
|
||||
case PAGE_UP:
|
||||
this.activeDate =
|
||||
this._dateAdapter.addCalendarYears(
|
||||
this._activeDate, event.altKey ? -yearsPerPage * 10 : -yearsPerPage);
|
||||
break;
|
||||
case PAGE_DOWN:
|
||||
this.activeDate =
|
||||
this._dateAdapter.addCalendarYears(
|
||||
this._activeDate, event.altKey ? yearsPerPage * 10 : yearsPerPage);
|
||||
break;
|
||||
case ENTER:
|
||||
this._yearSelected(this._dateAdapter.getYear(this._activeDate));
|
||||
break;
|
||||
default:
|
||||
// Don't prevent default or focus active cell on keys that we don't explicitly handle.
|
||||
return;
|
||||
}
|
||||
|
||||
this._focusActiveCell();
|
||||
// Prevent unexpected default actions such as form submission.
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
_getActiveCell(): number {
|
||||
return this._dateAdapter.getYear(this.activeDate) % yearsPerPage;
|
||||
}
|
||||
|
||||
/** Focuses the active cell after the microtask queue is empty. */
|
||||
private _focusActiveCell() {
|
||||
this._matCalendarBody._focusActiveCell();
|
||||
}
|
||||
|
||||
/** Creates an MatCalendarCell for the given year. */
|
||||
private _createCellForYear(year: number) {
|
||||
let yearName = this._dateAdapter.getYearName(this._dateAdapter.createDate(year, 0, 1));
|
||||
@@ -181,4 +256,9 @@ export class MatMultiYearView<D> implements AfterContentInit {
|
||||
private _getValidDateOrNull(obj: any): D | null {
|
||||
return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null;
|
||||
}
|
||||
|
||||
/** Determines whether the user has the RTL layout direction. */
|
||||
private _isRtl() {
|
||||
return this._dir && this._dir.value === 'rtl';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
[numCols]="4"
|
||||
[cellAspectRatio]="4 / 7"
|
||||
[activeCell]="_dateAdapter.getMonth(activeDate)"
|
||||
(selectedValueChange)="_monthSelected($event)">
|
||||
(selectedValueChange)="_monthSelected($event)"
|
||||
(keydown)="_handleCalendarBodyKeydown($event)">
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1,12 +1,26 @@
|
||||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {Component, ViewChild} from '@angular/core';
|
||||
import {
|
||||
DOWN_ARROW,
|
||||
END,
|
||||
HOME,
|
||||
LEFT_ARROW,
|
||||
PAGE_DOWN,
|
||||
PAGE_UP,
|
||||
RIGHT_ARROW,
|
||||
UP_ARROW,
|
||||
} from '@angular/cdk/keycodes';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {MatYearView} from './year-view';
|
||||
import {Component, ViewChild} from '@angular/core';
|
||||
import {Direction, Directionality} from '@angular/cdk/bidi';
|
||||
import {FEB, JAN, JUL, JUN, MAR, DEC, NOV, AUG, MAY, OCT, SEP} from '@angular/material/core';
|
||||
import {MatCalendarBody} from './calendar-body';
|
||||
import {MatNativeDateModule} from '@angular/material/core';
|
||||
import {FEB, JAN, JUL, JUN, MAR} from '@angular/material/core';
|
||||
import {MatYearView} from './year-view';
|
||||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {dispatchKeyboardEvent, dispatchFakeEvent} from '@angular/cdk/testing';
|
||||
|
||||
describe('MatYearView', () => {
|
||||
let dir: {value: Direction};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
@@ -20,6 +34,9 @@ describe('MatYearView', () => {
|
||||
StandardYearView,
|
||||
YearViewWithDateFilter,
|
||||
],
|
||||
providers: [
|
||||
{provide: Directionality, useFactory: () => dir = {value: 'ltr'}}
|
||||
]
|
||||
});
|
||||
|
||||
TestBed.compileComponents();
|
||||
@@ -96,6 +113,182 @@ describe('MatYearView', () => {
|
||||
|
||||
expect(testComponent.selected).toEqual(new Date(2017, JUN, 30));
|
||||
});
|
||||
|
||||
describe('a11y', () => {
|
||||
describe('calendar body', () => {
|
||||
let calendarBodyEl: HTMLElement;
|
||||
let calendarInstance: StandardYearView;
|
||||
|
||||
beforeEach(() => {
|
||||
calendarInstance = fixture.componentInstance;
|
||||
calendarBodyEl =
|
||||
fixture.debugElement.nativeElement.querySelector('.mat-calendar-body') as HTMLElement;
|
||||
expect(calendarBodyEl).not.toBeNull();
|
||||
dir.value = 'ltr';
|
||||
fixture.componentInstance.date = new Date(2017, JAN, 5);
|
||||
dispatchFakeEvent(calendarBodyEl, 'focus');
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should decrement month on left arrow press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.yearView.activeDate).toEqual(new Date(2016, DEC, 5));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.yearView.activeDate).toEqual(new Date(2016, NOV, 5));
|
||||
});
|
||||
|
||||
it('should increment month on left arrow press in rtl', () => {
|
||||
dir.value = 'rtl';
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.yearView.activeDate).toEqual(new Date(2017, FEB, 5));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.yearView.activeDate).toEqual(new Date(2017, MAR, 5));
|
||||
});
|
||||
|
||||
it('should increment month on right arrow press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.yearView.activeDate).toEqual(new Date(2017, FEB, 5));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.yearView.activeDate).toEqual(new Date(2017, MAR, 5));
|
||||
});
|
||||
|
||||
it('should decrement month on right arrow press in rtl', () => {
|
||||
dir.value = 'rtl';
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.yearView.activeDate).toEqual(new Date(2016, DEC, 5));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', RIGHT_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.yearView.activeDate).toEqual(new Date(2016, NOV, 5));
|
||||
});
|
||||
|
||||
it('should go up a row on up arrow press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', UP_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.yearView.activeDate).toEqual(new Date(2016, SEP, 5));
|
||||
|
||||
calendarInstance.yearView.activeDate = new Date(2017, JUL, 1);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', UP_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.yearView.activeDate).toEqual(new Date(2017, MAR, 1));
|
||||
|
||||
calendarInstance.yearView.activeDate = new Date(2017, DEC, 10);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', UP_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.yearView.activeDate).toEqual(new Date(2017, AUG, 10));
|
||||
});
|
||||
|
||||
it('should go down a row on down arrow press', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', DOWN_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.yearView.activeDate).toEqual(new Date(2017, MAY, 5));
|
||||
|
||||
calendarInstance.yearView.activeDate = new Date(2017, JUN, 1);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', DOWN_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.yearView.activeDate).toEqual(new Date(2017, OCT, 1));
|
||||
|
||||
calendarInstance.yearView.activeDate = new Date(2017, SEP, 30);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', DOWN_ARROW);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.yearView.activeDate).toEqual(new Date(2018, JAN, 30));
|
||||
});
|
||||
|
||||
it('should go to first month of the year on home press', () => {
|
||||
calendarInstance.yearView.activeDate = new Date(2017, SEP, 30);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', HOME);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.yearView.activeDate).toEqual(new Date(2017, JAN, 30));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', HOME);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.yearView.activeDate).toEqual(new Date(2017, JAN, 30));
|
||||
});
|
||||
|
||||
it('should go to last month of the year on end press', () => {
|
||||
calendarInstance.yearView.activeDate = new Date(2017, OCT, 31);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', END);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.yearView.activeDate).toEqual(new Date(2017, DEC, 31));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', END);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.yearView.activeDate).toEqual(new Date(2017, DEC, 31));
|
||||
});
|
||||
|
||||
it('should go back one year on page up press', () => {
|
||||
calendarInstance.yearView.activeDate = new Date(2016, FEB, 29);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_UP);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.yearView.activeDate).toEqual(new Date(2015, FEB, 28));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_UP);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.yearView.activeDate).toEqual(new Date(2014, FEB, 28));
|
||||
});
|
||||
|
||||
it('should go forward one year on page down press', () => {
|
||||
calendarInstance.yearView.activeDate = new Date(2016, FEB, 29);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_DOWN);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.yearView.activeDate).toEqual(new Date(2017, FEB, 28));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', PAGE_DOWN);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.yearView.activeDate).toEqual(new Date(2018, FEB, 28));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('year view with date filter', () => {
|
||||
|
||||
@@ -6,23 +6,35 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {
|
||||
DOWN_ARROW,
|
||||
END,
|
||||
ENTER,
|
||||
HOME,
|
||||
LEFT_ARROW,
|
||||
PAGE_DOWN,
|
||||
PAGE_UP,
|
||||
RIGHT_ARROW,
|
||||
UP_ARROW,
|
||||
} from '@angular/cdk/keycodes';
|
||||
import {
|
||||
AfterContentInit,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Inject,
|
||||
Input,
|
||||
Optional,
|
||||
Output,
|
||||
ViewChild,
|
||||
ViewEncapsulation,
|
||||
ChangeDetectorRef,
|
||||
} from '@angular/core';
|
||||
import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '@angular/material/core';
|
||||
import {MatCalendarCell} from './calendar-body';
|
||||
import {Directionality} from '@angular/cdk/bidi';
|
||||
import {MatCalendarBody, MatCalendarCell} from './calendar-body';
|
||||
import {createMissingDateImplError} from './datepicker-errors';
|
||||
|
||||
|
||||
/**
|
||||
* An internal component used to display a single year in the datepicker.
|
||||
* @docs-private
|
||||
@@ -34,7 +46,7 @@ import {createMissingDateImplError} from './datepicker-errors';
|
||||
exportAs: 'matYearView',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
preserveWhitespaces: false,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class MatYearView<D> implements AfterContentInit {
|
||||
/** The date to display in this year view (everything other than the year is ignored). */
|
||||
@@ -42,9 +54,10 @@ export class MatYearView<D> implements AfterContentInit {
|
||||
get activeDate(): D { return this._activeDate; }
|
||||
set activeDate(value: D) {
|
||||
let oldActiveDate = this._activeDate;
|
||||
this._activeDate =
|
||||
const validDate =
|
||||
this._getValidDateOrNull(this._dateAdapter.deserialize(value)) || this._dateAdapter.today();
|
||||
if (this._dateAdapter.getYear(oldActiveDate) != this._dateAdapter.getYear(this._activeDate)) {
|
||||
this._activeDate = this._dateAdapter.clampDate(validDate, this.minDate, this.maxDate);
|
||||
if (this._dateAdapter.getYear(oldActiveDate) !== this._dateAdapter.getYear(this._activeDate)) {
|
||||
this._init();
|
||||
}
|
||||
}
|
||||
@@ -84,6 +97,9 @@ export class MatYearView<D> implements AfterContentInit {
|
||||
/** Emits the selected month. This doesn't imply a change on the selected date */
|
||||
@Output() readonly monthSelected: EventEmitter<D> = new EventEmitter<D>();
|
||||
|
||||
/** The body of calendar table */
|
||||
@ViewChild(MatCalendarBody) _matCalendarBody;
|
||||
|
||||
/** Grid of calendar cells representing the months of the year. */
|
||||
_months: MatCalendarCell[][];
|
||||
|
||||
@@ -99,9 +115,10 @@ export class MatYearView<D> implements AfterContentInit {
|
||||
*/
|
||||
_selectedMonth: number | null;
|
||||
|
||||
constructor(@Optional() public _dateAdapter: DateAdapter<D>,
|
||||
constructor(private _changeDetectorRef: ChangeDetectorRef,
|
||||
@Optional() @Inject(MAT_DATE_FORMATS) private _dateFormats: MatDateFormats,
|
||||
private _changeDetectorRef: ChangeDetectorRef) {
|
||||
@Optional() public _dateAdapter: DateAdapter<D>,
|
||||
@Optional() private _dir?: Directionality) {
|
||||
if (!this._dateAdapter) {
|
||||
throw createMissingDateImplError('DateAdapter');
|
||||
}
|
||||
@@ -114,6 +131,7 @@ export class MatYearView<D> implements AfterContentInit {
|
||||
|
||||
ngAfterContentInit() {
|
||||
this._init();
|
||||
this._focusActiveCell();
|
||||
}
|
||||
|
||||
/** Handles when a new month is selected. */
|
||||
@@ -130,6 +148,56 @@ export class MatYearView<D> implements AfterContentInit {
|
||||
Math.min(this._dateAdapter.getDate(this.activeDate), daysInMonth)));
|
||||
}
|
||||
|
||||
/** Handles keydown events on the calendar body when calendar is in year view. */
|
||||
_handleCalendarBodyKeydown(event: KeyboardEvent): void {
|
||||
// TODO(mmalerba): We currently allow keyboard navigation to disabled dates, but just prevent
|
||||
// disabled ones from being selected. This may not be ideal, we should look into whether
|
||||
// navigation should skip over disabled dates, and if so, how to implement that efficiently.
|
||||
|
||||
const isRtl = this._isRtl();
|
||||
|
||||
switch (event.keyCode) {
|
||||
case LEFT_ARROW:
|
||||
this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, isRtl ? 1 : -1);
|
||||
break;
|
||||
case RIGHT_ARROW:
|
||||
this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, isRtl ? -1 : 1);
|
||||
break;
|
||||
case UP_ARROW:
|
||||
this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, -4);
|
||||
break;
|
||||
case DOWN_ARROW:
|
||||
this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, 4);
|
||||
break;
|
||||
case HOME:
|
||||
this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate,
|
||||
-this._dateAdapter.getMonth(this._activeDate));
|
||||
break;
|
||||
case END:
|
||||
this.activeDate = this._dateAdapter.addCalendarMonths(this._activeDate,
|
||||
11 - this._dateAdapter.getMonth(this._activeDate));
|
||||
break;
|
||||
case PAGE_UP:
|
||||
this.activeDate =
|
||||
this._dateAdapter.addCalendarYears(this._activeDate, event.altKey ? -10 : -1);
|
||||
break;
|
||||
case PAGE_DOWN:
|
||||
this.activeDate =
|
||||
this._dateAdapter.addCalendarYears(this._activeDate, event.altKey ? 10 : 1);
|
||||
break;
|
||||
case ENTER:
|
||||
this._monthSelected(this._dateAdapter.getMonth(this._activeDate));
|
||||
break;
|
||||
default:
|
||||
// Don't prevent default or focus active cell on keys that we don't explicitly handle.
|
||||
return;
|
||||
}
|
||||
|
||||
this._focusActiveCell();
|
||||
// Prevent unexpected default actions such as form submission.
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
/** Initializes this year view. */
|
||||
_init() {
|
||||
this._selectedMonth = this._getMonthInCurrentYear(this.selected);
|
||||
@@ -143,6 +211,11 @@ export class MatYearView<D> implements AfterContentInit {
|
||||
this._changeDetectorRef.markForCheck();
|
||||
}
|
||||
|
||||
/** Focuses the active cell after the microtask queue is empty. */
|
||||
private _focusActiveCell() {
|
||||
this._matCalendarBody._focusActiveCell();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the month in this year that the given Date falls on.
|
||||
* Returns null if the given Date is in another year.
|
||||
@@ -226,4 +299,9 @@ export class MatYearView<D> implements AfterContentInit {
|
||||
private _getValidDateOrNull(obj: any): D | null {
|
||||
return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null;
|
||||
}
|
||||
|
||||
/** Determines whether the user has the RTL layout direction. */
|
||||
private _isRtl() {
|
||||
return this._dir && this._dir.value === 'rtl';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user