Skip to content

Commit c42784e

Browse files
committed
fix(datepicker): handle date ranges across multiple months (#18404)
Currently we don't handle date ranges across months, because we store the date cell's value as the date in the current month. These changes add another value that will be used comparisons and which is based on the time since epoch.
1 parent 3ebc0ac commit c42784e

File tree

4 files changed

+43
-18
lines changed

4 files changed

+43
-18
lines changed

src/material/datepicker/calendar-body.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@
3636
[attr.data-mat-col]="colIndex"
3737
[class.mat-calendar-body-disabled]="!item.enabled"
3838
[class.mat-calendar-body-active]="_isActiveCell(rowIndex, colIndex)"
39-
[class.mat-calendar-body-in-range]="_isInRange(item.value)"
40-
[class.mat-calendar-body-range-start]="item.value === startValue"
41-
[class.mat-calendar-body-range-end]="item.value === endValue || item.value === _hoveredValue"
39+
[class.mat-calendar-body-in-range]="_isInRange(item.compareValue)"
40+
[class.mat-calendar-body-range-start]="item.compareValue === startValue"
41+
[class.mat-calendar-body-range-end]="item.compareValue === endValue || item.compareValue === _hoveredValue"
4242
[attr.aria-label]="item.ariaLabel"
4343
[attr.aria-disabled]="!item.enabled || null"
4444
[attr.aria-selected]="_isSelected(item)"
@@ -49,7 +49,7 @@
4949
[style.paddingBottom]="_cellPadding">
5050
<div class="mat-calendar-body-cell-content"
5151
[class.mat-calendar-body-selected]="_isSelected(item)"
52-
[class.mat-calendar-body-today]="todayValue === item.value">
52+
[class.mat-calendar-body-today]="todayValue === item.compareValue">
5353
{{item.displayValue}}
5454
</div>
5555
</td>

src/material/datepicker/calendar-body.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ export class MatCalendarCell {
3636
public displayValue: string,
3737
public ariaLabel: string,
3838
public enabled: boolean,
39-
public cssClasses: MatCalendarCellCssClasses = {}) {}
39+
public cssClasses: MatCalendarCellCssClasses = {},
40+
public compareValue = value) {}
4041
}
4142

4243

