mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(datepicker): add max/min filter to multi year and year views (#9727)
* add max/min filter to multi year and year views * address @mmalerba's comments * fix and tighten up logic * possibility to have a null filter in month view
This commit is contained in:
@@ -26,7 +26,9 @@
|
||||
*ngSwitchCase="'month'"
|
||||
[activeDate]="_activeDate"
|
||||
[selected]="selected"
|
||||
[dateFilter]="_dateFilterForViews"
|
||||
[dateFilter]="dateFilter"
|
||||
[maxDate]="maxDate"
|
||||
[minDate]="minDate"
|
||||
(selectedChange)="_dateSelected($event)"
|
||||
(_userSelection)="_userSelected()">
|
||||
</mat-month-view>
|
||||
@@ -35,7 +37,9 @@
|
||||
*ngSwitchCase="'year'"
|
||||
[activeDate]="_activeDate"
|
||||
[selected]="selected"
|
||||
[dateFilter]="_dateFilterForViews"
|
||||
[dateFilter]="dateFilter"
|
||||
[maxDate]="maxDate"
|
||||
[minDate]="minDate"
|
||||
(monthSelected)="_monthSelectedInYearView($event)"
|
||||
(selectedChange)="_goToDateInView($event, 'month')">
|
||||
</mat-year-view>
|
||||
@@ -44,7 +48,9 @@
|
||||
*ngSwitchCase="'multi-year'"
|
||||
[activeDate]="_activeDate"
|
||||
[selected]="selected"
|
||||
[dateFilter]="_dateFilterForViews"
|
||||
[dateFilter]="dateFilter"
|
||||
[maxDate]="maxDate"
|
||||
[minDate]="minDate"
|
||||
(yearSelected)="_yearSelectedInMultiYearView($event)"
|
||||
(selectedChange)="_goToDateInView($event, 'year')">
|
||||
</mat-multi-year-view>
|
||||
|
||||
@@ -131,14 +131,6 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
|
||||
/** 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)) &&
|
||||
(!this.minDate || this._dateAdapter.compareDate(date, this.minDate) >= 0) &&
|
||||
(!this.maxDate || this._dateAdapter.compareDate(date, this.maxDate) <= 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* The current active date. This determines which time period is shown and which date is
|
||||
* highlighted when using keyboard navigation.
|
||||
@@ -368,7 +360,7 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
|
||||
this._dateAdapter.addCalendarMonths(this._activeDate, 1);
|
||||
break;
|
||||
case ENTER:
|
||||
if (this._dateFilterForViews(this._activeDate)) {
|
||||
if (!this.dateFilter || this.dateFilter(this._activeDate)) {
|
||||
this._dateSelected(this._activeDate);
|
||||
this._userSelected();
|
||||
// Prevent unexpected default actions such as form submission.
|
||||
|
||||
@@ -64,6 +64,22 @@ export class MatMonthView<D> implements AfterContentInit {
|
||||
}
|
||||
private _selected: D | null;
|
||||
|
||||
/** The minimum selectable date. */
|
||||
@Input()
|
||||
get minDate(): D | null { return this._minDate; }
|
||||
set minDate(value: D | null) {
|
||||
this._minDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
|
||||
}
|
||||
private _minDate: D | null;
|
||||
|
||||
/** The maximum selectable date. */
|
||||
@Input()
|
||||
get maxDate(): D | null { return this._maxDate; }
|
||||
set maxDate(value: D | null) {
|
||||
this._maxDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
|
||||
}
|
||||
private _maxDate: D | null;
|
||||
|
||||
/** A function used to filter which dates are selectable. */
|
||||
@Input() dateFilter: (date: D) => boolean;
|
||||
|
||||
@@ -154,25 +170,32 @@ export class MatMonthView<D> implements AfterContentInit {
|
||||
|
||||
/** Creates MatCalendarCells for the dates in this month. */
|
||||
private _createWeekCells() {
|
||||
let daysInMonth = this._dateAdapter.getNumDaysInMonth(this.activeDate);
|
||||
let dateNames = this._dateAdapter.getDateNames();
|
||||
const daysInMonth = this._dateAdapter.getNumDaysInMonth(this.activeDate);
|
||||
const dateNames = this._dateAdapter.getDateNames();
|
||||
this._weeks = [[]];
|
||||
for (let i = 0, cell = this._firstWeekOffset; i < daysInMonth; i++, cell++) {
|
||||
if (cell == DAYS_PER_WEEK) {
|
||||
this._weeks.push([]);
|
||||
cell = 0;
|
||||
}
|
||||
let date = this._dateAdapter.createDate(
|
||||
this._dateAdapter.getYear(this.activeDate),
|
||||
this._dateAdapter.getMonth(this.activeDate), i + 1);
|
||||
let enabled = !this.dateFilter ||
|
||||
this.dateFilter(date);
|
||||
let ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.dateA11yLabel);
|
||||
const date = this._dateAdapter.createDate(
|
||||
this._dateAdapter.getYear(this.activeDate),
|
||||
this._dateAdapter.getMonth(this.activeDate), i + 1);
|
||||
const enabled = this._shouldEnableDate(date);
|
||||
const ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.dateA11yLabel);
|
||||
this._weeks[this._weeks.length - 1]
|
||||
.push(new MatCalendarCell(i + 1, dateNames[i], ariaLabel, enabled));
|
||||
}
|
||||
}
|
||||
|
||||
/** Date filter for the month */
|
||||
private _shouldEnableDate(date: D): boolean {
|
||||
return !!date &&
|
||||
(!this.dateFilter || this.dateFilter(date)) &&
|
||||
(!this.minDate || this._dateAdapter.compareDate(date, this.minDate) >= 0) &&
|
||||
(!this.maxDate || this._dateAdapter.compareDate(date, this.maxDate) <= 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the date in this month that the given Date falls on.
|
||||
* Returns null if the given Date is in another month.
|
||||
|
||||
@@ -64,6 +64,22 @@ export class MatMultiYearView<D> implements AfterContentInit {
|
||||
}
|
||||
private _selected: D | null;
|
||||
|
||||
/** The minimum selectable date. */
|
||||
@Input()
|
||||
get minDate(): D | null { return this._minDate; }
|
||||
set minDate(value: D | null) {
|
||||
this._minDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
|
||||
}
|
||||
private _minDate: D | null;
|
||||
|
||||
/** The maximum selectable date. */
|
||||
@Input()
|
||||
get maxDate(): D | null { return this._maxDate; }
|
||||
set maxDate(value: D | null) {
|
||||
this._maxDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
|
||||
}
|
||||
private _maxDate: D | null;
|
||||
|
||||
/** A function used to filter which dates are selectable. */
|
||||
@Input() dateFilter: (date: D) => boolean;
|
||||
|
||||
@@ -128,11 +144,19 @@ export class MatMultiYearView<D> implements AfterContentInit {
|
||||
/** 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, this._isYearEnabled(year));
|
||||
return new MatCalendarCell(year, yearName, yearName, this._shouldEnableYear(year));
|
||||
}
|
||||
|
||||
/** Whether the given year is enabled. */
|
||||
private _isYearEnabled(year: number) {
|
||||
private _shouldEnableYear(year: number) {
|
||||
// disable if the year is greater than maxDate lower than minDate
|
||||
if (year === undefined || year === null ||
|
||||
(this.maxDate && year > this._dateAdapter.getYear(this.maxDate)) ||
|
||||
(this.minDate && year < this._dateAdapter.getYear(this.minDate))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// enable if it reaches here and there's no filter defined
|
||||
if (!this.dateFilter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -59,6 +59,22 @@ export class MatYearView<D> implements AfterContentInit {
|
||||
}
|
||||
private _selected: D | null;
|
||||
|
||||
/** The minimum selectable date. */
|
||||
@Input()
|
||||
get minDate(): D | null { return this._minDate; }
|
||||
set minDate(value: D | null) {
|
||||
this._minDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
|
||||
}
|
||||
private _minDate: D | null;
|
||||
|
||||
/** The maximum selectable date. */
|
||||
@Input()
|
||||
get maxDate(): D | null { return this._maxDate; }
|
||||
set maxDate(value: D | null) {
|
||||
this._maxDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
|
||||
}
|
||||
private _maxDate: D | null;
|
||||
|
||||
/** A function used to filter which dates are selectable. */
|
||||
@Input() dateFilter: (date: D) => boolean;
|
||||
|
||||
@@ -142,17 +158,25 @@ export class MatYearView<D> implements AfterContentInit {
|
||||
this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate), month, 1),
|
||||
this._dateFormats.display.monthYearA11yLabel);
|
||||
return new MatCalendarCell(
|
||||
month, monthName.toLocaleUpperCase(), ariaLabel, this._isMonthEnabled(month));
|
||||
month, monthName.toLocaleUpperCase(), ariaLabel, this._shouldEnableMonth(month));
|
||||
}
|
||||
|
||||
/** Whether the given month is enabled. */
|
||||
private _isMonthEnabled(month: number) {
|
||||
private _shouldEnableMonth(month: number) {
|
||||
|
||||
const activeYear = this._dateAdapter.getYear(this.activeDate);
|
||||
|
||||
if (month === undefined || month === null ||
|
||||
this._isYearAndMonthAfterMaxDate(activeYear, month) ||
|
||||
this._isYearAndMonthBeforeMinDate(activeYear, month)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.dateFilter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let firstOfMonth = this._dateAdapter.createDate(
|
||||
this._dateAdapter.getYear(this.activeDate), month, 1);
|
||||
const firstOfMonth = this._dateAdapter.createDate(activeYear, month, 1);
|
||||
|
||||
// If any date in the month is enabled count the month as enabled.
|
||||
for (let date = firstOfMonth; this._dateAdapter.getMonth(date) == month;
|
||||
@@ -165,6 +189,36 @@ export class MatYearView<D> implements AfterContentInit {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the combination month/year is after this.maxDate, considering
|
||||
* just the month and year of this.maxDate
|
||||
*/
|
||||
private _isYearAndMonthAfterMaxDate(year: number, month: number) {
|
||||
if (this.maxDate) {
|
||||
const maxYear = this._dateAdapter.getYear(this.maxDate);
|
||||
const maxMonth = this._dateAdapter.getMonth(this.maxDate);
|
||||
|
||||
return year > maxYear || (year === maxYear && month > maxMonth);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the combination month/year is before this.minDate, considering
|
||||
* just the month and year of this.minDate
|
||||
*/
|
||||
private _isYearAndMonthBeforeMinDate(year: number, month: number) {
|
||||
if (this.minDate) {
|
||||
const minYear = this._dateAdapter.getYear(this.minDate);
|
||||
const minMonth = this._dateAdapter.getMonth(this.minDate);
|
||||
|
||||
return year < minYear || (year === minYear && month < minMonth);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param obj The object to check.
|
||||
* @returns The given object if it is both a date instance and valid, otherwise null.
|
||||
|
||||
Reference in New Issue
Block a user