import { ChangeDetectionStrategy , ChangeDetectorRef , Component , EventEmitter , forwardRef , Input , OnChanges , OnDestroy , Output , SimpleChanges , StaticProvider } from '@angular/core' ; import { ControlValueAccessor , NG_VALUE_ACCESSOR } from '@angular/forms' ; import { getControlsValue } from './time-picker-controls.util' ; import { TimePickerConfig } from './time-picker.config' ; import { TimeChangeSource , TimePickerComponentState , TimePickerControls } from './time-picker.models' ; import { isValidDate , padNumber , parseTime , isInputValid , isHourInputValid , isMinuteInputValid , isSecondInputValid , isInputLimitValid } from './time-picker.utils' ; import { Subscription } from 'rxjs' ; import { ThyTimePickerStore } from './time-picker.store' ; export const TIMEPICKER_CONTROL_VALUE_ACCESSOR = { provide : NG_VALUE_ACCESSOR , useExisting : forwardRef ( ( ) => ThyTimePickerComponent ) , multi : true } ; @ Component ( { selector : 'thy-time-picker' , changeDetection : ChangeDetectionStrategy . OnPush , providers : [ TIMEPICKER_CONTROL_VALUE_ACCESSOR , ThyTimePickerStore ] , templateUrl : './time-picker.component.html' } ) export class ThyTimePickerComponent implements ControlValueAccessor , TimePickerComponentState , TimePickerControls , OnChanges , OnDestroy { /** hours change step */ @ Input ( ) hourStep : number ; /** hours change step */ @ Input ( ) minuteStep : number ; /** seconds change step */ @ Input ( ) secondsStep : number ; /** if true hours and minutes fields will be readonly */ @ Input ( ) readonlyInput : boolean ; /** if true hours and minutes fields will be disabled */ @ Input ( ) disabled : boolean ; /** if true scroll inside hours and minutes inputs will change time */ @ Input ( ) mousewheel : boolean ; /** if true the values of hours and minutes can be changed using the up/down arrow keys on the keyboard */ @ Input ( ) arrowKeys : boolean ; /** if true spinner arrows above and below the inputs will be shown */ @ Input ( ) showSpinners : boolean ; /** if true meridian button will be shown */ @ Input ( ) showMeridian : boolean ; /** show minutes in timePicker */ @ Input ( ) showMinutes : boolean ; /** show seconds in timePicker */ @ Input ( ) showSeconds : boolean ; /** meridian labels based on locale */ @ Input ( ) meridians : string [ ] ; /** minimum time user can select */ @ Input ( ) min : Date ; /** maximum time user can select */ @ Input ( ) max : Date ; /** placeholder for hours field in timePicker */ @ Input ( ) hoursPlaceholder : string ; /** placeholder for minutes field in timePicker */ @ Input ( ) minutesPlaceholder : string ; /** placeholder for seconds field in timePicker */ @ Input ( ) secondsPlaceholder : string ; /** emits true if value is a valid date */ @ Output ( ) isValid = new EventEmitter < boolean > ( ) ; hours : string ; minutes : string ; seconds : string ; meridian : string ; get isEditable ( ) : boolean { return ! ( this . readonlyInput || this . disabled ) ; } invalidHours = false ; invalidMinutes = false ; invalidSeconds = false ; canIncrementHours : boolean ; canIncrementMinutes : boolean ; canIncrementSeconds : boolean ; canDecrementHours : boolean ; canDecrementMinutes : boolean ; canDecrementSeconds : boolean ; canToggleMeridian : boolean ; onChange = Function . prototype ; onTouched = Function . prototype ; timePickerSub : Subscription ; constructor ( _config , private _cd , private _store ) { Object . assign ( this , _config ) ; this . timePickerSub = _store . select ( state => state . value ) . subscribe ( ( value ) => { this . _renderTime ( value ) ; this . onChange ( value ) ; this . _store . updateControls ( getControlsValue ( this ) ) ; } ) ; _store . select ( state => state . controls ) . subscribe ( ( controlsState ) => { this . isValid . emit ( isInputValid ( this . hours , this . minutes , this . seconds , this . isPM ( ) ) ) ; Object . assign ( this , controlsState ) ; _cd . markForCheck ( ) ; } ) ; } resetValidation ( ) { this . invalidHours = false ; this . invalidMinutes = false ; this . invalidSeconds = false ; } isPM ( ) { return this . showMeridian && this . meridian === this . meridians [ 1 ] ; } prevDef ( $event ) { $event . preventDefault ( ) ; } wheelSign ( $event ) { return Math . sign ( $event . deltaY ) * - 1 ; } ngOnChanges ( changes ) { this . _store . updateControls ( getControlsValue ( this ) ) ; } changeHours ( step , source = '' ) { this . resetValidation ( ) ; this . _store . changeHours ( { step , source } ) ; } changeMinutes ( step , source = '' ) { this . resetValidation ( ) ; this . _store . changeMinutes ( { step , source } ) ; } changeSeconds ( step , source = '' ) { this . resetValidation ( ) ; this . _store . changeSeconds ( { step , source } ) ; } updateHours ( hours ) { this . resetValidation ( ) ; this . hours = hours ; const isValid = isHourInputValid ( this . hours , this . isPM ( ) ) && this . isValidLimit ( ) ; if ( ! isValid ) { this . invalidHours = true ; this . isValid . emit ( false ) ; this . onChange ( null ) ; return ; } this . _updateTime ( ) ; } updateMinutes ( minutes ) { this . resetValidation ( ) ; this . minutes = minutes ; const isValid = isMinuteInputValid ( this . minutes ) && this . isValidLimit ( ) ; if ( ! isValid ) { this . invalidMinutes = true ; this . isValid . emit ( false ) ; this . onChange ( null ) ; return ; } this . _updateTime ( ) ; } updateSeconds ( seconds ) { this . resetValidation ( ) ; this . seconds = seconds ; const isValid = isSecondInputValid ( this . seconds ) && this . isValidLimit ( ) ; if ( ! isValid ) { this . invalidSeconds = true ; this . isValid . emit ( false ) ; this . onChange ( null ) ; return ; } this . _updateTime ( ) ; } isValidLimit ( ) { return isInputLimitValid ( { hour : this . hours , minute : this . minutes , seconds : this . seconds , isPM : this . isPM ( ) } , this . max , this . min ) ; } _updateTime ( ) { const _seconds = this . showSeconds ? this . seconds : void 0 ; const _minutes = this . showMinutes ? this . minutes : void 0 ; if ( ! isInputValid ( this . hours , _minutes , _seconds , this . isPM ( ) ) ) { this . isValid . emit ( false ) ; this . onChange ( null ) ; return ; } this . _store . setTime ( { hour : this . hours , minute : this . minutes , seconds : this . seconds , isPM : this . isPM ( ) } ) ; } toggleMeridian ( ) { if ( ! this . showMeridian || ! this . isEditable ) { return ; } const _hoursPerDayHalf = 12 ; this . _store . changeHours ( { step : _hoursPerDayHalf , source : '' } ) ; } writeValue ( obj ) { if ( isValidDate ( obj ) ) { this . _store . writeValue ( parseTime ( obj ) ) ; } else if ( obj == null ) { this . _store . writeValue ( null ) ; } } registerOnChange ( fn ) { this . onChange = fn ; } registerOnTouched ( fn ) { this . onTouched = fn ; } setDisabledState ( isDisabled ) { this . disabled = isDisabled ; this . _cd . markForCheck ( ) ; } ngOnDestroy ( ) { this . timePickerSub . unsubscribe ( ) ; } private _renderTime ( value ) { if ( ! isValidDate ( value ) ) { this . hours = '' ; this . minutes = '' ; this . seconds = '' ; this . meridian = this . meridians [ 0 ] ; return ; } const _value = parseTime ( value ) ; const _hoursPerDayHalf = 12 ; let _hours = _value . getHours ( ) ; if ( this . showMeridian ) { this . meridian = this . meridians [ _hours >= _hoursPerDayHalf ? 1 : 0 ] ; _hours = _hours % _hoursPerDayHalf ; if ( _hours === 0 ) { _hours = _hoursPerDayHalf ; } } this . hours = padNumber ( _hours ) ; this . minutes = padNumber ( _value . getMinutes ( ) ) ; this . seconds = padNumber ( _value . getUTCSeconds ( ) ) ; } }