diff --git a/src/lib/datepicker/calendar.html b/src/lib/datepicker/calendar.html index 7b06f3296eab..5fab7ec8735e 100644 --- a/src/lib/datepicker/calendar.html +++ b/src/lib/datepicker/calendar.html @@ -26,7 +26,9 @@ *ngSwitchCase="'month'" [activeDate]="_activeDate" [selected]="selected" - [dateFilter]="_dateFilterForViews" + [dateFilter]="dateFilter" + [maxDate]="maxDate" + [minDate]="minDate" (selectedChange)="_dateSelected($event)" (_userSelection)="_userSelected()"> @@ -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')"> @@ -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')"> diff --git a/src/lib/datepicker/calendar.ts b/src/lib/datepicker/calendar.ts index 1cbc49ebce93..644496adc52f 100644 --- a/src/lib/datepicker/calendar.ts +++ b/src/lib/datepicker/calendar.ts @@ -131,14 +131,6 @@ export class MatCalendar implements AfterContentInit, OnDestroy, OnChanges { /** Reference to the current multi-year view component. */ @ViewChild(MatMultiYearView) multiYearView: MatMultiYearView; - /** 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 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. diff --git a/src/lib/datepicker/month-view.ts b/src/lib/datepicker/month-view.ts index eb5ef809b9df..933608feef60 100644 --- a/src/lib/datepicker/month-view.ts +++ b/src/lib/datepicker/month-view.ts @@ -64,6 +64,22 @@ export class MatMonthView 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 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. diff --git a/src/lib/datepicker/multi-year-view.ts b/src/lib/datepicker/multi-year-view.ts index 65ebf88610b9..7935aa481156 100644 --- a/src/lib/datepicker/multi-year-view.ts +++ b/src/lib/datepicker/multi-year-view.ts @@ -64,6 +64,22 @@ export class MatMultiYearView 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 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; } diff --git a/src/lib/datepicker/year-view.ts b/src/lib/datepicker/year-view.ts index 4b948f4ac789..cfa082275d48 100644 --- a/src/lib/datepicker/year-view.ts +++ b/src/lib/datepicker/year-view.ts @@ -59,6 +59,22 @@ export class MatYearView 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 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 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.