@@ -31,13 +31,14 @@ export type MatCalendarCellCssClasses = string | string[] | Set<string> | {[key:
31
31
* An internal class that represents the data corresponding to a single calendar cell.
32
32
* @docs -private
33
33
*/
34
- export class MatCalendarCell {
34
+ export class MatCalendarCell < D = any > {
35
35
constructor ( public value : number ,
36
36
public displayValue : string ,
37
37
public ariaLabel : string ,
38
38
public enabled : boolean ,
39
39
public cssClasses : MatCalendarCellCssClasses = { } ,
40
- public compareValue = value ) { }
40
+ public compareValue = value ,
41
+ public rawValue ?: D ) { }
41
42
}
42
43
43
44
/** Event emitted when a date inside the calendar is triggered as a result of a user action. */
@@ -64,6 +65,12 @@ export interface MatCalendarUserEvent<D> {
64
65
changeDetection : ChangeDetectionStrategy . OnPush ,
65
66
} )
66
67
export class MatCalendarBody implements OnChanges , OnDestroy {
68
+ /**
69
+ * Used to skip the next focus event when rendering the preview range.
70
+ * We need a flag like this, because some browsers fire focus events asynchronously.
71
+ */
72
+ private _skipNextFocus : boolean ;
73
+
67
74
/** The label for the table. (e.g. "Jan 2017"). */
68
75
@Input ( ) label : string ;
69
76
@@ -88,6 +95,9 @@ export class MatCalendarBody implements OnChanges, OnDestroy {
88
95
/** The cell number of the active cell in the table. */
89
96
@Input ( ) activeCell : number = 0 ;
90
97
98
+ /** Whether a range is being selected. */
99
+ @Input ( ) isRange : boolean = false ;
100
+
91
101
/**
92
102
* The aspect ratio (width / height) to use for the cells in the table. This aspect ratio will be
93
103
* maintained even as the table resizes.
@@ -100,10 +110,19 @@ export class MatCalendarBody implements OnChanges, OnDestroy {
100
110
/** End of the comparison range. */
101
111
@Input ( ) comparisonEnd : number | null ;
102
112
113
+ /** Start of the preview range. */
114
+ @Input ( ) previewStart : number | null = null ;
115
+
116
+ /** End of the preview range. */
117
+ @Input ( ) previewEnd : number | null = null ;
118
+
103
119
/** Emits when a new value is selected. */
104
120
@Output ( ) readonly selectedValueChange : EventEmitter < MatCalendarUserEvent < number > > =
105
121
new EventEmitter < MatCalendarUserEvent < number > > ( ) ;
106
122
123
+ /** Emits when the preview has changed as a result of a user action. */
124
+ @Output ( ) previewChange = new EventEmitter < MatCalendarUserEvent < MatCalendarCell | null > > ( ) ;
125
+
107
126
/** The number of blank cells to put at the beginning for the first row. */
108
127
_firstRowOffset : number ;
109
128
@@ -113,12 +132,6 @@ export class MatCalendarBody implements OnChanges, OnDestroy {
113
132
/** Width of an individual cell. */
114
133
_cellWidth : string ;
115
134
116
- /**
117
- * Value that the user is either currently hovering over or is focusing
118
- * using the keyboard. Only applies when selecting the end of a date range.
119
- */
120
- _previewEnd = - 1 ;
121
-
122
135
constructor (
123
136
private _elementRef : ElementRef < HTMLElement > ,
124
137
private _changeDetectorRef : ChangeDetectorRef ,
@@ -160,10 +173,6 @@ export class MatCalendarBody implements OnChanges, OnDestroy {
160
173
if ( columnChanges || ! this . _cellWidth ) {
161
174
this . _cellWidth = `${ 100 / numCols } %` ;
162
175
}
163
-
164
- if ( changes [ 'startValue' ] || changes [ 'endValue' ] ) {
165
- this . _previewEnd = - 1 ;
166
- }
167
176
}
168
177
169
178
ngOnDestroy ( ) {
@@ -187,24 +196,23 @@ export class MatCalendarBody implements OnChanges, OnDestroy {
187
196
}
188
197
189
198
/** Focuses the active cell after the microtask queue is empty. */
190
- _focusActiveCell ( ) {
199
+ _focusActiveCell ( movePreview = true ) {
191
200
this . _ngZone . runOutsideAngular ( ( ) => {
192
201
this . _ngZone . onStable . asObservable ( ) . pipe ( take ( 1 ) ) . subscribe ( ( ) => {
193
202
const activeCell : HTMLElement | null =
194
203
this . _elementRef . nativeElement . querySelector ( '.mat-calendar-body-active' ) ;
195
204
196
205
if ( activeCell ) {
206
+ if ( ! movePreview ) {
207
+ this . _skipNextFocus = true ;
208
+ }
209
+
197
210
activeCell . focus ( ) ;
198
211
}
199
212
} ) ;
200
213
} ) ;
201
214
}
202
215
203
- /** Gets whether the calendar is currently selecting a range. */
204
- _isSelectingRange ( ) : boolean {
205
- return this . startValue !== this . endValue ;
206
- }
207
-
208
216
/** Gets whether a value is the start of the main range. */
209
217
_isRangeStart ( value : number ) {
210
218
return value === this . startValue ;
@@ -217,7 +225,8 @@ export class MatCalendarBody implements OnChanges, OnDestroy {
217
225
218
226
/** Gets whether a value is within the currently-selected range. */
219
227
_isInRange ( value : number ) : boolean {
220
- return this . _isSelectingRange ( ) && value >= this . startValue && value <= this . endValue ;
228
+ return this . isRange && this . startValue !== null && this . endValue !== null &&
229
+ value >= this . startValue && value <= this . endValue ;
221
230
}
222
231
223
232
/** Gets whether a value is the start of the comparison range. */
@@ -272,41 +281,41 @@ export class MatCalendarBody implements OnChanges, OnDestroy {
272
281
273
282
/** Gets whether a value is the start of the preview range. */
274
283
_isPreviewStart ( value : number ) {
275
- return this . _previewEnd > - 1 && this . _isRangeStart ( value ) ;
284
+ return value === this . previewStart && this . previewEnd && value < this . previewEnd ;
276
285
}
277
286
278
287
/** Gets whether a value is the end of the preview range. */
279
288
_isPreviewEnd ( value : number ) {
280
- return value === this . _previewEnd ;
289
+ return value === this . previewEnd && this . previewStart && value > this . previewStart ;
281
290
}
282
291
283
292
/** Gets whether a value is inside the preview range. */
284
293
_isInPreview ( value : number ) {
285
- return this . _isSelectingRange ( ) && value >= this . startValue && value <= this . _previewEnd ;
294
+ if ( ! this . isRange ) {
295
+ return false ;
296
+ }
297
+
298
+ const { previewStart, previewEnd} = this ;
299
+ return previewStart !== null && previewEnd !== null && previewStart !== previewEnd &&
300
+ value >= previewStart && value <= previewEnd ;
286
301
}
287
302
288
303
/**
289
304
* Event handler for when the user enters an element
290
305
* inside the calendar body (e.g. by hovering in or focus).
291
306
*/
292
307
private _enterHandler = ( event : Event ) => {
293
- // We only need to hit the zone when we're selecting a range, we have a
294
- // start value without an end value and we've hovered over a date cell.
295
- if ( ! event . target || ! this . startValue || this . endValue || ! this . _isSelectingRange ( ) ) {
308
+ if ( this . _skipNextFocus && event . type === 'focus' ) {
309
+ this . _skipNextFocus = false ;
296
310
return ;
297
311
}
298
312
299
- const cell = this . _getCellFromElement ( event . target as HTMLElement ) ;
300
-
301
- if ( cell ) {
302
- const value = cell . compareValue ;
303
-
304
- // Only set as the preview end value if we're after the start of the range.
305
- const previewEnd = ( cell . enabled && value > this . startValue ) ? value : - 1 ;
313
+ // We only need to hit the zone when we're selecting a range.
314
+ if ( event . target && this . isRange ) {
315
+ const cell = this . _getCellFromElement ( event . target as HTMLElement ) ;
306
316
307
- if ( previewEnd !== this . _previewEnd ) {
308
- this . _previewEnd = previewEnd ;
309
- this . _ngZone . run ( ( ) => this . _changeDetectorRef . markForCheck ( ) ) ;
317
+ if ( cell ) {
318
+ this . _ngZone . run ( ( ) => this . previewChange . emit ( { value : cell . enabled ? cell : null , event} ) ) ;
310
319
}
311
320
}
312
321
}
@@ -317,13 +326,13 @@ export class MatCalendarBody implements OnChanges, OnDestroy {
317
326
*/
318
327
private _leaveHandler = ( event : Event ) => {
319
328
// We only need to hit the zone when we're selecting a range.
320
- if ( this . _previewEnd !== - 1 && this . _isSelectingRange ( ) ) {
329
+ if ( this . previewEnd !== null && this . isRange ) {
321
330
// Only reset the preview end value when leaving cells. This looks better, because
322
331
// we have a gap between the cells and the rows and we don't want to remove the
323
332
// range just for it to show up again when the user moves a few pixels to the side.
324
333
if ( event . target && isTableCell ( event . target as HTMLElement ) ) {
325
334
this . _ngZone . run ( ( ) => {
326
- this . _previewEnd = - 1 ;
335
+ this . previewChange . emit ( { value : null , event } ) ;
327
336
328
337
// Note that here we need to use `detectChanges`, rather than `markForCheck`, because
329
338
// the way `_focusActiveCell` is set up at the moment makes it fire at the wrong time
0 commit comments