license import { DOWN_ARROW , END , ENTER , HOME , LEFT_ARROW , PAGE_DOWN , PAGE_UP , RIGHT_ARROW , UP_ARROW , SPACE , } from '@angular/cdk/keycodes' ; import { AfterContentInit , ChangeDetectionStrategy , ChangeDetectorRef , Component , EventEmitter , Input , Optional , Output , ViewChild , ViewEncapsulation , } from '@angular/core' ; import { DateAdapter } from '@angular/material/core' ; import { Directionality } from '@angular/cdk/bidi' ; import { MatCalendarBody , MatCalendarCell } from './calendar-body' ; import { createMissingDateImplError } from './datepicker-errors' ; export const yearsPerPage = 24 ; export const yearsPerRow = 4 ; docs-private @ Component ( { moduleId : module . id , selector : 'mat-multi-year-view' , templateUrl : 'multi-year-view.html' , exportAs : 'matMultiYearView' , encapsulation : ViewEncapsulation . None , changeDetection : ChangeDetectionStrategy . OnPush } ) export class MatMultiYearView < D > implements AfterContentInit { /** The date to display in this multi-year view (everything other than the year is ignored). */ @ Input ( ) get activeDate ( ) : D { return this . _activeDate ; } set activeDate ( value ) { let oldActiveDate = this . _activeDate ; const validDate = this . _getValidDateOrNull ( this . _dateAdapter . deserialize ( value ) ) || this . _dateAdapter . today ( ) ; this . _activeDate = this . _dateAdapter . clampDate ( validDate , this . minDate , this . maxDate ) ; if ( Math . floor ( this . _dateAdapter . getYear ( oldActiveDate ) / yearsPerPage ) != Math . floor ( this . _dateAdapter . getYear ( this . _activeDate ) / yearsPerPage ) ) { this . _init ( ) ; } } private _activeDate : D ; /** The currently selected date. */ @ Input ( ) get selected ( ) : D | null { return this . _selected ; } set selected ( value ) { this . _selected = this . _getValidDateOrNull ( this . _dateAdapter . deserialize ( value ) ) ; this . _selectedYear = this . _selected && this . _dateAdapter . getYear ( this . _selected ) ; } private _selected : D | null ; /** The minimum selectable date. */ @ Input ( ) get minDate ( ) : D | null { return this . _minDate ; } set minDate ( value ) { this . _minDate = this . _getValidDateOrNull ( this . _dateAdapter . deserialize ( value ) ) ; } private _minDate : D | null ; /** The maximum selectable date. */ @ Input ( ) get maxDate ( ) : D | null { return this . _maxDate ; } set maxDate ( value ) { this . _maxDate = this . _getValidDateOrNull ( this . _dateAdapter . deserialize ( value ) ) ; } private _maxDate : D | null ; /** A function used to filter which dates are selectable. */ @ Input ( ) dateFilter : ( date : D ) => boolean ; /** Emits when a new year is selected. */ @ Output ( ) readonly selectedChange : EventEmitter < D > = new EventEmitter < D > ( ) ; /** Emits the selected year. This doesn't imply a change on the selected date */ @ Output ( ) readonly yearSelected : EventEmitter < D > = new EventEmitter < D > ( ) ; /** Emits when any date is activated. */ @ Output ( ) readonly activeDateChange : EventEmitter < D > = new EventEmitter < D > ( ) ; /** The body of calendar table */ @ ViewChild ( MatCalendarBody ) _matCalendarBody : MatCalendarBody ; /** Grid of calendar cells representing the currently displayed years. */ _years : MatCalendarCell [ ] [ ] ; /** The year that today falls on. */ _todayYear : number ; /** The year of the selected date. Null if the selected date is null. */ _selectedYear : number | null ; constructor ( private _changeDetectorRef , @ Optional ( ) public _dateAdapter , @ Optional ( ) private _dir ? ) { if ( ! this . _dateAdapter ) { throw createMissingDateImplError ( 'DateAdapter' ) ; } this . _activeDate = this . _dateAdapter . today ( ) ; } ngAfterContentInit ( ) { this . _init ( ) ; } /** Initializes this multi-year view. */ _init ( ) { this . _todayYear = this . _dateAdapter . getYear ( this . _dateAdapter . today ( ) ) ; let activeYear = this . _dateAdapter . getYear ( this . _activeDate ) ; let activeOffset = activeYear % yearsPerPage ; this . _years = [ ] ; for ( let i = 0 , row = [ ] ; i < yearsPerPage ; i ++ ) { row . push ( activeYear - activeOffset + i ) ; if ( row . length == yearsPerRow ) { this . _years . push ( row . map ( year => this . _createCellForYear ( year ) ) ) ; row = [ ] ; } } this . _changeDetectorRef . markForCheck ( ) ; } /** Handles when a new year is selected. */ _yearSelected ( year ) { this . yearSelected . emit ( this . _dateAdapter . createDate ( year , 0 , 1 ) ) ; let month = this . _dateAdapter . getMonth ( this . activeDate ) ; let daysInMonth = this . _dateAdapter . getNumDaysInMonth ( this . _dateAdapter . createDate ( year , month , 1 ) ) ; this . selectedChange . emit ( this . _dateAdapter . createDate ( year , month , Math . min ( this . _dateAdapter . getDate ( this . activeDate ) , daysInMonth ) ) ) ; } /** Handles keydown events on the calendar body when calendar is in multi-year view. */ _handleCalendarBodyKeydown ( event ) { const oldActiveDate = this . _activeDate ; const isRtl = this . _isRtl ( ) ; switch ( event . keyCode ) { case LEFT_ARROW : this . activeDate = this . _dateAdapter . addCalendarYears ( this . _activeDate , isRtl ? 1 : - 1 ) ; break ; case RIGHT_ARROW : this . activeDate = this . _dateAdapter . addCalendarYears ( this . _activeDate , isRtl ? - 1 : 1 ) ; break ; case UP_ARROW : this . activeDate = this . _dateAdapter . addCalendarYears ( this . _activeDate , - yearsPerRow ) ; break ; case DOWN_ARROW : this . activeDate = this . _dateAdapter . addCalendarYears ( this . _activeDate , yearsPerRow ) ; break ; case HOME : this . activeDate = this . _dateAdapter . addCalendarYears ( this . _activeDate , - this . _dateAdapter . getYear ( this . _activeDate ) % yearsPerPage ) ; break ; case END : this . activeDate = this . _dateAdapter . addCalendarYears ( this . _activeDate , yearsPerPage - this . _dateAdapter . getYear ( this . _activeDate ) % yearsPerPage - 1 ) ; break ; case PAGE_UP : this . activeDate = this . _dateAdapter . addCalendarYears ( this . _activeDate , event . altKey ? - yearsPerPage * 10 : - yearsPerPage ) ; break ; case PAGE_DOWN : this . activeDate = this . _dateAdapter . addCalendarYears ( this . _activeDate , event . altKey ? yearsPerPage * 10 : yearsPerPage ) ; break ; case ENTER : case SPACE : this . _yearSelected ( this . _dateAdapter . getYear ( this . _activeDate ) ) ; break ; default : return ; } if ( this . _dateAdapter . compareDate ( oldActiveDate , this . activeDate ) ) { this . activeDateChange . emit ( this . activeDate ) ; } this . _focusActiveCell ( ) ; event . preventDefault ( ) ; } _getActiveCell ( ) { return this . _dateAdapter . getYear ( this . activeDate ) % yearsPerPage ; } /** Focuses the active cell after the microtask queue is empty. */ _focusActiveCell ( ) { this . _matCalendarBody . _focusActiveCell ( ) ; } /** Creates an MatCalendarCell for the given year. */ private _createCellForYear ( year ) { let yearName = this . _dateAdapter . getYearName ( this . _dateAdapter . createDate ( year , 0 , 1 ) ) ; return new MatCalendarCell ( year , yearName , yearName , this . _shouldEnableYear ( year ) ) ; } /** Whether the given year is enabled. */ private _shouldEnableYear ( year ) { if ( year === undefined || year === null || ( this . maxDate && year > this . _dateAdapter . getYear ( this . maxDate ) ) || ( this . minDate && year < this . _dateAdapter . getYear ( this . minDate ) ) ) { return false ; } if ( ! this . dateFilter ) { return true ; } const firstOfYear = this . _dateAdapter . createDate ( year , 0 , 1 ) ; for ( let date = firstOfYear ; this . _dateAdapter . getYear ( date ) == year ; date = this . _dateAdapter . addCalendarDays ( date , 1 ) ) { if ( this . dateFilter ( date ) ) { return true ; } } return false ; } param obj returns private _getValidDateOrNull ( obj ) { return ( this . _dateAdapter . isDateInstance ( obj ) && this . _dateAdapter . isValid ( obj ) ) ? obj : null ; } /** Determines whether the user has the RTL layout direction. */ private _isRtl ( ) { return this . _dir && this . _dir . value === 'rtl' ; } }