@@ -17,6 +17,8 @@ import {
1717 NgZone ,
1818 OnChanges ,
1919 SimpleChanges ,
20+ OnDestroy ,
21+ ChangeDetectorRef ,
2022} from '@angular/core' ;
2123import { take } from 'rxjs/operators' ;
2224
@@ -55,7 +57,7 @@ export class MatCalendarCell {
5557 encapsulation : ViewEncapsulation . None ,
5658 changeDetection : ChangeDetectionStrategy . OnPush ,
5759} )
58- export class MatCalendarBody implements OnChanges {
60+ export class MatCalendarBody implements OnChanges , OnDestroy {
5961 /** The label for the table. (e.g. "Jan 2017"). */
6062 @Input ( ) label : string ;
6163
@@ -66,7 +68,11 @@ export class MatCalendarBody implements OnChanges {
6668 @Input ( ) todayValue : number ;
6769
6870 /** The value in the table that is currently selected. */
69- @Input ( ) selectedValue : number ;
71+ // @Input () selectedValue: number;
72+
73+ @Input ( ) startValue : number ;
74+
75+ @Input ( ) endValue : number ;
7076
7177 /** The minimum number of free cells needed to fit the label in the first row. */
7278 @Input ( ) labelMinRequiredCells : number ;
@@ -95,14 +101,34 @@ export class MatCalendarBody implements OnChanges {
95101 /** Width of an individual cell. */
96102 _cellWidth : string ;
97103
98- constructor ( private _elementRef : ElementRef < HTMLElement > , private _ngZone : NgZone ) { }
104+ _hoveredValue : number ;
105+
106+ constructor (
107+ private _elementRef : ElementRef < HTMLElement > ,
108+ private _changeDetectorRef : ChangeDetectorRef ,
109+ private _ngZone : NgZone ) {
110+
111+ _ngZone . runOutsideAngular ( ( ) => {
112+ const element = _elementRef . nativeElement ;
113+ element . addEventListener ( 'mouseenter' , this . _enterHandler , true ) ;
114+ element . addEventListener ( 'focus' , this . _enterHandler , true ) ;
115+ element . addEventListener ( 'mouseleave' , this . _leaveHandler , true ) ;
116+ element . addEventListener ( 'blur' , this . _leaveHandler , true ) ;
117+ } ) ;
118+ }
99119
120+ /** Called when a cell is clicked. */
100121 _cellClicked ( cell : MatCalendarCell ) : void {
101122 if ( cell . enabled ) {
102123 this . selectedValueChange . emit ( cell . value ) ;
103124 }
104125 }
105126
127+ /** Returns whether a cell should be marked as selected. */
128+ _isSelected ( cell : MatCalendarCell ) {
129+ return this . startValue === cell . value || this . endValue === cell . value ;
130+ }
131+
106132 ngOnChanges ( changes : SimpleChanges ) {
107133 const columnChanges = changes [ 'numCols' ] ;
108134 const { rows, numCols} = this ;
@@ -120,6 +146,15 @@ export class MatCalendarBody implements OnChanges {
120146 }
121147 }
122148
149+ ngOnDestroy ( ) {
150+ const element = this . _elementRef . nativeElement ;
151+ element . removeEventListener ( 'mouseenter' , this . _enterHandler , true ) ;
152+ element . removeEventListener ( 'focus' , this . _enterHandler , true ) ;
153+ element . removeEventListener ( 'mouseleave' , this . _leaveHandler , true ) ;
154+ element . removeEventListener ( 'blur' , this . _leaveHandler , true ) ;
155+ }
156+
157+ /** Returns whether a cell is active. */
123158 _isActiveCell ( rowIndex : number , colIndex : number ) : boolean {
124159 let cellNumber = rowIndex * this . numCols + colIndex ;
125160
@@ -144,4 +179,86 @@ export class MatCalendarBody implements OnChanges {
144179 } ) ;
145180 } ) ;
146181 }
182+
183+ /** Gets whether the calendar is currently selecting a range. */
184+ _isRange ( ) : boolean {
185+ return this . startValue !== this . endValue ;
186+ }
187+
188+ /** Gets whether a value is within the currently-selected range. */
189+ _isInRange ( value : number ) : boolean {
190+ if ( ! this . _isRange ( ) || value < this . startValue ) {
191+ return false ;
192+ }
193+
194+ return value <= this . endValue || value <= this . _hoveredValue ;
195+ }
196+
197+ /**
198+ * Event handler for when the user enters an element
199+ * inside the calendar body (e.g. by hovering in or focus).
200+ */
201+ private _enterHandler = ( event : Event ) => {
202+ // We only need to hit the zone when we're selecting a range, we
203+ // have a start value without an end value and we've hovered over a date cell.
204+ if ( ! event . target || ! this . _isRange ( ) || ! this . startValue || this . endValue ) {
205+ return ;
206+ }
207+
208+ const cell = this . _getCellFromElement ( event . target as HTMLElement ) ;
209+
210+ if ( cell ) {
211+ this . _ngZone . run ( ( ) => {
212+ this . _hoveredValue = cell . value ;
213+ this . _changeDetectorRef . markForCheck ( ) ;
214+ } ) ;
215+ }
216+ }
217+
218+ /**
219+ * Event handler for when the user's pointer leaves an element
220+ * inside the calendar body (e.g. by hovering out or blurring).
221+ */
222+ private _leaveHandler = ( event : Event ) => {
223+ // We only need to hit the zone when we're selecting a range.
224+ if ( this . _isRange ( ) && this . _hoveredValue !== - 1 ) {
225+ // Only reset the hovered value when leaving cells. This looks better, because
226+ // we have a gap between the cells and the rows and we don't want to remove the
227+ // range just for it to show up again when the user moves a few pixels to the side.
228+ if ( event . target && isTableCell ( event . target as HTMLElement ) ) {
229+ this . _ngZone . run ( ( ) => {
230+ this . _hoveredValue = - 1 ;
231+ this . _changeDetectorRef . markForCheck ( ) ;
232+ } ) ;
233+ }
234+ }
235+ }
236+
237+ /** Finds the MatCalendarCell that corresponds to a DOM node. */
238+ private _getCellFromElement ( element : HTMLElement ) : MatCalendarCell | null {
239+ let cell : HTMLElement | undefined ;
240+
241+ if ( isTableCell ( element ) ) {
242+ cell = element ;
243+ } else if ( isTableCell ( element . parentNode ! ) ) {
244+ cell = element . parentNode as HTMLElement ;
245+ }
246+
247+ if ( cell ) {
248+ const row = cell . getAttribute ( 'data-mat-row' ) ;
249+ const col = cell . getAttribute ( 'data-mat-col' ) ;
250+
251+ if ( row && col ) {
252+ return this . rows [ parseInt ( row ) ] [ parseInt ( col ) ] ;
253+ }
254+ }
255+
256+ return null ;
257+ }
258+
259+ }
260+
261+ /** Checks whether a node is a table cell element. */
262+ function isTableCell ( node : Node ) : node is HTMLTableCellElement {
263+ return node . nodeName === 'TD' ;
147264}
0 commit comments