import { Component , Input , Output , EventEmitter , forwardRef , OnInit , ViewEncapsulation , ChangeDetectionStrategy , ChangeDetectorRef , AfterViewInit , ElementRef , HostBinding } from '@angular/core' ; import { ControlValueAccessor , NG_VALUE_ACCESSOR } from '@angular/forms' ; import moment from 'moment-timezone' ; import { getMonth } from './utils/get-month/get-month.util' ; import { getDecadeStartYear } from './utils/get-decade-start-year/get-decade-start-year.util' ; import { CalendarDay } from './calendar-day.interface' ; import { CalendarMonth } from './calendar-month.type' ; import { CalendarView } from './calendar-view.enum' ; import { KeyboardKeys } from '../../enums/keyboard-keys.enum' ; const CALENDAR_VALUE_ACCESSOR = { provide : NG_VALUE_ACCESSOR , useExisting : forwardRef ( ( ) => CalendarComponent ) , multi : true } ; @ Component ( { selector : 'ngx-calendar' , exportAs : 'ngxCalendar' , templateUrl : './calendar.component.html' , styleUrls : [ './calendar.component.scss' ] , host : { class : 'ngx-calendar' , tabindex : '1' , '(blur)' : 'onTouchedCallback()' } , providers : [ CALENDAR_VALUE_ACCESSOR ] , encapsulation : ViewEncapsulation . None , changeDetection : ChangeDetectionStrategy . OnPush } ) export class CalendarComponent implements OnInit , AfterViewInit , ControlValueAccessor { @ Input ( ) minDate : Date | string ; @ HostBinding ( 'class.ngx-calendar--disabled' ) @ Input ( ) disabled : boolean ; @ Input ( ) maxDate : Date | string ; @ Input ( ) daysOfWeek : string [ ] = [ 'S' , 'M' , 'T' , 'W' , 'T' , 'F' , 'S' ] ; @ Input ( ) timezone : string ; @ Input ( ) inputFormats : Array < string | moment . MomentBuiltinFormat > = [ 'L' , 'LT' , 'L LT' , moment . ISO_8601 ] ; @ Input ( 'minView' ) get minView ( ) { return this . _minView ? this . _minView : CalendarView . Date ; } set minView ( val ) { this . _minView = val ; this . validateView ( ) ; } @ Input ( 'defaultView' ) get defaultView ( ) { return this . _defaultView ? this . _defaultView : this . minView ; } set defaultView ( val ) { this . _defaultView = val ; this . validateView ( ) ; } @ Output ( ) change = new EventEmitter < Date > ( ) ; @ Output ( ) dayKeyEnter = new EventEmitter < Date > ( ) ; @ HostBinding ( 'attr.tabindex' ) tabindex = - 1 ; get value ( ) { return this . _value ; } set value ( val ) { const date = this . createMoment ( val ) ; if ( date . isValid ( ) ) { if ( ! date . isSame ( this . _value , 'day' ) ) { this . _value = val ; this . onChangeCallback ( this . _value ) ; } this . change . emit ( this . _value ) ; } } get current ( ) : moment . Moment { return this . _current ; } focusDate : moment . Moment ; weeks : CalendarMonth ; currentView : CalendarView ; monthsList = moment . monthsShort ( ) ; startYear : number ; readonly CalendarView = CalendarView ; private _value : Date ; private _current : moment . Moment ; private _minView : CalendarView ; private _defaultView : CalendarView ; constructor ( private readonly cdr , private readonly elm ) { } ngOnInit ( ) { this . focusDate = this . createMoment ( this . value ) ; this . weeks = getMonth ( this . focusDate ) ; this . monthsList = moment . monthsShort ( ) ; this . _current = this . focusDate ; this . startYear = getDecadeStartYear ( this . _current . year ( ) ) ; this . validateView ( ) ; } ngAfterViewInit ( ) { this . cdr . markForCheck ( ) ; } changeViews ( ) { if ( this . currentView === CalendarView . Date ) { this . currentView = CalendarView . Month ; } else if ( this . currentView === CalendarView . Month ) { this . currentView = CalendarView . Year ; } else { this . currentView = this . minView ; } this . weeks = getMonth ( this . focusDate ) ; } validateView ( ) { const viewsList = [ CalendarView . Date , CalendarView . Month , CalendarView . Year ] ; if ( ! viewsList . includes ( this . minView ) ) { this . minView = CalendarView . Date ; } if ( viewsList . indexOf ( this . minView ) > viewsList . indexOf ( this . defaultView ) ) { this . defaultView = this . minView ; } this . currentView = this . defaultView ; } /**
     * Checks if `date` matches selected value
     */ isDayActive ( date ) { return date . isSame ( this . value , 'day' ) ; } /**
     * Checks if `date` matches selected value
     */ isDayFocus ( date ) { if ( ! this . focusDate ) return false ; return date . isSame ( this . focusDate , 'day' ) ; } /**
     * Checks if `month` matches selected value, in the viewed year
     */ isMonthActive ( month ) { const date = this . createMoment ( this . value ) . month ( month ) ; return date . isSame ( this . value , 'month' ) && date . isSame ( this . focusDate , 'year' ) ; } /**
     * Checks if `month` and year matches current
     */ isCurrentMonth ( month ) { const date = this . focusDate . clone ( ) . month ( month ) ; return date . isSame ( this . _current , 'month' ) && date . isSame ( this . _current , 'year' ) ; } /**
     * Checks if `month` and year matches current focus
     */ isFocusMonth ( month ) { const date = this . focusDate . clone ( ) . month ( month ) ; return date . isSame ( this . focusDate , 'month' ) && date . isSame ( this . focusDate , 'year' ) ; } /**
     * Checks if `year` matches selected year
     */ isYearActive ( year ) { const date = this . createMoment ( this . value ) . year ( year ) ; return date . isSame ( this . value , 'year' ) ; } /**
     * Checks if year matches current year
     */ isCurrentYear ( year ) { const date = this . createMoment ( this . value ) . year ( year ) ; return date . isSame ( this . _current , 'year' ) ; } /**
     * Checks if year matches current focus
     */ isFocusYear ( year ) { const date = this . focusDate . clone ( ) . year ( year ) ; return date . isSame ( this . focusDate , 'year' ) ; } isDisabled ( value , type ) { if ( this . disabled ) return true ; if ( ! value ) return false ; let date ; switch ( type ) { case 'day' : date = value ; break ; case 'month' : date = this . focusDate . clone ( ) . month ( value ) ; break ; case 'year' : date = this . focusDate . clone ( ) . year ( value ) ; break ; default : return false ; } const isBeforeMin = this . minDate && date . isBefore ( this . parseDate ( this . minDate ) , type ) ; const isAfterMax = this . maxDate && date . isAfter ( this . parseDate ( this . maxDate ) , type ) ; return isBeforeMin || isAfterMax ; } onDayClick ( day ) { this . focusDate = day . date . clone ( ) ; this . value = this . focusDate . toDate ( ) ; if ( day . prevMonth || day . nextMonth ) { this . weeks = getMonth ( this . focusDate ) ; } } onDayFocus ( day ) { this . focusDate = day . date . clone ( ) ; this . cdr . detectChanges ( ) ; this . focus ( ) ; } onMonthClick ( month ) { this . focusDate . month ( month ) ; this . value = this . focusDate . toDate ( ) ; if ( this . minView !== CalendarView . Month ) { this . currentView = CalendarView . Date ; this . weeks = getMonth ( this . focusDate ) ; } } onYearClick ( year ) { this . focusDate . year ( year ) ; this . value = this . focusDate . toDate ( ) ; if ( this . minView !== CalendarView . Year ) { this . currentView = CalendarView . Month ; this . weeks = getMonth ( this . focusDate ) ; } } prevMonth ( ) { const date = this . focusDate . clone ( ) ; this . focusDate = date . subtract ( 1 , 'month' ) ; this . weeks = getMonth ( this . focusDate ) ; } nextMonth ( ) { const date = this . focusDate . clone ( ) ; this . focusDate = date . add ( 1 , 'month' ) ; this . weeks = getMonth ( this . focusDate ) ; } prevYear ( ) { const date = this . focusDate . clone ( ) ; this . focusDate = date . subtract ( 1 , 'year' ) ; } nextYear ( ) { const date = this . focusDate . clone ( ) ; this . focusDate = date . add ( 1 , 'year' ) ; } moveFocus ( amount , duration ) { const focusDate = this . focusDate . clone ( ) . add ( amount , duration ) ; this . setFocus ( focusDate ) ; } setFocus ( focusDate ) { this . focusDate = focusDate ; this . weeks = getMonth ( this . focusDate ) ; if ( this . focusDate . year ( ) < this . startYear ) { this . prevTwoDecades ( ) ; } else if ( this . focusDate . year ( ) > this . startYear + 20 ) { this . nextTwoDecades ( ) ; } this . cdr . detectChanges ( ) ; this . focus ( ) ; } prevTwoDecades ( ) { this . startYear = this . startYear - 20 ; } nextTwoDecades ( ) { this . startYear = this . startYear + 20 ; } writeValue ( val ) { const focusDate = this . createMoment ( val ) ; if ( focusDate . isValid ( ) && ! focusDate . isSame ( this . value , 'day' ) ) { this . focusDate = focusDate ; this . weeks = getMonth ( this . focusDate ) ; this . _value = val ; this . startYear = getDecadeStartYear ( this . focusDate . year ( ) ) ; } this . cdr . markForCheck ( ) ; } registerOnChange ( fn ) { this . onChangeCallback = fn ; } registerOnTouched ( fn ) { this . onTouchedCallback = fn ; } onTouchedCallback : ( ) => void = ( ) => { } ; focus ( ) { const elm = this . elm . nativeElement . querySelector ( 'button.focus' ) ; if ( elm ) { elm . focus ( ) ; } } onDayDown ( event ) { let stop = false ; if ( this . currentView === CalendarView . Date ) { switch ( event . code ) { case KeyboardKeys . ARROW_DOWN : this . moveFocus ( 1 , 'week' ) ; stop = true ; break ; case KeyboardKeys . ARROW_UP : this . moveFocus ( - 1 , 'week' ) ; stop = true ; break ; case KeyboardKeys . ARROW_LEFT : this . moveFocus ( - 1 , 'day' ) ; stop = true ; break ; case KeyboardKeys . ARROW_RIGHT : this . moveFocus ( 1 , 'day' ) ; stop = true ; break ; case KeyboardKeys . PAGE_UP : { const prev = event . altKey ? 'year' : 'month' ; this . moveFocus ( - 1 , prev ) ; stop = true ; break ; } case KeyboardKeys . PAGE_DOWN : { const next = event . altKey ? 'year' : 'month' ; this . moveFocus ( 1 , next ) ; stop = true ; break ; } case KeyboardKeys . ENTER : setTimeout ( ( ) => { this . dayKeyEnter . emit ( ) ; } , 200 ) ; break ; case KeyboardKeys . HOME : { const startOf = event . altKey ? 'month' : 'week' ; this . setFocus ( this . focusDate . clone ( ) . startOf ( startOf ) ) ; stop = true ; break ; } case KeyboardKeys . END : { const endOf = event . altKey ? 'month' : 'week' ; this . setFocus ( this . focusDate . clone ( ) . endOf ( endOf ) ) ; stop = true ; break ; } } } if ( stop ) { event . stopPropagation ( ) ; event . preventDefault ( ) ; } this . cdr . detectChanges ( ) ; } onMonthDown ( event ) { let stop = false ; if ( this . currentView === CalendarView . Month ) { switch ( event . code ) { case KeyboardKeys . ARROW_DOWN : this . moveFocus ( 3 , 'month' ) ; stop = true ; break ; case KeyboardKeys . ARROW_UP : this . moveFocus ( - 3 , 'month' ) ; stop = true ; break ; case KeyboardKeys . ARROW_LEFT : this . moveFocus ( - 1 , 'month' ) ; stop = true ; break ; case KeyboardKeys . ARROW_RIGHT : this . moveFocus ( 1 , 'month' ) ; stop = true ; break ; case KeyboardKeys . HOME : this . setFocus ( this . focusDate . clone ( ) . startOf ( 'year' ) ) ; stop = true ; break ; case KeyboardKeys . END : this . setFocus ( this . focusDate . clone ( ) . endOf ( 'year' ) ) ; stop = true ; break ; case KeyboardKeys . PAGE_UP : this . moveFocus ( - 1 , 'year' ) ; stop = true ; break ; case KeyboardKeys . PAGE_DOWN : this . moveFocus ( 1 , 'year' ) ; stop = true ; break ; case KeyboardKeys . SPACE : setTimeout ( ( ) => { this . setFocus ( this . focusDate . clone ( ) ) ; } , 200 ) ; break ; case KeyboardKeys . ENTER : setTimeout ( ( ) => { this . dayKeyEnter . emit ( ) ; } , 200 ) ; break ; } } if ( stop ) { event . stopPropagation ( ) ; event . preventDefault ( ) ; } this . cdr . detectChanges ( ) ; } onYearDown ( event ) { let stop = false ; if ( this . currentView === CalendarView . Year ) { switch ( event . code ) { case KeyboardKeys . ARROW_DOWN : this . moveFocus ( 4 , 'year' ) ; stop = true ; break ; case KeyboardKeys . ARROW_UP : this . moveFocus ( - 4 , 'year' ) ; stop = true ; break ; case KeyboardKeys . ARROW_LEFT : this . moveFocus ( - 1 , 'year' ) ; stop = true ; break ; case KeyboardKeys . ARROW_RIGHT : this . moveFocus ( 1 , 'year' ) ; stop = true ; break ; case KeyboardKeys . PAGE_UP : this . moveFocus ( - 20 , 'year' ) ; stop = true ; break ; case KeyboardKeys . PAGE_DOWN : this . moveFocus ( 20 , 'year' ) ; stop = true ; break ; case KeyboardKeys . SPACE : setTimeout ( ( ) => { this . setFocus ( this . focusDate . clone ( ) ) ; } , 200 ) ; break ; case KeyboardKeys . ENTER : setTimeout ( ( ) => { this . dayKeyEnter . emit ( ) ; } , 200 ) ; break ; } } if ( stop ) { event . stopPropagation ( ) ; event . preventDefault ( ) ; } this . cdr . detectChanges ( ) ; } private onChangeCallback : ( _ : any ) => void = ( ) => { } ; private parseDate ( date ) { date = date instanceof Date ? date . toISOString ( ) : date ; return this . timezone ? moment . tz ( date , this . inputFormats , this . timezone ) : moment ( date , this . inputFormats ) ; } private createMoment ( date ) { const m = moment ( date ) . clone ( ) ; return this . timezone ? m . tz ( this . timezone ) : m ; } }