mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(datepicker): add year selection mode (#8565)
* feat(datepicker): add multi-year view. * fix broken tests * add calendar tests for multi-year * add tests for multi year view * update docs * address comments * address comments
This commit is contained in:
@@ -39,6 +39,8 @@ export class MatCalendarCell {
|
||||
styleUrls: ['calendar-body.css'],
|
||||
host: {
|
||||
'class': 'mat-calendar-body',
|
||||
'role': 'grid',
|
||||
'attr.aria-readonly': 'true'
|
||||
},
|
||||
exportAs: 'matCalendarBody',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<button mat-button class="mat-calendar-period-button"
|
||||
(click)="_currentPeriodClicked()" [attr.aria-label]="_periodButtonLabel">
|
||||
{{_periodButtonText}}
|
||||
<div class="mat-calendar-arrow" [class.mat-calendar-invert]="!_monthView"></div>
|
||||
<div class="mat-calendar-arrow" [class.mat-calendar-invert]="_currentView != 'month'"></div>
|
||||
</button>
|
||||
|
||||
<div class="mat-calendar-spacer"></div>
|
||||
@@ -21,9 +21,9 @@
|
||||
</div>
|
||||
|
||||
<div class="mat-calendar-content" (keydown)="_handleCalendarBodyKeydown($event)"
|
||||
[ngSwitch]="_monthView" cdkMonitorSubtreeFocus>
|
||||
[ngSwitch]="_currentView" cdkMonitorSubtreeFocus>
|
||||
<mat-month-view
|
||||
*ngSwitchCase="true"
|
||||
*ngSwitchCase="'month'"
|
||||
[activeDate]="_activeDate"
|
||||
[selected]="selected"
|
||||
[dateFilter]="_dateFilterForViews"
|
||||
@@ -32,10 +32,18 @@
|
||||
</mat-month-view>
|
||||
|
||||
<mat-year-view
|
||||
*ngSwitchDefault
|
||||
*ngSwitchCase="'year'"
|
||||
[activeDate]="_activeDate"
|
||||
[selected]="selected"
|
||||
[dateFilter]="_dateFilterForViews"
|
||||
(selectedChange)="_monthSelected($event)">
|
||||
(selectedChange)="_goToDateInView($event, 'month')">
|
||||
</mat-year-view>
|
||||
|
||||
<mat-multi-year-view
|
||||
*ngSwitchCase="'multi-year'"
|
||||
[activeDate]="_activeDate"
|
||||
[selected]="selected"
|
||||
[dateFilter]="_dateFilterForViews"
|
||||
(selectedChange)="_goToDateInView($event, 'year')">
|
||||
</mat-multi-year-view>
|
||||
</div>
|
||||
|
||||
@@ -32,6 +32,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 {MatYearView} from './year-view';
|
||||
|
||||
|
||||
@@ -47,6 +48,7 @@ describe('MatCalendar', () => {
|
||||
MatCalendarBody,
|
||||
MatMonthView,
|
||||
MatYearView,
|
||||
MatMultiYearView,
|
||||
|
||||
// Test components.
|
||||
StandardCalendar,
|
||||
@@ -85,22 +87,22 @@ describe('MatCalendar', () => {
|
||||
});
|
||||
|
||||
it('should be in month view with specified month active', () => {
|
||||
expect(calendarInstance._monthView).toBe(true, 'should be in month view');
|
||||
expect(calendarInstance._currentView).toBe('month');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
});
|
||||
|
||||
it('should toggle view when period clicked', () => {
|
||||
expect(calendarInstance._monthView).toBe(true, 'should be in month view');
|
||||
expect(calendarInstance._currentView).toBe('month');
|
||||
|
||||
periodButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._monthView).toBe(false, 'should be in year view');
|
||||
expect(calendarInstance._currentView).toBe('multi-year');
|
||||
|
||||
periodButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._monthView).toBe(true, 'should be in month view');
|
||||
expect(calendarInstance._currentView).toBe('month');
|
||||
});
|
||||
|
||||
it('should go to next and previous month', () => {
|
||||
@@ -121,9 +123,14 @@ describe('MatCalendar', () => {
|
||||
periodButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._monthView).toBe(false, 'should be in year view');
|
||||
expect(calendarInstance._currentView).toBe('multi-year');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
|
||||
(calendarElement.querySelector('.mat-calendar-body-active') as HTMLElement).click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('year');
|
||||
|
||||
nextButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -135,19 +142,44 @@ describe('MatCalendar', () => {
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
});
|
||||
|
||||
it('should go back to month view after selecting month in year view', () => {
|
||||
it('should go to previous and next multi-year range', () => {
|
||||
periodButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._monthView).toBe(false, 'should be in year view');
|
||||
expect(calendarInstance._currentView).toBe('multi-year');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
|
||||
nextButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017 + yearsPerPage, JAN, 31));
|
||||
|
||||
prevButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
});
|
||||
|
||||
it('should go back to month view after selecting year and month', () => {
|
||||
periodButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('multi-year');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 31));
|
||||
|
||||
let yearCells = calendarElement.querySelectorAll('.mat-calendar-body-cell');
|
||||
(yearCells[0] as HTMLElement).click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('year');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2016, JAN, 31));
|
||||
|
||||
let monthCells = calendarElement.querySelectorAll('.mat-calendar-body-cell');
|
||||
(monthCells[monthCells.length - 1] as HTMLElement).click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._monthView).toBe(true, 'should be in month view');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, DEC, 31));
|
||||
expect(calendarInstance._currentView).toBe('month');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2016, DEC, 31));
|
||||
expect(testComponent.selected).toBeFalsy('no date should be selected yet');
|
||||
});
|
||||
|
||||
@@ -156,7 +188,7 @@ describe('MatCalendar', () => {
|
||||
(monthCells[monthCells.length - 1] as HTMLElement).click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._monthView).toBe(true, 'should be in month view');
|
||||
expect(calendarInstance._currentView).toBe('month');
|
||||
expect(testComponent.selected).toEqual(new Date(2017, JAN, 31));
|
||||
});
|
||||
|
||||
@@ -165,11 +197,11 @@ describe('MatCalendar', () => {
|
||||
const button = fixture.debugElement.nativeElement
|
||||
.querySelector('.mat-calendar-period-button');
|
||||
|
||||
intl.switchToYearViewLabel = 'Go to year view?';
|
||||
intl.switchToMultiYearViewLabel = 'Go to multi-year view?';
|
||||
intl.changes.next();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(button.getAttribute('aria-label')).toBe('Go to year view?');
|
||||
expect(button.getAttribute('aria-label')).toBe('Go to multi-year view?');
|
||||
}));
|
||||
|
||||
describe('a11y', () => {
|
||||
@@ -311,7 +343,12 @@ describe('MatCalendar', () => {
|
||||
dispatchMouseEvent(periodButton, 'click');
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._monthView).toBe(false);
|
||||
expect(calendarInstance._currentView).toBe('multi-year');
|
||||
|
||||
(calendarBodyEl.querySelector('.mat-calendar-body-active') as HTMLElement).click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('year');
|
||||
});
|
||||
|
||||
it('should decrement month on left arrow press', () => {
|
||||
@@ -448,11 +485,130 @@ describe('MatCalendar', () => {
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', ENTER);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._monthView).toBe(true);
|
||||
expect(calendarInstance._currentView).toBe('month');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, FEB, 28));
|
||||
expect(testComponent.selected).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('multi-year view', () => {
|
||||
beforeEach(() => {
|
||||
dispatchMouseEvent(periodButton, 'click');
|
||||
fixture.detectChanges();
|
||||
|
||||
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);
|
||||
fixture.detectChanges();
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', ENTER);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._currentView).toBe('year');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2018, JAN, 31));
|
||||
expect(testComponent.selected).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -557,6 +713,9 @@ describe('MatCalendar', () => {
|
||||
periodButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
(calendarElement.querySelector('.mat-calendar-body-active') as HTMLElement).click();
|
||||
fixture.detectChanges();
|
||||
|
||||
spyOn(calendarInstance.yearView, '_init').and.callThrough();
|
||||
|
||||
testComponent.minDate = new Date(2017, NOV, 1);
|
||||
@@ -572,6 +731,9 @@ describe('MatCalendar', () => {
|
||||
periodButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
(calendarElement.querySelector('.mat-calendar-body-active') as HTMLElement).click();
|
||||
fixture.detectChanges();
|
||||
|
||||
spyOn(calendarInstance.yearView, '_init').and.callThrough();
|
||||
|
||||
testComponent.maxDate = new Date(2017, DEC, 1);
|
||||
@@ -580,6 +742,35 @@ describe('MatCalendar', () => {
|
||||
expect(calendarInstance.yearView._init).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should re-render the multi-year view when the minDate changes', () => {
|
||||
fixture.detectChanges();
|
||||
const periodButton =
|
||||
calendarElement.querySelector('.mat-calendar-period-button') as HTMLElement;
|
||||
periodButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
spyOn(calendarInstance.multiYearView, '_init').and.callThrough();
|
||||
|
||||
testComponent.minDate = new Date(2017, NOV, 1);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.multiYearView._init).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should re-render the multi-year view when the maxDate changes', () => {
|
||||
fixture.detectChanges();
|
||||
const periodButton =
|
||||
calendarElement.querySelector('.mat-calendar-period-button') as HTMLElement;
|
||||
periodButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
spyOn(calendarInstance.multiYearView, '_init').and.callThrough();
|
||||
|
||||
testComponent.maxDate = new Date(2017, DEC, 1);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance.multiYearView._init).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('calendar with date filter', () => {
|
||||
@@ -623,7 +814,7 @@ describe('MatCalendar', () => {
|
||||
});
|
||||
|
||||
it('should not allow selection of disabled date in month view', () => {
|
||||
expect(calendarInstance._monthView).toBe(true);
|
||||
expect(calendarInstance._currentView).toBe('month');
|
||||
expect(calendarInstance._activeDate).toEqual(new Date(2017, JAN, 1));
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', ENTER);
|
||||
@@ -638,15 +829,18 @@ describe('MatCalendar', () => {
|
||||
dispatchMouseEvent(periodButton, 'click');
|
||||
fixture.detectChanges();
|
||||
|
||||
(calendarElement.querySelector('.mat-calendar-body-active') as HTMLElement).click();
|
||||
fixture.detectChanges();
|
||||
|
||||
calendarInstance._activeDate = new Date(2017, NOV, 1);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._monthView).toBe(false);
|
||||
expect(calendarInstance._currentView).toBe('year');
|
||||
|
||||
dispatchKeyboardEvent(calendarBodyEl, 'keydown', ENTER);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(calendarInstance._monthView).toBe(true);
|
||||
expect(calendarInstance._currentView).toBe('month');
|
||||
expect(testComponent.selected).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
+111
-51
@@ -27,13 +27,13 @@ import {
|
||||
Inject,
|
||||
Input,
|
||||
NgZone,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
Optional,
|
||||
Output,
|
||||
ViewEncapsulation,
|
||||
ViewChild,
|
||||
OnChanges,
|
||||
SimpleChanges,
|
||||
ViewChild,
|
||||
ViewEncapsulation,
|
||||
} from '@angular/core';
|
||||
import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '@angular/material/core';
|
||||
import {take} from 'rxjs/operators/take';
|
||||
@@ -41,6 +41,7 @@ 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 {MatYearView} from './year-view';
|
||||
|
||||
|
||||
@@ -73,7 +74,7 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
|
||||
private _startAt: D | null;
|
||||
|
||||
/** Whether the calendar should be started in month or year view. */
|
||||
@Input() startView: 'month' | 'year' = 'month';
|
||||
@Input() startView: 'month' | 'year' | 'multi-year' = 'month';
|
||||
|
||||
/** The currently selected date. */
|
||||
@Input()
|
||||
@@ -114,7 +115,10 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
|
||||
/** Reference to the current year view component. */
|
||||
@ViewChild(MatYearView) yearView: MatYearView<D>;
|
||||
|
||||
/** Date filter for the month and year views. */
|
||||
/** Reference to the current multi-year view component. */
|
||||
@ViewChild(MatMultiYearView) multiYearView: MatMultiYearView<D>;
|
||||
|
||||
/** Date filter for the month, year, and multi-year views. */
|
||||
_dateFilterForViews = (date: D) => {
|
||||
return !!date &&
|
||||
(!this.dateFilter || this.dateFilter(date)) &&
|
||||
@@ -133,28 +137,46 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
|
||||
private _clampedActiveDate: D;
|
||||
|
||||
/** Whether the calendar is in month view. */
|
||||
_monthView: boolean;
|
||||
_currentView: 'month' | 'year' | 'multi-year';
|
||||
|
||||
/** The label for the current calendar view. */
|
||||
get _periodButtonText(): string {
|
||||
return this._monthView ?
|
||||
this._dateAdapter.format(this._activeDate, this._dateFormats.display.monthYearLabel)
|
||||
.toLocaleUpperCase() :
|
||||
this._dateAdapter.getYearName(this._activeDate);
|
||||
if (this._currentView == 'month') {
|
||||
return this._dateAdapter.format(this._activeDate, this._dateFormats.display.monthYearLabel)
|
||||
.toLocaleUpperCase();
|
||||
}
|
||||
if (this._currentView == 'year') {
|
||||
return this._dateAdapter.getYearName(this._activeDate);
|
||||
}
|
||||
const activeYear = this._dateAdapter.getYear(this._activeDate);
|
||||
const firstYearInView = this._dateAdapter.getYearName(
|
||||
this._dateAdapter.createDate(activeYear - activeYear % 24, 0, 1));
|
||||
const lastYearInView = this._dateAdapter.getYearName(
|
||||
this._dateAdapter.createDate(activeYear + yearsPerPage - 1 - activeYear % 24, 0, 1));
|
||||
return `${firstYearInView} \u2013 ${lastYearInView}`;
|
||||
}
|
||||
|
||||
get _periodButtonLabel(): string {
|
||||
return this._monthView ? this._intl.switchToYearViewLabel : this._intl.switchToMonthViewLabel;
|
||||
return this._currentView == 'month' ?
|
||||
this._intl.switchToMultiYearViewLabel : this._intl.switchToMonthViewLabel;
|
||||
}
|
||||
|
||||
/** The label for the the previous button. */
|
||||
get _prevButtonLabel(): string {
|
||||
return this._monthView ? this._intl.prevMonthLabel : this._intl.prevYearLabel;
|
||||
return {
|
||||
'month': this._intl.prevMonthLabel,
|
||||
'year': this._intl.prevYearLabel,
|
||||
'multi-year': this._intl.prevMultiYearLabel
|
||||
}[this._currentView];
|
||||
}
|
||||
|
||||
/** The label for the the next button. */
|
||||
get _nextButtonLabel(): string {
|
||||
return this._monthView ? this._intl.nextMonthLabel : this._intl.nextYearLabel;
|
||||
return {
|
||||
'month': this._intl.nextMonthLabel,
|
||||
'year': this._intl.nextYearLabel,
|
||||
'multi-year': this._intl.nextMultiYearLabel
|
||||
}[this._currentView];
|
||||
}
|
||||
|
||||
constructor(private _elementRef: ElementRef,
|
||||
@@ -178,7 +200,7 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
|
||||
ngAfterContentInit() {
|
||||
this._activeDate = this.startAt || this._dateAdapter.today();
|
||||
this._focusActiveCell();
|
||||
this._monthView = this.startView != 'year';
|
||||
this._currentView = this.startView;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@@ -189,7 +211,7 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
|
||||
const change = changes.minDate || changes.maxDate || changes.dateFilter;
|
||||
|
||||
if (change && !change.firstChange) {
|
||||
const view = this.monthView || this.yearView;
|
||||
const view = this.monthView || this.yearView || this.multiYearView;
|
||||
|
||||
if (view) {
|
||||
view._init();
|
||||
@@ -208,29 +230,31 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
|
||||
this._userSelection.emit();
|
||||
}
|
||||
|
||||
/** Handles month selection in the year view. */
|
||||
_monthSelected(month: D): void {
|
||||
this._activeDate = month;
|
||||
this._monthView = true;
|
||||
/** Handles month selection in the multi-year view. */
|
||||
_goToDateInView(date: D, view: 'month' | 'year' | 'multi-year'): void {
|
||||
this._activeDate = date;
|
||||
this._currentView = view;
|
||||
}
|
||||
|
||||
/** Handles user clicks on the period label. */
|
||||
_currentPeriodClicked(): void {
|
||||
this._monthView = !this._monthView;
|
||||
this._currentView = this._currentView == 'month' ? 'multi-year' : 'month';
|
||||
}
|
||||
|
||||
/** Handles user clicks on the previous button. */
|
||||
_previousClicked(): void {
|
||||
this._activeDate = this._monthView ?
|
||||
this._activeDate = this._currentView == 'month' ?
|
||||
this._dateAdapter.addCalendarMonths(this._activeDate, -1) :
|
||||
this._dateAdapter.addCalendarYears(this._activeDate, -1);
|
||||
this._dateAdapter.addCalendarYears(
|
||||
this._activeDate, this._currentView == 'year' ? -1 : -yearsPerPage);
|
||||
}
|
||||
|
||||
/** Handles user clicks on the next button. */
|
||||
_nextClicked(): void {
|
||||
this._activeDate = this._monthView ?
|
||||
this._activeDate = this._currentView == 'month' ?
|
||||
this._dateAdapter.addCalendarMonths(this._activeDate, 1) :
|
||||
this._dateAdapter.addCalendarYears(this._activeDate, 1);
|
||||
this._dateAdapter.addCalendarYears(
|
||||
this._activeDate, this._currentView == 'year' ? 1 : yearsPerPage);
|
||||
}
|
||||
|
||||
/** Whether the previous period button is enabled. */
|
||||
@@ -251,10 +275,12 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
|
||||
// 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._monthView) {
|
||||
if (this._currentView == 'month') {
|
||||
this._handleCalendarBodyKeydownInMonthView(event);
|
||||
} else {
|
||||
} else if (this._currentView == 'year') {
|
||||
this._handleCalendarBodyKeydownInYearView(event);
|
||||
} else {
|
||||
this._handleCalendarBodyKeydownInMultiYearView(event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,10 +295,16 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
|
||||
|
||||
/** Whether the two dates represent the same view in the current view mode (month or year). */
|
||||
private _isSameView(date1: D, date2: D): boolean {
|
||||
return this._monthView ?
|
||||
this._dateAdapter.getYear(date1) == this._dateAdapter.getYear(date2) &&
|
||||
this._dateAdapter.getMonth(date1) == this._dateAdapter.getMonth(date2) :
|
||||
this._dateAdapter.getYear(date1) == this._dateAdapter.getYear(date2);
|
||||
if (this._currentView == 'month') {
|
||||
return this._dateAdapter.getYear(date1) == this._dateAdapter.getYear(date2) &&
|
||||
this._dateAdapter.getMonth(date1) == this._dateAdapter.getMonth(date2);
|
||||
}
|
||||
if (this._currentView == 'year') {
|
||||
return this._dateAdapter.getYear(date1) == this._dateAdapter.getYear(date2);
|
||||
}
|
||||
// Otherwise we are in 'multi-year' view.
|
||||
return Math.floor(this._dateAdapter.getYear(date1) / yearsPerPage) ==
|
||||
Math.floor(this._dateAdapter.getYear(date2) / yearsPerPage);
|
||||
}
|
||||
|
||||
/** Handles keydown events on the calendar body when calendar is in month view. */
|
||||
@@ -337,10 +369,10 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
|
||||
this._activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, 1);
|
||||
break;
|
||||
case UP_ARROW:
|
||||
this._activeDate = this._prevMonthInSameCol(this._activeDate);
|
||||
this._activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, -4);
|
||||
break;
|
||||
case DOWN_ARROW:
|
||||
this._activeDate = this._nextMonthInSameCol(this._activeDate);
|
||||
this._activeDate = this._dateAdapter.addCalendarMonths(this._activeDate, 4);
|
||||
break;
|
||||
case HOME:
|
||||
this._activeDate = this._dateAdapter.addCalendarMonths(this._activeDate,
|
||||
@@ -359,7 +391,53 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
|
||||
this._dateAdapter.addCalendarYears(this._activeDate, event.altKey ? 10 : 1);
|
||||
break;
|
||||
case ENTER:
|
||||
this._monthSelected(this._activeDate);
|
||||
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.
|
||||
@@ -371,24 +449,6 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the date for the month that comes before the given month in the same column in the
|
||||
* calendar table.
|
||||
*/
|
||||
private _prevMonthInSameCol(date: D): D {
|
||||
// Decrement by 4 since there are 4 months per row.
|
||||
return this._dateAdapter.addCalendarMonths(date, -4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the date for the month that comes after the given month in the same column in the
|
||||
* calendar table.
|
||||
*/
|
||||
private _nextMonthInSameCol(date: D): D {
|
||||
// Increment by 4 since there are 4 months per row.
|
||||
return this._dateAdapter.addCalendarMonths(date, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param obj The object to check.
|
||||
* @returns The given object if it is both a date instance and valid, otherwise null.
|
||||
|
||||
@@ -37,9 +37,15 @@ export class MatDatepickerIntl {
|
||||
/** A label for the next year button (used by screen readers). */
|
||||
nextYearLabel = 'Next year';
|
||||
|
||||
/** A label for the previous multi-year button (used by screen readers). */
|
||||
prevMultiYearLabel = 'Previous 20 years';
|
||||
|
||||
/** A label for the next multi-year button (used by screen readers). */
|
||||
nextMultiYearLabel = 'Next 20 years';
|
||||
|
||||
/** A label for the 'switch to month view' button (used by screen readers). */
|
||||
switchToMonthViewLabel = 'Change to month view';
|
||||
switchToMonthViewLabel = 'Choose date';
|
||||
|
||||
/** A label for the 'switch to year view' button (used by screen readers). */
|
||||
switchToYearViewLabel = 'Change to year view';
|
||||
switchToMultiYearViewLabel = 'Choose month and year';
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import {MatDatepickerInput} from './datepicker-input';
|
||||
import {MatDatepickerIntl} from './datepicker-intl';
|
||||
import {MatDatepickerToggle} from './datepicker-toggle';
|
||||
import {MatMonthView} from './month-view';
|
||||
import {MatMultiYearView} from './multi-year-view';
|
||||
import {MatYearView} from './year-view';
|
||||
|
||||
|
||||
@@ -45,6 +46,7 @@ import {MatYearView} from './year-view';
|
||||
MatDatepickerToggle,
|
||||
MatMonthView,
|
||||
MatYearView,
|
||||
MatMultiYearView,
|
||||
],
|
||||
declarations: [
|
||||
MatCalendar,
|
||||
@@ -55,6 +57,7 @@ import {MatYearView} from './year-view';
|
||||
MatDatepickerToggle,
|
||||
MatMonthView,
|
||||
MatYearView,
|
||||
MatMultiYearView,
|
||||
],
|
||||
providers: [
|
||||
MatDatepickerIntl,
|
||||
|
||||
@@ -34,15 +34,15 @@ can easily be used as a prefix or suffix on the material input:
|
||||
|
||||
### Setting the calendar starting view
|
||||
|
||||
By default the calendar will open in month view, this can be changed by setting the `startView`
|
||||
property of `<mat-datepicker>` to `year`. In year view the user will see all months of the year and
|
||||
then proceed to month view after choosing a month.
|
||||
The `startView` property of `<mat-datepicker>` can be used to set the view that will show up when
|
||||
the calendar first opens. It can be set to `month`, `year`, or `multi-year`; by default it will open
|
||||
to month view.
|
||||
|
||||
The month or year that the calendar opens to is determined by first checking if any date is
|
||||
currently selected, if so it will open to the month or year containing that date. Otherwise it will
|
||||
open to the month or year containing today's date. This behavior can be overridden by using the
|
||||
`startAt` property of `<mat-datepicker>`. In this case the calendar will open to the month or year
|
||||
containing the `startAt` date.
|
||||
The month, year, or range of years that the calendar opens to is determined by first checking if any
|
||||
date is currently selected, if so it will open to the month or year containing that date. Otherwise
|
||||
it will open to the month or year containing today's date. This behavior can be overridden by using
|
||||
the `startAt` property of `<mat-datepicker>`. In this case the calendar will open to the month or
|
||||
year containing the `startAt` date.
|
||||
|
||||
<!-- example(datepicker-start-view) -->
|
||||
|
||||
@@ -275,46 +275,62 @@ should have a placeholder or be given a meaningful label via `aria-label`, `aria
|
||||
|
||||
#### Keyboard shortcuts
|
||||
|
||||
The keyboard shortcuts to handle datepicker are:
|
||||
The datepicker supports the following keyboard shortcuts:
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------------------|-------------------------------------|
|
||||
| `ALT` + `DOWN_ARROW` | Open the calendar pop-up |
|
||||
| `ESCAPE` | Close the calendar pop-up |
|
||||
| Shortcut | Action |
|
||||
|----------------------|-------------------------------------------|
|
||||
| `ALT` + `DOWN_ARROW` | Open the calendar pop-up |
|
||||
| `ESCAPE` | Close the calendar pop-up |
|
||||
|
||||
|
||||
In month view:
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------------------|-------------------------------------|
|
||||
| `LEFT_ARROW` | Go to previous day |
|
||||
| `RIGHT_ARROW` | Go to next day |
|
||||
| `UP_ARROW` | Go to same day in the previous week |
|
||||
| `DOWN_ARROW` | Go to same day in the next week |
|
||||
| `HOME` | Go to the first day of the month |
|
||||
| `END` | Go to the last day of the month |
|
||||
| `PAGE_UP` | Go to previous month |
|
||||
| `ALT` + `PAGE_UP` | Go to previous year |
|
||||
| `PAGE_DOWN` | Go to next month |
|
||||
| `ALT` + `PAGE_DOWN` | Go to next year |
|
||||
| `ENTER` | Select current date |
|
||||
| Shortcut | Action |
|
||||
|----------------------|-------------------------------------------|
|
||||
| `LEFT_ARROW` | Go to previous day |
|
||||
| `RIGHT_ARROW` | Go to next day |
|
||||
| `UP_ARROW` | Go to same day in the previous week |
|
||||
| `DOWN_ARROW` | Go to same day in the next week |
|
||||
| `HOME` | Go to the first day of the month |
|
||||
| `END` | Go to the last day of the month |
|
||||
| `PAGE_UP` | Go to the same day in the previous month |
|
||||
| `ALT` + `PAGE_UP` | Go to the same day in the previous year |
|
||||
| `PAGE_DOWN` | Go to the same day in the next month |
|
||||
| `ALT` + `PAGE_DOWN` | Go to the same day in the next year |
|
||||
| `ENTER` | Select current date |
|
||||
|
||||
|
||||
In year view:
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------------------|-------------------------------------|
|
||||
| `LEFT_ARROW` | Go to previous month |
|
||||
| `RIGHT_ARROW` | Go to next month |
|
||||
| `UP_ARROW` | Go to previous 6 months |
|
||||
| `DOWN_ARROW` | Go to next 6 months |
|
||||
| `HOME` | Go to the first month of the year |
|
||||
| `END` | Go to the last month of the year |
|
||||
| `PAGE_UP` | Go to previous year |
|
||||
| `ALT` + `PAGE_UP` | Go to previous 10 years |
|
||||
| `PAGE_DOWN` | Go to next year |
|
||||
| `ALT` + `PAGE_DOWN` | Go to next 10 years |
|
||||
| `ENTER` | Select current month |
|
||||
| Shortcut | Action |
|
||||
|----------------------|-------------------------------------------|
|
||||
| `LEFT_ARROW` | Go to previous month |
|
||||
| `RIGHT_ARROW` | Go to next month |
|
||||
| `UP_ARROW` | Go up a row (back 4 months) |
|
||||
| `DOWN_ARROW` | Go down a row (forward 4 months) |
|
||||
| `HOME` | Go to the first month of the year |
|
||||
| `END` | Go to the last month of the year |
|
||||
| `PAGE_UP` | Go to the same month in the previous year |
|
||||
| `ALT` + `PAGE_UP` | Go to the same month 10 years back |
|
||||
| `PAGE_DOWN` | Go to the same month in the next year |
|
||||
| `ALT` + `PAGE_DOWN` | Go to the same month 10 years forward |
|
||||
| `ENTER` | Select current month |
|
||||
|
||||
In multi-year view:
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------------------|-------------------------------------------|
|
||||
| `LEFT_ARROW` | Go to previous year |
|
||||
| `RIGHT_ARROW` | Go to next year |
|
||||
| `UP_ARROW` | Go up a row (back 4 years) |
|
||||
| `DOWN_ARROW` | Go down a row (forward 4 years) |
|
||||
| `HOME` | Go to the first year in the current range |
|
||||
| `END` | Go to the last year in the current range |
|
||||
| `PAGE_UP` | Go back 24 years |
|
||||
| `ALT` + `PAGE_UP` | Go back 240 years |
|
||||
| `PAGE_DOWN` | Go forward 24 years |
|
||||
| `ALT` + `PAGE_DOWN` | Go forward 240 years |
|
||||
| `ENTER` | Select current year |
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
<tr><th class="mat-calendar-table-header-divider" colspan="7" aria-hidden="true"></th></tr>
|
||||
</thead>
|
||||
<tbody mat-calendar-body
|
||||
role="grid"
|
||||
[label]="_monthLabel"
|
||||
[rows]="_weeks"
|
||||
[todayValue]="_todayDate"
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<table class="mat-calendar-table">
|
||||
<thead class="mat-calendar-table-header">
|
||||
<tr><th class="mat-calendar-table-header-divider" colspan="4"></th></tr>
|
||||
</thead>
|
||||
<tbody mat-calendar-body
|
||||
allowDisabledSelection="true"
|
||||
[rows]="_years"
|
||||
[todayValue]="_todayYear"
|
||||
[selectedValue]="_selectedYear"
|
||||
[numCols]="4"
|
||||
[cellAspectRatio]="4 / 7"
|
||||
[activeCell]="_getActiveCell()"
|
||||
(selectedValueChange)="_yearSelected($event)">
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -0,0 +1,86 @@
|
||||
import {Component, ViewChild} from '@angular/core';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {JAN, MatNativeDateModule} from '@angular/material/core';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {MatCalendarBody} from './calendar-body';
|
||||
import {MatMultiYearView, yearsPerPage} from './multi-year-view';
|
||||
import {MatYearView} from './year-view';
|
||||
|
||||
describe('MatMultiYearView', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MatNativeDateModule,
|
||||
],
|
||||
declarations: [
|
||||
MatCalendarBody,
|
||||
MatMultiYearView,
|
||||
|
||||
// Test components.
|
||||
StandardMultiYearView,
|
||||
],
|
||||
});
|
||||
|
||||
TestBed.compileComponents();
|
||||
});
|
||||
|
||||
describe('standard multi-year view', () => {
|
||||
let fixture: ComponentFixture<StandardMultiYearView>;
|
||||
let testComponent: StandardMultiYearView;
|
||||
let multiYearViewNativeElement: Element;
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(StandardMultiYearView);
|
||||
fixture.detectChanges();
|
||||
|
||||
let multiYearViewDebugElement = fixture.debugElement.query(By.directive(MatMultiYearView));
|
||||
multiYearViewNativeElement = multiYearViewDebugElement.nativeElement;
|
||||
testComponent = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('has correct number of years', () => {
|
||||
let cellEls = multiYearViewNativeElement.querySelectorAll('.mat-calendar-body-cell')!;
|
||||
expect(cellEls.length).toBe(yearsPerPage);
|
||||
});
|
||||
|
||||
it('shows selected year if in same range', () => {
|
||||
let selectedEl = multiYearViewNativeElement.querySelector('.mat-calendar-body-selected')!;
|
||||
expect(selectedEl.innerHTML.trim()).toBe('2020');
|
||||
});
|
||||
|
||||
it('does not show selected year if in different range', () => {
|
||||
testComponent.selected = new Date(2040, JAN, 10);
|
||||
fixture.detectChanges();
|
||||
|
||||
let selectedEl = multiYearViewNativeElement.querySelector('.mat-calendar-body-selected');
|
||||
expect(selectedEl).toBeNull();
|
||||
});
|
||||
|
||||
it('fires selected change event on cell clicked', () => {
|
||||
let cellEls = multiYearViewNativeElement.querySelectorAll('.mat-calendar-body-cell');
|
||||
(cellEls[cellEls.length - 1] as HTMLElement).click();
|
||||
fixture.detectChanges();
|
||||
|
||||
let selectedEl = multiYearViewNativeElement.querySelector('.mat-calendar-body-selected')!;
|
||||
expect(selectedEl.innerHTML.trim()).toBe('2039');
|
||||
});
|
||||
|
||||
it('should mark active date', () => {
|
||||
let cellEls = multiYearViewNativeElement.querySelectorAll('.mat-calendar-body-cell');
|
||||
expect((cellEls[1] as HTMLElement).innerText.trim()).toBe('2017');
|
||||
expect(cellEls[1].classList).toContain('mat-calendar-body-active');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<mat-multi-year-view [activeDate]="date" [(selected)]="selected"></mat-multi-year-view>`,
|
||||
})
|
||||
class StandardMultiYearView {
|
||||
date = new Date(2017, JAN, 1);
|
||||
selected = new Date(2020, JAN, 1);
|
||||
|
||||
@ViewChild(MatYearView) yearView: MatYearView<Date>;
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {
|
||||
AfterContentInit,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
Optional,
|
||||
Output,
|
||||
ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
import {DateAdapter} from '@angular/material/core';
|
||||
import {MatCalendarCell} from './calendar-body';
|
||||
import {createMissingDateImplError} from './datepicker-errors';
|
||||
|
||||
|
||||
export const yearsPerPage = 24;
|
||||
|
||||
export const yearsPerRow = 4;
|
||||
|
||||
|
||||
/**
|
||||
* An internal component used to display a year selector in the datepicker.
|
||||
* @docs-private
|
||||
*/
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'mat-multi-year-view',
|
||||
templateUrl: 'multi-year-view.html',
|
||||
exportAs: 'matMultiYearView',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
preserveWhitespaces: false,
|
||||
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). */
|
||||
@Input()
|
||||
get activeDate(): D { return this._activeDate; }
|
||||
set activeDate(value: D) {
|
||||
let oldActiveDate = this._activeDate;
|
||||
this._activeDate =
|
||||
this._getValidDateOrNull(this._dateAdapter.deserialize(value)) || this._dateAdapter.today();
|
||||
if (Math.floor(this._dateAdapter.getYear(oldActiveDate) / yearsPerPage) !=
|
||||
Math.floor(this._dateAdapter.getYear(this._activeDate) / yearsPerPage)) {
|
||||
this._init();
|
||||
}
|
||||
}
|
||||
private _activeDate: D;
|
||||
|
||||
/** The currently selected date. */
|
||||
@Input()
|
||||
get selected(): D | null { return this._selected; }
|
||||
set selected(value: D | null) {
|
||||
this._selected = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
|
||||
this._selectedYear = this._selected && this._dateAdapter.getYear(this._selected);
|
||||
}
|
||||
private _selected: D | null;
|
||||
|
||||
/** A function used to filter which dates are selectable. */
|
||||
@Input() dateFilter: (date: D) => boolean;
|
||||
|
||||
/** Emits when a new month is selected. */
|
||||
@Output() selectedChange = new EventEmitter<D>();
|
||||
|
||||
/** Grid of calendar cells representing the currently displayed years. */
|
||||
_years: MatCalendarCell[][];
|
||||
|
||||
/** The year that today falls on. */
|
||||
_todayYear: number;
|
||||
|
||||
/** 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) {
|
||||
if (!this._dateAdapter) {
|
||||
throw createMissingDateImplError('DateAdapter');
|
||||
}
|
||||
|
||||
this._activeDate = this._dateAdapter.today();
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
/** Initializes this multi-year view. */
|
||||
_init() {
|
||||
this._todayYear = this._dateAdapter.getYear(this._dateAdapter.today());
|
||||
let activeYear = this._dateAdapter.getYear(this._activeDate);
|
||||
let activeOffset = activeYear % yearsPerPage;
|
||||
this._years = [];
|
||||
for (let i = 0, row: number[] = []; i < yearsPerPage; i++) {
|
||||
row.push(activeYear - activeOffset + i);
|
||||
if (row.length == yearsPerRow) {
|
||||
this._years.push(row.map(year => this._createCellForYear(year)));
|
||||
row = [];
|
||||
}
|
||||
}
|
||||
this._changeDetectorRef.markForCheck();
|
||||
}
|
||||
|
||||
/** Handles when a new year is selected. */
|
||||
_yearSelected(year: number) {
|
||||
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)));
|
||||
}
|
||||
|
||||
_getActiveCell(): number {
|
||||
return this._dateAdapter.getYear(this.activeDate) % yearsPerPage;
|
||||
}
|
||||
|
||||
/** Creates an MatCalendarCell for the given year. */
|
||||
private _createCellForYear(year: number) {
|
||||
let yearName = this._dateAdapter.getYearName(this._dateAdapter.createDate(year, 0, 1));
|
||||
return new MatCalendarCell(year, yearName, yearName, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param obj The object to check.
|
||||
* @returns The given object if it is both a date instance and valid, otherwise null.
|
||||
*/
|
||||
private _getValidDateOrNull(obj: any): D | null {
|
||||
return (this._dateAdapter.isDateInstance(obj) && this._dateAdapter.isValid(obj)) ? obj : null;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
<tr><th class="mat-calendar-table-header-divider" colspan="4"></th></tr>
|
||||
</thead>
|
||||
<tbody mat-calendar-body
|
||||
role="grid"
|
||||
allowDisabledSelection="true"
|
||||
[label]="_yearLabel"
|
||||
[rows]="_months"
|
||||
|
||||
@@ -106,7 +106,7 @@ export class MatYearView<D> implements AfterContentInit {
|
||||
Math.min(this._dateAdapter.getDate(this.activeDate), daysInMonth)));
|
||||
}
|
||||
|
||||
/** Initializes this month view. */
|
||||
/** Initializes this year view. */
|
||||
_init() {
|
||||
this._selectedMonth = this._getMonthInCurrentYear(this.selected);
|
||||
this._todayMonth = this._getMonthInCurrentYear(this._dateAdapter.today());
|
||||
|
||||
Reference in New Issue
Block a user