@@ -67,11 +68,10 @@ export class MatCalendarBody implements OnChanges, OnDestroy {
6768
/** The value in the table that corresponds to today. */
6869
@Input() todayValue: number;
6970

70-
/** The value in the table that is currently selected. */
71-
// @Input() selectedValue: number;
72-
71+
/** Start value of the selected date range. */
7372
@Input() startValue: number;
7473

74+
/** End value of the selected date range. */
7575
@Input() endValue: number;
7676

7777
/** The minimum number of free cells needed to fit the label in the first row. */
@@ -126,7 +126,7 @@ export class MatCalendarBody implements OnChanges, OnDestroy {
126126

127127
/** Returns whether a cell should be marked as selected. */
128128
_isSelected(cell: MatCalendarCell) {
129-
return this.startValue === cell.value || this.endValue === cell.value;
129+
return this.startValue === cell.compareValue || this.endValue === cell.compareValue;
130130
}
131131

132132
ngOnChanges(changes: SimpleChanges) {
@@ -209,7 +209,7 @@ export class MatCalendarBody implements OnChanges, OnDestroy {
209209

210210
if (cell) {
211211
this._ngZone.run(() => {
212-
this._hoveredValue = cell.value;
212+
this._hoveredValue = cell.compareValue;
213213
this._changeDetectorRef.markForCheck();
214214
});
215215
}

src/material/datepicker/month-view.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,17 @@ export class MatMonthView<D> implements AfterContentInit, OnDestroy {
168168

169169
/** Handles when a new date is selected. */
170170
_dateSelected(date: number) {
171-
if (this._rangeStart !== date || this._rangeEnd !== date) {
171+
let rangeStartDate: number | null;
172+
let rangeEndDate: number | null;
173+
174+
if (this._selected instanceof DateRange) {
175+
rangeStartDate = this._getDateInCurrentMonth(this._selected.start);
176+
rangeEndDate = this._getDateInCurrentMonth(this._selected.end);
177+
} else {
178+
rangeStartDate = rangeEndDate = this._getDateInCurrentMonth(this._selected);
179+
}
180+
181+
if (rangeStartDate !== date || rangeEndDate !== date) {
172182
const selectedYear = this._dateAdapter.getYear(this.activeDate);
173183
const selectedMonth = this._dateAdapter.getMonth(this.activeDate);
174184
const selectedDate = this._dateAdapter.createDate(selectedYear, selectedMonth, date);
@@ -246,7 +256,7 @@ export class MatMonthView<D> implements AfterContentInit, OnDestroy {
246256
/** Initializes this month view. */
247257
_init() {
248258
this._setRange(this.selected);
249-
this._todayDate = this._getDateInCurrentMonth(this._dateAdapter.today());
259+
this._todayDate = this._getCellCompareValue(this._dateAdapter.today());
250260
this._monthLabel =
251261
this._dateAdapter.getMonthNames('short')[this._dateAdapter.getMonth(this.activeDate)]
252262
.toLocaleUpperCase();
@@ -297,8 +307,8 @@ export class MatMonthView<D> implements AfterContentInit, OnDestroy {
297307
const ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.dateA11yLabel);
298308
const cellClasses = this.dateClass ? this.dateClass(date) : undefined;
299309

300-
this._weeks[this._weeks.length - 1]
301-
.push(new MatCalendarCell(i + 1, dateNames[i], ariaLabel, enabled, cellClasses));
310+
this._weeks[this._weeks.length - 1].push(new MatCalendarCell(i + 1, dateNames[i], ariaLabel,
311+
enabled, cellClasses, this._getCellCompareValue(date)!));
302312
}
303313
}
304314

@@ -325,6 +335,20 @@ export class MatMonthView<D> implements AfterContentInit, OnDestroy {
325335
this._dateAdapter.getYear(d1) == this._dateAdapter.getYear(d2));
326336
}
327337

338+
/** Gets the value that will be used to one cell to another. */
339+
private _getCellCompareValue(date: D | null): number | null {
340+
if (date) {
341+
// We use the time since the Unix epoch to compare dates in this view, rather than the
342+
// cell values, because we need to support ranges that span across multiple months/years.
343+
const year = this._dateAdapter.getYear(date);
344+
const month = this._dateAdapter.getMonth(date);
345+
const day = this._dateAdapter.getDate(date);
346+
return new Date(year, month, day).getTime();
347+
}
348+
349+
return null;
350+
}
351+
328352
/**
329353
* @param obj The object to check.
330354
* @returns The given object if it is both a date instance and valid, otherwise null.
@@ -341,10 +365,10 @@ export class MatMonthView<D> implements AfterContentInit, OnDestroy {
341365
/** Sets the current range based on a model value. */
342366
private _setRange(value: DateRange<D> | D | null) {
343367
if (value instanceof DateRange) {
344-
this._rangeStart = this._getDateInCurrentMonth(value.start);
345-
this._rangeEnd = this._getDateInCurrentMonth(value.end);
368+
this._rangeStart = this._getCellCompareValue(value.start);
369+
this._rangeEnd = this._getCellCompareValue(value.end);
346370
} else {
347-
this._rangeStart = this._rangeEnd = this._getDateInCurrentMonth(value);
371+
this._rangeStart = this._rangeEnd = this._getCellCompareValue(value);
348372
}
349373
}
350374
}

tools/public_api_guard/material/datepicker.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,12 @@ export declare class MatCalendarBody implements OnChanges, OnDestroy {
8686

8787
export declare class MatCalendarCell {
8888
ariaLabel: string;
89+
compareValue: number;
8990
cssClasses: MatCalendarCellCssClasses;
9091
displayValue: string;
9192
enabled: boolean;
9293
value: number;
93-
constructor(value: number, displayValue: string, ariaLabel: string, enabled: boolean, cssClasses?: MatCalendarCellCssClasses);
94+
constructor(value: number, displayValue: string, ariaLabel: string, enabled: boolean, cssClasses?: MatCalendarCellCssClasses, compareValue?: number);
9495
}
9596

9697
export declare type MatCalendarCellCssClasses = string | string[] | Set<string> | {

0 commit comments

Comments
 (0)