/* tslint:disable:no-forward-ref max-file-line-count */
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component, EventEmitter,
forwardRef,
Input,
OnChanges, Output,
SimpleChanges
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { TimepickerActions } from './reducer/timepicker.actions';
import { TimepickerStore } from './reducer/timepicker.store';
import { getControlsValue } from './timepicker-controls.util';
import { TimepickerConfig } from './timepicker.config';
import { TimeChangeSource, TimepickerComponentState, TimepickerControls } from './timepicker.models';
import { isValidDate, padNumber, parseTime, isInputValid } from './timepicker.utils';
export const TIMEPICKER_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
// tslint:disable-next-line
useExisting: forwardRef(() => TimepickerComponent),
multi: true
};
@Component({
selector: 'timepicker',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [TIMEPICKER_CONTROL_VALUE_ACCESSOR, TimepickerStore],
template: `
`
})
export class TimepickerComponent implements ControlValueAccessor, TimepickerComponentState, TimepickerControls, OnChanges {
/** 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 scroll inside hours and minutes inputs will change time */
@Input() mousewheel: boolean;
/** if true up/down arrowkeys inside hours and minutes inputs will change time */
@Input() arrowkeys: boolean;
/** if true spinner arrows above and below the inputs will be shown */
@Input() showSpinners: boolean;
@Input() showMeridian: boolean;
@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;
/** emits true if value is a valid date */
@Output() isValid: EventEmitter = new EventEmitter();
// ui variables
hours: string;
minutes: string;
seconds: string;
meridian: string;
get isSpinnersVisible(): boolean {
return this.showSpinners && !this.readonlyInput;
}
// min\max validation for input fields
invalidHours = false;
invalidMinutes = false;
invalidSeconds = false;
// time picker controls state
canIncrementHours: boolean;
canIncrementMinutes: boolean;
canIncrementSeconds: boolean;
canDecrementHours: boolean;
canDecrementMinutes: boolean;
canDecrementSeconds: boolean;
// control value accessor methods
onChange: any = Function.prototype;
onTouched: any = Function.prototype;
constructor(_config: TimepickerConfig,
private _cd: ChangeDetectorRef,
private _store: TimepickerStore,
private _timepickerActions: TimepickerActions) {
Object.assign(this, _config);
// todo: add unsubscribe
_store
.select((state) => state.value)
.subscribe((value) => {
// update UI values if date changed
this._renderTime(value);
this.onChange(value);
this._store.dispatch(this._timepickerActions.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();
});
}
isPM(): boolean {
return this.showMeridian && this.meridian === this.meridians[1];
}
prevDef($event: any) {
$event.preventDefault();
}
wheelSign($event: any): number {
return Math.sign($event.deltaY as number) * -1;
}
ngOnChanges(changes: SimpleChanges): void {
this._store.dispatch(this._timepickerActions.updateControls(getControlsValue(this)));
}
changeHours(step: number, source: TimeChangeSource = ''): void {
this._store.dispatch(this._timepickerActions.changeHours({step, source}));
}
changeMinutes(step: number, source: TimeChangeSource = ''): void {
this._store.dispatch(this._timepickerActions.changeMinutes({step, source}));
}
changeSeconds(step: number, source: TimeChangeSource = ''): void {
this._store.dispatch(this._timepickerActions.changeSeconds({step, source}));
}
updateHours(hours: string): void {
this.hours = hours;
this._updateTime();
}
updateMinutes(minutes: string) {
this.minutes = minutes;
this._updateTime();
}
updateSeconds(seconds: string) {
this.seconds = seconds;
this._updateTime();
}
_updateTime() {
if (!isInputValid(this.hours, this.minutes, this.seconds, this.isPM())) {
this.onChange(null);
return;
}
this._store.dispatch(this._timepickerActions
.setTime({
hour: this.hours,
minute: this.minutes,
seconds: this.seconds,
isPM: this.isPM()
}));
}
toggleMeridian(): void {
if (!this.showMeridian || this.readonlyInput) {
return;
}
const _hoursPerDayHalf = 12;
this._store.dispatch(this._timepickerActions.changeHours({step: _hoursPerDayHalf, source: ''}));
}
/**
* Write a new value to the element.
*/
writeValue(obj: any): void {
if (isValidDate(obj)) {
this._store.dispatch(this._timepickerActions.writeValue(parseTime(obj)));
}
}
/**
* Set the function to be called when the control receives a change event.
*/
registerOnChange(fn: (_: any) => {}): void {
this.onChange = fn;
}
/**
* Set the function to be called when the control receives a touch event.
*/
registerOnTouched(fn: () => {}): void {
this.onTouched = fn;
}
/**
* This function is called when the control status changes to or from "DISABLED".
* Depending on the value, it will enable or disable the appropriate DOM element.
*
* @param isDisabled
*/
setDisabledState(isDisabled: boolean): void {
this.readonlyInput = isDisabled;
}
private _renderTime(value: string | Date): void {
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;
// should be 12 PM, not 00 PM
if (_hours === 0) {
_hours = _hoursPerDayHalf;
}
}
this.hours = padNumber(_hours);
this.minutes = padNumber(_value.getMinutes());
this.seconds = padNumber(_value.getUTCSeconds());
}
}