Skip to content

Commit 0793142

Browse files
julianobrasilmmalerba
authored andcommitted
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
1 parent ad230f4 commit 0793142

File tree

5 files changed

+125
-26
lines changed

5 files changed

+125
-26
lines changed

src/lib/datepicker/calendar.html

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
*ngSwitchCase="'month'"
2727
[activeDate]="_activeDate"
2828
[selected]="selected"
29-
[dateFilter]="_dateFilterForViews"
29+
[dateFilter]="dateFilter"
30+
[maxDate]="maxDate"
31+
[minDate]="minDate"
3032
(selectedChange)="_dateSelected($event)"
3133
(_userSelection)="_userSelected()">
3234
</mat-month-view>
@@ -35,15 +37,19 @@
3537
*ngSwitchCase="'year'"
3638
[activeDate]="_activeDate"
3739
[selected]="selected"
38-
[dateFilter]="_dateFilterForViews"
40+
[dateFilter]="dateFilter"
41+
[maxDate]="maxDate"
42+
[minDate]="minDate"
3943
(selectedChange)="_goToDateInView($event, 'month')">
4044
</mat-year-view>
4145

4246
<mat-multi-year-view
4347
*ngSwitchCase="'multi-year'"
4448
[activeDate]="_activeDate"
4549
[selected]="selected"
46-
[dateFilter]="_dateFilterForViews"
50+
[dateFilter]="dateFilter"
51+
[maxDate]="maxDate"
52+
[minDate]="minDate"
4753
(selectedChange)="_goToDateInView($event, 'year')">
4854
</mat-multi-year-view>
4955
</div>

src/lib/datepicker/calendar.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -119,14 +119,6 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
119119
/** Reference to the current multi-year view component. */
120120
@ViewChild(MatMultiYearView) multiYearView: MatMultiYearView<D>;
121121

122-
/** Date filter for the month, year, and multi-year views. */
123-
_dateFilterForViews = (date: D) => {
124-
return !!date &&
125-
(!this.dateFilter || this.dateFilter(date)) &&
126-
(!this.minDate || this._dateAdapter.compareDate(date, this.minDate) >= 0) &&
127-
(!this.maxDate || this._dateAdapter.compareDate(date, this.maxDate) <= 0);
128-
}
129-
130122
/**
131123
* The current active date. This determines which time period is shown and which date is
132124
* highlighted when using keyboard navigation.
@@ -346,7 +338,7 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
346338
this._dateAdapter.addCalendarMonths(this._activeDate, 1);
347339
break;
348340
case ENTER:
349-
if (this._dateFilterForViews(this._activeDate)) {
341+
if (!this.dateFilter || this.dateFilter(this._activeDate)) {
350342
this._dateSelected(this._activeDate);
351343
this._userSelected();
352344
// Prevent unexpected default actions such as form submission.

src/lib/datepicker/month-view.ts

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,22 @@ export class MatMonthView<D> implements AfterContentInit {
6464
}
6565
private _selected: D | null;
6666

67+
/** The minimum selectable date. */
68+
@Input()
69+
get minDate(): D | null { return this._minDate; }
70+
set minDate(value: D | null) {
71+
this._minDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
72+
}
73+
private _minDate: D | null;
74+
75+
/** The maximum selectable date. */
76+
@Input()
77+
get maxDate(): D | null { return this._maxDate; }
78+
set maxDate(value: D | null) {
79+
this._maxDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
80+
}
81+
private _maxDate: D | null;
82+
6783
/** A function used to filter which dates are selectable. */
6884
@Input() dateFilter: (date: D) => boolean;
6985

@@ -154,25 +170,32 @@ export class MatMonthView<D> implements AfterContentInit {
154170

155171
/** Creates MatCalendarCells for the dates in this month. */
156172
private _createWeekCells() {
157-
let daysInMonth = this._dateAdapter.getNumDaysInMonth(this.activeDate);
158-
let dateNames = this._dateAdapter.getDateNames();
173+
const daysInMonth = this._dateAdapter.getNumDaysInMonth(this.activeDate);
174+
const dateNames = this._dateAdapter.getDateNames();
159175
this._weeks = [[]];
160176
for (let i = 0, cell = this._firstWeekOffset; i < daysInMonth; i++, cell++) {
161177
if (cell == DAYS_PER_WEEK) {
162178
this._weeks.push([]);
163179
cell = 0;
164180
}
165-
let date = this._dateAdapter.createDate(
166-
this._dateAdapter.getYear(this.activeDate),
167-
this._dateAdapter.getMonth(this.activeDate), i + 1);
168-
let enabled = !this.dateFilter ||
169-
this.dateFilter(date);
170-
let ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.dateA11yLabel);
181+
const date = this._dateAdapter.createDate(
182+
this._dateAdapter.getYear(this.activeDate),
183+
this._dateAdapter.getMonth(this.activeDate), i + 1);
184+
const enabled = this._shouldEnableDate(date);
185+
const ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.dateA11yLabel);
171186
this._weeks[this._weeks.length - 1]
172187
.push(new MatCalendarCell(i + 1, dateNames[i], ariaLabel, enabled));
173188
}
174189
}
175190

