@@ -89,6 +89,12 @@ export class MatCalendarBody implements OnChanges, OnDestroy {
8989 */
9090 @Input ( ) cellAspectRatio : number = 1 ;
9191
92+ /** Start of the comparison range. */
93+ @Input ( ) comparisonStart : number | null ;
94+
95+ /** End of the comparison range. */
96+ @Input ( ) comparisonEnd : number | null ;
97+
9298 /** Emits when a new value is selected. */
9399 @Output ( ) readonly selectedValueChange : EventEmitter < number > = new EventEmitter < number > ( ) ;
94100
@@ -105,7 +111,7 @@ export class MatCalendarBody implements OnChanges, OnDestroy {
105111 * Value that the user is either currently hovering over or is focusing
106112 * using the keyboard. Only applies when selecting the end of a date range.
107113 */
108- _hoveredValue : number ;
114+ _hoveredValue = - 1 ;
109115
110116 constructor (
111117 private _elementRef : ElementRef < HTMLElement > ,
@@ -189,17 +195,73 @@ export class MatCalendarBody implements OnChanges, OnDestroy {
189195 }
190196
191197 /** Gets whether the calendar is currently selecting a range. */
192- _isRange ( ) : boolean {
198+ _isSelectingRange ( ) : boolean {
193199 return this . startValue !== this . endValue ;
194200 }
195201
202+ /** Gets whether a value is the start of the main range. */
203+ _isRangeStart ( value : number ) {
204+ return value === this . startValue ;
205+ }
206+
207+ /** Gets whether a value is the end of the main range. */
208+ _isRangeEnd ( value : number ) {
209+ return value === this . endValue || ( value === this . _hoveredValue && value >= this . startValue ) ;
210+ }
211+
196212 /** Gets whether a value is within the currently-selected range. */
197213 _isInRange ( value : number ) : boolean {
198- if ( ! this . _isRange ( ) || value < this . startValue ) {
214+ return this . _isSelectingRange ( ) && value >= this . startValue &&
215+ ( value <= this . endValue || value <= this . _hoveredValue ) ;
216+ }
217+
218+ /** Gets whether a value is the start of the comparison range. */
219+ _isComparisonStart ( value : number ) {
220+ return value === this . comparisonStart ;
221+ }
222+
223+ /** Whether the cell is a start bridge cell between the main and comparison ranges. */
224+ _isComparisonBridgeStart ( value : number , rowIndex : number , colIndex : number ) {
225+ if ( ! this . _isComparisonStart ( value ) || this . _isRangeStart ( value ) || ! this . _isInRange ( value ) ) {
226+ return false ;
227+ }
228+
229+ let previousCell : MatCalendarCell | undefined = this . rows [ rowIndex ] [ colIndex - 1 ] ;
230+
231+ if ( ! previousCell ) {
232+ const previousRow = this . rows [ rowIndex - 1 ] ;
233+ previousCell = previousRow && previousRow [ previousRow . length - 1 ] ;
234+ }
235+
236+ return previousCell && ! this . _isRangeEnd ( previousCell . compareValue ) ;
237+ }
238+
239+ /** Whether the cell is an end bridge cell between the main and comparison ranges. */
240+ _isComparisonBridgeEnd ( value : number , rowIndex : number , colIndex : number ) {
241+ if ( ! this . _isComparisonEnd ( value ) || this . _isRangeEnd ( value ) || ! this . _isInRange ( value ) ) {
199242 return false ;
200243 }
201244
202- return value <= this . endValue || value <= this . _hoveredValue ;
245+ let nextCell : MatCalendarCell | undefined = this . rows [ rowIndex ] [ colIndex + 1 ] ;
246+
247+ if ( ! nextCell ) {
248+ const nextRow = this . rows [ rowIndex + 1 ] ;
249+ nextCell = nextRow && nextRow [ 0 ] ;
250+ }
251+
252+ return nextCell && ! this . _isRangeStart ( nextCell . compareValue ) ;
253+ }
254+
255+ /** Gets whether a value is the end of the comparison range. */
256+ _isComparisonEnd ( value : number ) {
257+ return value === this . comparisonEnd ;
258+ }
259+
260+ /** Gets whether a value is within the current comparison range. */
261+ _isInComparisonRange ( value : number ) {
262+ return this . comparisonStart && this . comparisonEnd &&
263+ value >= this . comparisonStart &&
264+ value <= this . comparisonEnd ;
203265 }
204266
205267 /**
@@ -209,18 +271,20 @@ export class MatCalendarBody implements OnChanges, OnDestroy {
209271 private _enterHandler = ( event : Event ) => {
210272 // We only need to hit the zone when we're selecting a range, we
211273 // have a start value without an end value and we've hovered over a date cell.
212- if ( ! event . target || ! this . startValue || this . endValue || ! this . _isRange ( ) ) {
274+ if ( ! event . target || ! this . startValue || this . endValue || ! this . _isSelectingRange ( ) ) {
213275 return ;
214276 }
215277
216278 const cell = this . _getCellFromElement ( event . target as HTMLElement ) ;
217279
218280 if ( cell ) {
219- this . _ngZone . run ( ( ) => {
220- this . _hoveredValue =
221- cell . enabled && cell . compareValue !== this . startValue ? cell . compareValue : - 1 ;
222- this . _changeDetectorRef . markForCheck ( ) ;
223- } ) ;
281+ const value = cell . compareValue ;
282+ const hoveredValue = cell . enabled ? value : - 1 ;
283+
284+ if ( hoveredValue !== this . _hoveredValue ) {
285+ this . _hoveredValue = hoveredValue ;
286+ this . _ngZone . run ( ( ) => this . _changeDetectorRef . markForCheck ( ) ) ;
287+ }
224288 }
225289 }
226290
@@ -230,7 +294,7 @@ export class MatCalendarBody implements OnChanges, OnDestroy {
230294 */
231295 private _leaveHandler = ( event : Event ) => {
232296 // We only need to hit the zone when we're selecting a range.
233- if ( this . _hoveredValue !== - 1 && this . _isRange ( ) ) {
297+ if ( this . _hoveredValue !== - 1 && this . _isSelectingRange ( ) ) {
234298 // Only reset the hovered value when leaving cells. This looks better, because
235299 // we have a gap between the cells and the rows and we don't want to remove the
236300 // range just for it to show up again when the user moves a few pixels to the side.
0 commit comments