@@ -27,20 +27,21 @@ import {
27
27
Inject ,
28
28
Input ,
29
29
NgZone ,
30
+ OnChanges ,
30
31
OnDestroy ,
31
32
Optional ,
32
33
Output ,
33
- ViewEncapsulation ,
34
- ViewChild ,
35
- OnChanges ,
36
34
SimpleChanges ,
35
+ ViewChild ,
36
+ ViewEncapsulation ,
37
37
} from '@angular/core' ;
38
38
import { DateAdapter , MAT_DATE_FORMATS , MatDateFormats } from '@angular/material/core' ;
39
39
import { first } from 'rxjs/operators/first' ;
40
40
import { Subscription } from 'rxjs/Subscription' ;
41
41
import { createMissingDateImplError } from './datepicker-errors' ;
42
42
import { MatDatepickerIntl } from './datepicker-intl' ;
43
43
import { MatMonthView } from './month-view' ;
44
+ import { MatMultiYearView , yearsPerPage , yearsPerRow } from './multi-year-view' ;
44
45
import { MatYearView } from './year-view' ;
45
46
46
47
@@ -73,7 +74,7 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
73
74
private _startAt : D | null ;
74
75
75
76
/** Whether the calendar should be started in month or year view. */
76
- @Input ( ) startView : 'month' | 'year' = 'month' ;
77
+ @Input ( ) startView : 'month' | 'year' | 'multi-year' = 'month' ;
77
78
78
79
/** The currently selected date. */
79
80
@Input ( )
@@ -114,7 +115,10 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
114
115
/** Reference to the current year view component. */
115
116
@ViewChild ( MatYearView ) yearView : MatYearView < D > ;
116
117
117
- /** Date filter for the month and year views. */
118
+ /** Reference to the current multi-year view component. */
119
+ @ViewChild ( MatMultiYearView ) multiYearView : MatMultiYearView < D > ;
120
+
121
+ /** Date filter for the month, year, and multi-year views. */
118
122
_dateFilterForViews = ( date : D ) => {
119
123
return ! ! date &&
120
124
( ! this . dateFilter || this . dateFilter ( date ) ) &&
@@ -133,28 +137,46 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
133
137
private _clampedActiveDate : D ;
134
138
135
139
/** Whether the calendar is in month view. */
136
- _monthView : boolean ;
140
+ _currentView : 'month' | 'year' | 'multi-year' ;
137
141
138
142
/** The label for the current calendar view. */
139
143
get _periodButtonText ( ) : string {
140
- return this . _monthView ?
141
- this . _dateAdapter . format ( this . _activeDate , this . _dateFormats . display . monthYearLabel )
142
- . toLocaleUpperCase ( ) :
143
- this . _dateAdapter . getYearName ( this . _activeDate ) ;
144
+ if ( this . _currentView == 'month' ) {
145
+ return this . _dateAdapter . format ( this . _activeDate , this . _dateFormats . display . monthYearLabel )
146
+ . toLocaleUpperCase ( ) ;
147
+ }
148
+ if ( this . _currentView == 'year' ) {
149
+ return this . _dateAdapter . getYearName ( this . _activeDate ) ;
150
+ }
151
+ let curYear = this . _dateAdapter . getYear ( this . _activeDate ) ;
152
+ let firstYear = this . _dateAdapter . getYearName (
153
+ this . _dateAdapter . createDate ( curYear - curYear % 24 , 0 , 1 ) ) ;
154
+ let lastYear = this . _dateAdapter . getYearName (
155
+ this . _dateAdapter . createDate ( curYear + yearsPerPage - 1 - curYear % 24 , 0 , 1 ) ) ;
156
+ return `${ firstYear } \u2013 ${ lastYear } ` ;
144
157
}
145
158
146
159
get _periodButtonLabel ( ) : string {
147
- return this . _monthView ? this . _intl . switchToYearViewLabel : this . _intl . switchToMonthViewLabel ;
160
+ return this . _currentView == 'month' ?
161
+ this . _intl . switchToMultiYearViewLabel : this . _intl . switchToMonthViewLabel ;
148
162
}
149
163
150
164
/** The label for the the previous button. */
151
165
get _prevButtonLabel ( ) : string {
152
- return this . _monthView ? this . _intl . prevMonthLabel : this . _intl . prevYearLabel ;
166
+ return {
167
+ 'month' : this . _intl . prevMonthLabel ,
168
+ 'year' : this . _intl . prevYearLabel ,
169
+ 'multi-year' : this . _intl . prevMultiYearLabel
170
+ } [ this . _currentView ] ;
153
171
}
154
172
155
173
/** The label for the the next button. */
156
174
get _nextButtonLabel ( ) : string {
157
- return this . _monthView ? this . _intl . nextMonthLabel : this . _intl . nextYearLabel ;
175
+ return {
176
+ 'month' : this . _intl . nextMonthLabel ,
177
+ 'year' : this . _intl . nextYearLabel ,
178
+ 'multi-year' : this . _intl . nextMultiYearLabel
179
+ } [ this . _currentView ] ;
158
180
}
159
181
160
182
constructor ( private _elementRef : ElementRef ,
@@ -178,7 +200,7 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
178
200
ngAfterContentInit ( ) {
179
201
this . _activeDate = this . startAt || this . _dateAdapter . today ( ) ;
180
202
this . _focusActiveCell ( ) ;
181
- this . _monthView = this . startView != 'year' ;
203
+ this . _currentView = this . startView ;
182
204
}
183
205
184
206
ngOnDestroy ( ) {
@@ -189,7 +211,7 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
189
211
const change = changes . minDate || changes . maxDate || changes . dateFilter ;
190
212
191
213
if ( change && ! change . firstChange ) {
192
- const view = this . monthView || this . yearView ;
214
+ const view = this . monthView || this . yearView || this . multiYearView ;
193
215
194
216
if ( view ) {
195
217
view . _init ( ) ;
@@ -208,29 +230,37 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
208
230
this . _userSelection . emit ( ) ;
209
231
}
210
232
233
+ /** Handles month selection in the multi-year view. */
234
+ _yearSelected ( year : D ) : void {
235
+ this . _activeDate = year ;
236
+ this . _currentView = 'year' ;
237
+ }
238
+
211
239
/** Handles month selection in the year view. */
212
240
_monthSelected ( month : D ) : void {
213
241
this . _activeDate = month ;
214
- this . _monthView = true ;
242
+ this . _currentView = 'month' ;
215
243
}
216
244
217
245
/** Handles user clicks on the period label. */
218
246
_currentPeriodClicked ( ) : void {
219
- this . _monthView = ! this . _monthView ;
247
+ this . _currentView = this . _currentView == 'month' ? 'multi-year' : 'month' ;
220
248
}
221
249
222
250
/** Handles user clicks on the previous button. */
223
251
_previousClicked ( ) : void {
224
- this . _activeDate = this . _monthView ?
252
+ this . _activeDate = this . _currentView == 'month' ?
225
253
this . _dateAdapter . addCalendarMonths ( this . _activeDate , - 1 ) :
226
- this . _dateAdapter . addCalendarYears ( this . _activeDate , - 1 ) ;
254
+ this . _dateAdapter . addCalendarYears (
255
+ this . _activeDate , this . _currentView == 'year' ? - 1 : - yearsPerPage ) ;
227
256
}
228
257
229
258
/** Handles user clicks on the next button. */
230
259
_nextClicked ( ) : void {
231
- this . _activeDate = this . _monthView ?
260
+ this . _activeDate = this . _currentView == 'month' ?
232
261
this . _dateAdapter . addCalendarMonths ( this . _activeDate , 1 ) :
233
- this . _dateAdapter . addCalendarYears ( this . _activeDate , 1 ) ;
262
+ this . _dateAdapter . addCalendarYears (
263
+ this . _activeDate , this . _currentView == 'year' ? 1 : yearsPerPage ) ;
234
264
}
235
265
236
266
/** Whether the previous period button is enabled. */
@@ -251,10 +281,12 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
251
281
// TODO(mmalerba): We currently allow keyboard navigation to disabled dates, but just prevent
252
282
// disabled ones from being selected. This may not be ideal, we should look into whether
253
283
// navigation should skip over disabled dates, and if so, how to implement that efficiently.
254
- if ( this . _monthView ) {
284
+ if ( this . _currentView == 'month' ) {
255
285
this . _handleCalendarBodyKeydownInMonthView ( event ) ;
256
- } else {
286
+ } else if ( this . _currentView == 'year' ) {
257
287
this . _handleCalendarBodyKeydownInYearView ( event ) ;
288
+ } else {
289
+ this . _handleCalendarBodyKeydownInMultiYearView ( event ) ;
258
290
}
259
291
}
260
292
@@ -269,10 +301,15 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
269
301
270
302
/** Whether the two dates represent the same view in the current view mode (month or year). */
271
303
private _isSameView ( date1 : D , date2 : D ) : boolean {
272
- return this . _monthView ?
273
- this . _dateAdapter . getYear ( date1 ) == this . _dateAdapter . getYear ( date2 ) &&
274
- this . _dateAdapter . getMonth ( date1 ) == this . _dateAdapter . getMonth ( date2 ) :
275
- this . _dateAdapter . getYear ( date1 ) == this . _dateAdapter . getYear ( date2 ) ;
304
+ if ( this . _currentView == 'month' ) {
305
+ return this . _dateAdapter . getYear ( date1 ) == this . _dateAdapter . getYear ( date2 ) &&
306
+ this . _dateAdapter . getMonth ( date1 ) == this . _dateAdapter . getMonth ( date2 )
307
+ }
308
+ if ( this . _currentView == 'year' ) {
309
+ return this . _dateAdapter . getYear ( date1 ) == this . _dateAdapter . getYear ( date2 ) ;
310
+ }
311
+ return Math . floor ( this . _dateAdapter . getYear ( date1 ) / yearsPerPage ) ==
312
+ Math . floor ( this . _dateAdapter . getYear ( date2 ) / yearsPerPage ) ;
276
313
}
277
314
278
315
/** Handles keydown events on the calendar body when calendar is in month view. */
@@ -336,10 +373,10 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
336
373
this . _activeDate = this . _dateAdapter . addCalendarMonths ( this . _activeDate , 1 ) ;
337
374
break ;
338
375
case UP_ARROW :
339
- this . _activeDate = this . _prevMonthInSameCol ( this . _activeDate ) ;
376
+ this . _activeDate = this . _dateAdapter . addCalendarMonths ( this . _activeDate , - 4 ) ;
340
377
break ;
341
378
case DOWN_ARROW :
342
- this . _activeDate = this . _nextMonthInSameCol ( this . _activeDate ) ;
379
+ this . _activeDate = this . _dateAdapter . addCalendarMonths ( this . _activeDate , 4 ) ;
343
380
break ;
344
381
case HOME :
345
382
this . _activeDate = this . _dateAdapter . addCalendarMonths ( this . _activeDate ,
@@ -370,28 +407,50 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
370
407
event . preventDefault ( ) ;
371
408
}
372
409
373
- /**
374
- * Determine the date for the month that comes before the given month in the same column in the
375
- * calendar table.
376
- */
377
- private _prevMonthInSameCol ( date : D ) : D {
378
- // Determine how many months to jump forward given that there are 2 empty slots at the beginning
379
- // of each year.
380
- let increment = this . _dateAdapter . getMonth ( date ) <= 4 ? - 5 :
381
- ( this . _dateAdapter . getMonth ( date ) >= 7 ? - 7 : - 12 ) ;
382
- return this . _dateAdapter . addCalendarMonths ( date , increment ) ;
383
- }
410
+ /** Handles keydown events on the calendar body when calendar is in multi-year view. */
411
+ private _handleCalendarBodyKeydownInMultiYearView ( event : KeyboardEvent ) : void {
412
+ switch ( event . keyCode ) {
413
+ case LEFT_ARROW :
414
+ this . _activeDate = this . _dateAdapter . addCalendarYears ( this . _activeDate , - 1 ) ;
415
+ break ;
416
+ case RIGHT_ARROW :
417
+ this . _activeDate = this . _dateAdapter . addCalendarYears ( this . _activeDate , 1 ) ;
418
+ break ;
419
+ case UP_ARROW :
420
+ this . _activeDate = this . _dateAdapter . addCalendarYears ( this . _activeDate , - yearsPerRow ) ;
421
+ break ;
422
+ case DOWN_ARROW :
423
+ this . _activeDate = this . _dateAdapter . addCalendarYears ( this . _activeDate , yearsPerRow ) ;
424
+ break ;
425
+ case HOME :
426
+ this . _activeDate = this . _dateAdapter . addCalendarYears ( this . _activeDate ,
427
+ - this . _dateAdapter . getYear ( this . _activeDate ) % yearsPerPage ) ;
428
+ break ;
429
+ case END :
430
+ this . _activeDate = this . _dateAdapter . addCalendarYears ( this . _activeDate ,
431
+ yearsPerPage - this . _dateAdapter . getYear ( this . _activeDate ) % yearsPerPage - 1 ) ;
432
+ break ;
433
+ case PAGE_UP :
434
+ this . _activeDate =
435
+ this . _dateAdapter . addCalendarYears (
436
+ this . _activeDate , event . altKey ? - yearsPerPage * 10 : - yearsPerPage ) ;
437
+ break ;
438
+ case PAGE_DOWN :
439
+ this . _activeDate =
440
+ this . _dateAdapter . addCalendarYears (
441
+ this . _activeDate , event . altKey ? yearsPerPage * 10 : yearsPerPage ) ;
442
+ break ;
443
+ case ENTER :
444
+ this . _yearSelected ( this . _activeDate ) ;
445
+ break ;
446
+ default :
447
+ // Don't prevent default or focus active cell on keys that we don't explicitly handle.
448
+ return ;
449
+ }
384
450
385
- /**
386
- * Determine the date for the month that comes after the given month in the same column in the
387
- * calendar table.
388
- */
389
- private _nextMonthInSameCol ( date : D ) : D {
390
- // Determine how many months to jump forward given that there are 2 empty slots at the beginning
391
- // of each year.
392
- let increment = this . _dateAdapter . getMonth ( date ) <= 4 ? 7 :
393
- ( this . _dateAdapter . getMonth ( date ) >= 7 ? 5 : 12 ) ;
394
- return this . _dateAdapter . addCalendarMonths ( date , increment ) ;
451
+ this . _focusActiveCell ( ) ;
452
+ // Prevent unexpected default actions such as form submission.
453
+ event . preventDefault ( ) ;
395
454
}
396
455
397
456
/**
0 commit comments