191+
/** Date filter for the month */
192+
private _shouldEnableDate(date: D): boolean {
193+
return !!date &&
194+
(!this.dateFilter || this.dateFilter(date)) &&
195+
(!this.minDate || this._dateAdapter.compareDate(date, this.minDate) >= 0) &&
196+
(!this.maxDate || this._dateAdapter.compareDate(date, this.maxDate) <= 0);
197+
}
198+
176199
/**
177200
* Gets the date in this month that the given Date falls on.
178201
* Returns null if the given Date is in another month.

src/lib/datepicker/multi-year-view.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,22 @@ export class MatMultiYearView<D> implements AfterContentInit {
6464
}
6565
private _selected: D | null;
6666

67+
/** The minimum selectable date. */
68+
@Input()
69+
get minDate(): D | null { return this._minDate; }
70+
set minDate(value: D | null) {
71+
this._minDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
72+
}
73+
private _minDate: D | null;
74+
75+
/** The maximum selectable date. */
76+
@Input()
77+
get maxDate(): D | null { return this._maxDate; }
78+
set maxDate(value: D | null) {
79+
this._maxDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
80+
}
81+
private _maxDate: D | null;
82+
6783
/** A function used to filter which dates are selectable. */
6884
@Input() dateFilter: (date: D) => boolean;
6985

@@ -124,11 +140,19 @@ export class MatMultiYearView<D> implements AfterContentInit {
124140
/** Creates an MatCalendarCell for the given year. */
125141
private _createCellForYear(year: number) {
126142
let yearName = this._dateAdapter.getYearName(this._dateAdapter.createDate(year, 0, 1));
127-
return new MatCalendarCell(year, yearName, yearName, this._isYearEnabled(year));
143+
return new MatCalendarCell(year, yearName, yearName, this._shouldEnableYear(year));
128144
}
129145

130146
/** Whether the given year is enabled. */
131-
private _isYearEnabled(year: number) {
147+
private _shouldEnableYear(year: number) {
148+
// disable if the year is greater than maxDate lower than minDate
149+
if (year === undefined || year === null ||
150+
(this.maxDate && year > this._dateAdapter.getYear(this.maxDate)) ||
151+
(this.minDate && year < this._dateAdapter.getYear(this.minDate))) {
152+
return false;
153+
}
154+
155+
// enable if it reaches here and there's no filter defined
132156
if (!this.dateFilter) {
133157
return true;
134158
}

src/lib/datepicker/year-view.ts

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,22 @@ export class MatYearView<D> implements AfterContentInit {
5959
}
6060
private _selected: D | null;
6161

62+
/** The minimum selectable date. */
63+
@Input()
64+
get minDate(): D | null { return this._minDate; }
65+
set minDate(value: D | null) {
66+
this._minDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
67+
}
68+
private _minDate: D | null;
69+
70+
/** The maximum selectable date. */
71+
@Input()
72+
get maxDate(): D | null { return this._maxDate; }
73+
set maxDate(value: D | null) {
74+
this._maxDate = this._getValidDateOrNull(this._dateAdapter.deserialize(value));
75+
}
76+
private _maxDate: D | null;
77+
6278
/** A function used to filter which dates are selectable. */
6379
@Input() dateFilter: (date: D) => boolean;
6480

@@ -134,17 +150,25 @@ export class MatYearView<D> implements AfterContentInit {
134150
this._dateAdapter.createDate(this._dateAdapter.getYear(this.activeDate), month, 1),
135151
this._dateFormats.display.monthYearA11yLabel);
136152
return new MatCalendarCell(
137-
month, monthName.toLocaleUpperCase(), ariaLabel, this._isMonthEnabled(month));
153+
month, monthName.toLocaleUpperCase(), ariaLabel, this._shouldEnableMonth(month));
138154
}
139155

140156
/** Whether the given month is enabled. */
141-
private _isMonthEnabled(month: number) {
157+
private _shouldEnableMonth(month: number) {
158+
159+
const activeYear = this._dateAdapter.getYear(this.activeDate);
160+
161+
if (month === undefined || month === null ||
162+
this._isYearAndMonthAfterMaxDate(activeYear, month) ||
163+
this._isYearAndMonthBeforeMinDate(activeYear, month)) {
164+
return false;
165+
}
166+
142167
if (!this.dateFilter) {
143168
return true;
144169
}
145170

146-
let firstOfMonth = this._dateAdapter.createDate(
147-
this._dateAdapter.getYear(this.activeDate), month, 1);
171+
const firstOfMonth = this._dateAdapter.createDate(activeYear, month, 1);
148172

149173
// If any date in the month is enabled count the month as enabled.
150174
for (let date = firstOfMonth; this._dateAdapter.getMonth(date) == month;
@@ -157,6 +181,36 @@ export class MatYearView<D> implements AfterContentInit {
157181
return false;
158182
}
159183

184+
/**
185+
* Tests whether the combination month/year is after this.maxDate, considering
186+
* just the month and year of this.maxDate
187+
*/
188+
private _isYearAndMonthAfterMaxDate(year: number, month: number) {
189+
if (this.maxDate) {
190+
const maxYear = this._dateAdapter.getYear(this.maxDate);
191+
const maxMonth = this._dateAdapter.getMonth(this.maxDate);
192+
193+
return year > maxYear || (year === maxYear && month > maxMonth);
194+
}
195+
196+
return false;
197+
}
198+
199+
/**
200+
* Tests whether the combination month/year is before this.minDate, considering
201+
* just the month and year of this.minDate
202+
*/
203+
private _isYearAndMonthBeforeMinDate(year: number, month: number) {
204+
if (this.minDate) {
205+
const minYear = this._dateAdapter.getYear(this.minDate);
206+
const minMonth = this._dateAdapter.getMonth(this.minDate);
207+
208+
return year < minYear || (year === minYear && month < minMonth);
209+
}
210+
211+
return false;
212+
}
213+
160214
/**
161215
* @param obj The object to check.
162216
* @returns The given object if it is both a date instance and valid, otherwise null.

0 commit comments

Comments
 (0)