diff --git a/demo/src/app/app.module.ts b/demo/src/app/app.module.ts
index a41fb67474..80dc539422 100644
--- a/demo/src/app/app.module.ts
+++ b/demo/src/app/app.module.ts
@@ -1,6 +1,6 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
-import { FormsModule } from '@angular/forms';
+import { FormsModule} from '@angular/forms';
import { RouterModule } from '@angular/router';
import { Ng2PageScrollModule } from 'ng2-page-scroll/ng2-page-scroll';
import { AppComponent } from './app.component';
diff --git a/demo/src/app/components/+timepicker/demo-timepicker.module.ts b/demo/src/app/components/+timepicker/demo-timepicker.module.ts
index 12104708c3..7d391f4280 100644
--- a/demo/src/app/components/+timepicker/demo-timepicker.module.ts
+++ b/demo/src/app/components/+timepicker/demo-timepicker.module.ts
@@ -1,6 +1,6 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
-import { FormsModule } from '@angular/forms';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { TimepickerModule } from 'ngx-bootstrap/timepicker';
@@ -17,6 +17,7 @@ import { routes } from './demo-timepicker.routes';
imports: [
CommonModule,
FormsModule,
+ ReactiveFormsModule,
SharedModule,
TimepickerModule.forRoot(),
RouterModule.forChild(routes)
diff --git a/demo/src/app/components/+timepicker/demos/custom-validation/custom-validation.html b/demo/src/app/components/+timepicker/demos/custom-validation/custom-validation.html
new file mode 100644
index 0000000000..31d8e317ea
--- /dev/null
+++ b/demo/src/app/components/+timepicker/demos/custom-validation/custom-validation.html
@@ -0,0 +1,7 @@
+
Illustrates custom validation, you have to select time between 11:00 and 12:59
+
+
+
+
+
+Time is: {{myTime}}
diff --git a/demo/src/app/components/+timepicker/demos/custom-validation/custom-validation.ts b/demo/src/app/components/+timepicker/demos/custom-validation/custom-validation.ts
new file mode 100644
index 0000000000..3b8b9d54eb
--- /dev/null
+++ b/demo/src/app/components/+timepicker/demos/custom-validation/custom-validation.ts
@@ -0,0 +1,14 @@
+import { Component } from '@angular/core';
+import { FormControl } from '@angular/forms';
+
+@Component({
+ selector: 'demo-timepicker-custom-validation',
+ templateUrl: './custom-validation.html'
+})
+export class DemoTimepickerCustomValidationComponent {
+ public myTime: Date;
+
+ public ctrl = new FormControl('', (control: FormControl) => {
+ const value = control.value;
+ console.log('control', control);
+}
diff --git a/demo/src/app/components/+timepicker/demos/index.ts b/demo/src/app/components/+timepicker/demos/index.ts
index 3fcb2e56c5..0e6d204e32 100644
--- a/demo/src/app/components/+timepicker/demos/index.ts
+++ b/demo/src/app/components/+timepicker/demos/index.ts
@@ -4,10 +4,16 @@ import { DemoTimepickerMeridianComponent } from './meridian/meridian';
import { DemoTimepickerDisabledComponent } from './disabled/disabled';
import { DemoTimepickerCustomComponent } from './custom/custom';
import { DemoTimepickerDynamicComponent } from './dynamic/dynamic';
+import { DemoTimepickerMinMaxComponent } from './min-max/min-max';
+import { DemoTimepickerSecondsComponent } from './seconds/seconds';
+import { DemoTimepickerMousewheelArrowkeysComponent } from './mousewheel-arrowkeys/mousewheel-arrowkeys';
+import { DemoTimepickerCustomValidationComponent } from './custom-validation/custom-validation';
export const DEMO_COMPONENTS = [
DemoTimepickerBasicComponent, DemoTimepickerConfigComponent, DemoTimepickerMeridianComponent,
- DemoTimepickerDisabledComponent, DemoTimepickerCustomComponent, DemoTimepickerDynamicComponent
+ DemoTimepickerMinMaxComponent, DemoTimepickerDisabledComponent, DemoTimepickerCustomComponent,
+ DemoTimepickerDynamicComponent, DemoTimepickerSecondsComponent, DemoTimepickerMousewheelArrowkeysComponent,
+ DemoTimepickerCustomValidationComponent
];
export const DEMOS = {
@@ -19,6 +25,10 @@ export const DEMOS = {
component: require('!!raw-loader?lang=typescript!./meridian/meridian'),
html: require('!!raw-loader?lang=markup!./meridian/meridian.html')
},
+ minmax: {
+ component: require('!!raw-loader?lang=typescript!./min-max/min-max'),
+ html: require('!!raw-loader?lang=markup!./min-max/min-max.html')
+ },
disabled: {
component: require('!!raw-loader?lang=typescript!./disabled/disabled'),
html: require('!!raw-loader?lang=markup!./disabled/disabled.html')
@@ -34,5 +44,17 @@ export const DEMOS = {
config: {
component: require('!!raw-loader?lang=typescript!./config/config'),
html: require('!!raw-loader?lang=markup!./config/config.html')
+ },
+ seconds: {
+ component: require('!!raw-loader?lang=typescript!./seconds/seconds'),
+ html: require('!!raw-loader?lang=markup!./seconds/seconds.html')
+ },
+ mousewheel: {
+ component: require('!!raw-loader?lang=typescript!./mousewheel-arrowkeys/mousewheel-arrowkeys'),
+ html: require('!!raw-loader?lang=markup!./mousewheel-arrowkeys/mousewheel-arrowkeys.html')
+ },
+ customvalidation: {
+ component: require('!!raw-loader?lang=typescript!./custom-validation/custom-validation'),
+ html: require('!!raw-loader?lang=markup!./custom-validation/custom-validation.html')
}
};
diff --git a/demo/src/app/components/+timepicker/demos/meridian/meridian.html b/demo/src/app/components/+timepicker/demos/meridian/meridian.html
index ff60681c2c..53229bb148 100644
--- a/demo/src/app/components/+timepicker/demos/meridian/meridian.html
+++ b/demo/src/app/components/+timepicker/demos/meridian/meridian.html
@@ -2,7 +2,12 @@
Time is: {{mytime}}
-
+
+
+
+
+
+Time is: {{mytime2}}
diff --git a/demo/src/app/components/+timepicker/demos/meridian/meridian.ts b/demo/src/app/components/+timepicker/demos/meridian/meridian.ts
index 76e7a37165..815b0a287d 100644
--- a/demo/src/app/components/+timepicker/demos/meridian/meridian.ts
+++ b/demo/src/app/components/+timepicker/demos/meridian/meridian.ts
@@ -5,12 +5,15 @@ import { Component } from '@angular/core';
templateUrl: './meridian.html'
})
export class DemoTimepickerMeridianComponent {
- public ismeridian:boolean = true;
+ public ismeridian: boolean = true;
- public mytime:Date = new Date();
+ public mytime: Date = new Date();
- public toggleMode():void {
+ public mytime2: Date = new Date();
+
+ public meridianText = ['12h', '24h'];
+
+ public toggleMode(): void {
this.ismeridian = !this.ismeridian;
}
-
}
diff --git a/demo/src/app/components/+timepicker/demos/min-max/min-max.html b/demo/src/app/components/+timepicker/demos/min-max/min-max.html
new file mode 100644
index 0000000000..ae707b97c8
--- /dev/null
+++ b/demo/src/app/components/+timepicker/demos/min-max/min-max.html
@@ -0,0 +1,3 @@
+
+
+Time is: {{myTime}}
diff --git a/demo/src/app/components/+timepicker/demos/min-max/min-max.ts b/demo/src/app/components/+timepicker/demos/min-max/min-max.ts
new file mode 100644
index 0000000000..60493f10b0
--- /dev/null
+++ b/demo/src/app/components/+timepicker/demos/min-max/min-max.ts
@@ -0,0 +1,18 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'demo-timepicker-min-max',
+ templateUrl: './min-max.html'
+})
+export class DemoTimepickerMinMaxComponent {
+ public myTime: Date = new Date();
+ public minTime: Date = new Date();
+ public maxTime: Date = new Date();
+
+ constructor() {
+ this.minTime.setHours(8);
+ this.minTime.setMinutes(0);
+ this.maxTime.setHours(17);
+ this.maxTime.setMinutes(0);
+ }
+}
diff --git a/demo/src/app/components/+timepicker/demos/mousewheel-arrowkeys/mousewheel-arrowkeys.html b/demo/src/app/components/+timepicker/demos/mousewheel-arrowkeys/mousewheel-arrowkeys.html
new file mode 100644
index 0000000000..55a4a556ed
--- /dev/null
+++ b/demo/src/app/components/+timepicker/demos/mousewheel-arrowkeys/mousewheel-arrowkeys.html
@@ -0,0 +1,11 @@
+Without mousewheel
+
+
+
+Time is: {{myTime1}}
+
+Without arrowkeys
+
+
+
+Time is: {{myTime2}}
diff --git a/demo/src/app/components/+timepicker/demos/mousewheel-arrowkeys/mousewheel-arrowkeys.ts b/demo/src/app/components/+timepicker/demos/mousewheel-arrowkeys/mousewheel-arrowkeys.ts
new file mode 100644
index 0000000000..acad0f6a12
--- /dev/null
+++ b/demo/src/app/components/+timepicker/demos/mousewheel-arrowkeys/mousewheel-arrowkeys.ts
@@ -0,0 +1,10 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'demo-timepicker-mousewheel-arrowkeys',
+ templateUrl: './mousewheel-arrowkeys.html'
+})
+export class DemoTimepickerMousewheelArrowkeysComponent {
+ public myTime1: Date = new Date();
+ public myTime2: Date = new Date();
+}
diff --git a/demo/src/app/components/+timepicker/demos/seconds/seconds.html b/demo/src/app/components/+timepicker/demos/seconds/seconds.html
new file mode 100644
index 0000000000..33e9d95e40
--- /dev/null
+++ b/demo/src/app/components/+timepicker/demos/seconds/seconds.html
@@ -0,0 +1,3 @@
+
+
+Time is: {{myTime}}
diff --git a/demo/src/app/components/+timepicker/demos/seconds/seconds.ts b/demo/src/app/components/+timepicker/demos/seconds/seconds.ts
new file mode 100644
index 0000000000..1ffc772eea
--- /dev/null
+++ b/demo/src/app/components/+timepicker/demos/seconds/seconds.ts
@@ -0,0 +1,10 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'demo-timepicker-seconds',
+ templateUrl: './seconds.html'
+})
+export class DemoTimepickerSecondsComponent {
+ public myTime: Date = new Date();
+ public showSec: boolean = true;
+}
diff --git a/demo/src/app/components/+timepicker/timepicker-section.component.ts b/demo/src/app/components/+timepicker/timepicker-section.component.ts
index e40b400339..29dfee9fb7 100644
--- a/demo/src/app/components/+timepicker/timepicker-section.component.ts
+++ b/demo/src/app/components/+timepicker/timepicker-section.component.ts
@@ -16,10 +16,14 @@ let titleDoc = require('html-loader!markdown-loader!./docs/title.md');
API Reference
@@ -45,6 +49,16 @@ let titleDoc = require('html-loader!markdown-loader!./docs/title.md');
+
+ Min - Max
+
+
+
+
+ Show seconds
+
+
+
Disabled
@@ -54,6 +68,11 @@ let titleDoc = require('html-loader!markdown-loader!./docs/title.md');
Custom steps
+
+
+ Custom validation
+
+
Dynamic
@@ -65,6 +84,11 @@ let titleDoc = require('html-loader!markdown-loader!./docs/title.md');
+
+ Mouse wheel and Arrow keys
+
+
+
API Reference
diff --git a/src/mini-ngrx/index.ts b/src/mini-ngrx/index.ts
new file mode 100644
index 0000000000..769b69d140
--- /dev/null
+++ b/src/mini-ngrx/index.ts
@@ -0,0 +1,16 @@
+export interface Action {
+ type: string;
+ payload?: any;
+}
+
+export type ActionReducer = (state: T, action: Action) => T;
+
+export { MiniState } from './state.class';
+export { MiniStore } from './store.class';
+
+/**
+ * sample usage class
+ *
+ *
+ *
+ */
diff --git a/src/mini-ngrx/state.class.ts b/src/mini-ngrx/state.class.ts
new file mode 100644
index 0000000000..b5c9f55f83
--- /dev/null
+++ b/src/mini-ngrx/state.class.ts
@@ -0,0 +1,23 @@
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+import { Observable } from 'rxjs/Observable';
+import { Action, ActionReducer } from './index';
+import { observeOn } from 'rxjs/operator/observeOn';
+import { queue } from 'rxjs/scheduler/queue';
+import { scan } from 'rxjs/operator/scan';
+
+export class MiniState extends BehaviorSubject {
+ constructor(_initialState: T, actionsDispatcher$: Observable, reducer: ActionReducer) {
+ super(_initialState);
+
+ const actionInQueue$ = observeOn.call(actionsDispatcher$, queue);
+ const state$ = scan.call(actionInQueue$, (state: T, action: Action) => {
+ if (!action) {
+ return state;
+ }
+
+ return reducer(state, action);
+ }, _initialState);
+
+ state$.subscribe((value: T) => this.next(value));
+ }
+}
diff --git a/src/mini-ngrx/store.class.ts b/src/mini-ngrx/store.class.ts
new file mode 100644
index 0000000000..3a5b33a7a7
--- /dev/null
+++ b/src/mini-ngrx/store.class.ts
@@ -0,0 +1,39 @@
+import { Observable } from 'rxjs/Observable';
+import { Observer } from 'rxjs/Observer';
+import { Operator } from 'rxjs/Operator';
+import { distinctUntilChanged } from 'rxjs/operator/distinctUntilChanged';
+
+import { map } from 'rxjs/operator/map';
+import { Action, ActionReducer } from './index';
+
+export class MiniStore extends Observable implements Observer {
+
+ constructor(private _dispatcher: Observer,
+ private _reducer: ActionReducer,
+ state$: Observable) {
+ super();
+
+ this.source = state$;
+ }
+
+ select(pathOrMapFn: (state: T) => R): Observable {
+ const mapped$: Observable = map.call(this, pathOrMapFn);
+
+ return distinctUntilChanged.call(mapped$);
+ }
+
+ lift(operator: Operator): MiniStore {
+ const store = new MiniStore(this._dispatcher, this._reducer, this);
+ store.operator = operator;
+
+ return store;
+ }
+
+ dispatch(action: Action) { this._dispatcher.next(action); }
+
+ next(action: Action) { this._dispatcher.next(action); }
+
+ error(err: any) { this._dispatcher.error(err); }
+
+ complete() {/*noop*/}
+}
diff --git a/src/old-timepicker/index.ts b/src/old-timepicker/index.ts
new file mode 100644
index 0000000000..fe8a961676
--- /dev/null
+++ b/src/old-timepicker/index.ts
@@ -0,0 +1,3 @@
+export { TimepickerConfig } from './timepicker.config';
+export { TimepickerComponent } from './timepicker.component';
+export { TimepickerModule } from './timepicker.module';
diff --git a/src/old-timepicker/timepicker.component.ts b/src/old-timepicker/timepicker.component.ts
new file mode 100644
index 0000000000..2815233587
--- /dev/null
+++ b/src/old-timepicker/timepicker.component.ts
@@ -0,0 +1,386 @@
+// tslint:disable max-file-line-count
+import { Component, Input, OnInit, forwardRef } from '@angular/core';
+import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
+import { TimepickerConfig } from './timepicker.config';
+
+export const TIMEPICKER_CONTROL_VALUE_ACCESSOR: any = {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => TimepickerComponent),
+ multi: true
+};
+
+// todo: refactor directive has to many functions! (extract to stateless helper)
+// todo: use moment js?
+// todo: implement `time` validator
+// todo: replace increment/decrement blockers with getters, or extract
+// todo: unify work with selected
+function isDefined(value: any): boolean {
+ return typeof value !== 'undefined';
+}
+
+function addMinutes(date: any, minutes: number): Date {
+ let dt = new Date(date.getTime() + minutes * 60000);
+ let newDate = new Date(date);
+ newDate.setHours(dt.getHours(), dt.getMinutes());
+ return newDate;
+}
+
+@Component({
+ selector: 'timepicker',
+ template: `
+
+ `,
+ providers: [TIMEPICKER_CONTROL_VALUE_ACCESSOR]
+})
+export class TimepickerComponent implements ControlValueAccessor, OnInit {
+ /** hours change step */
+ @Input() public hourStep: number;
+ /** hours change step */
+ @Input() public minuteStep: number;
+ /** if true hours and minutes fields will be readonly */
+ @Input() public readonlyInput: boolean;
+ /** if true scroll inside hours and minutes inputs will change time */
+ @Input() public mousewheel: boolean;
+ /** if true up/down arrowkeys inside hours and minutes inputs will change time */
+ @Input() public arrowkeys: boolean;
+ /** if true spinner arrows above and below the inputs will be shown */
+ @Input() public showSpinners: boolean;
+ /** minimum time user can select */
+ @Input() public min: Date;
+ /** maximum time user can select */
+ @Input() public max: Date;
+ /** meridian labels based on locale */
+ @Input() public meridians: string[];
+
+ /** if true works in 12H mode and displays AM/PM. If false works in 24H mode and hides AM/PM */
+ @Input()
+ public get showMeridian(): boolean {
+ return this._showMeridian;
+ }
+
+ public set showMeridian(value: boolean) {
+ this._showMeridian = value;
+ // || !this.$error.time
+ // if (true) {
+ this.updateTemplate();
+ return;
+ // }
+ // Evaluate from template
+ /*let hours = this.getHoursFromTemplate();
+ let minutes = this.getMinutesFromTemplate();
+ if (isDefined(hours) && isDefined(minutes)) {
+ this.selected.setHours(hours);
+ this.refresh();
+ }*/
+ }
+
+ public onChange: any = Function.prototype;
+ public onTouched: any = Function.prototype;
+
+ // input values
+ public hours: string;
+ public minutes: string;
+
+ // validation
+ public invalidHours: any;
+ public invalidMinutes: any;
+
+ public meridian: any; // ??
+
+ // result value
+ protected _selected: Date = new Date();
+ protected _showMeridian: boolean;
+
+ protected get selected(): Date {
+ return this._selected;
+ }
+
+ protected set selected(v: Date) {
+ if (v) {
+ this._selected = v;
+ this.updateTemplate();
+ this.onChange(this.selected);
+ }
+ }
+
+ protected config: TimepickerConfig;
+
+ public constructor(_config: TimepickerConfig) {
+ this.config = _config;
+ Object.assign(this, _config);
+ }
+
+ // todo: add formatter value to Date object
+ public ngOnInit(): void {
+ // todo: take in account $locale.DATETIME_FORMATS.AMPMS;
+ if (this.mousewheel) {
+ // this.setupMousewheelEvents();
+ }
+
+ if (this.arrowkeys) {
+ // this.setupArrowkeyEvents();
+ }
+
+ // this.setupInputEvents();
+ }
+
+ public writeValue(v: any): void {
+ if (v === this.selected) {
+ return;
+ }
+ if (v && v instanceof Date) {
+ this.selected = v;
+ return;
+ }
+ this.selected = v ? new Date(v) : void 0;
+ }
+
+ public registerOnChange(fn: (_: any) => {}): void {
+ this.onChange = fn;
+ }
+
+ public registerOnTouched(fn: () => {}): void {
+ this.onTouched = fn;
+ }
+
+ public setDisabledState(isDisabled: boolean): void {
+ this.readonlyInput = isDisabled;
+ }
+
+ public updateHours(): void {
+ if (this.readonlyInput) {
+ return;
+ }
+
+ let hours = this.getHoursFromTemplate();
+ let minutes = this.getMinutesFromTemplate();
+ this.invalidHours = !isDefined(hours);
+ this.invalidMinutes = !isDefined(minutes);
+
+ if (this.invalidHours || this.invalidMinutes) {
+ // TODO: needed a validation functionality.
+ return;
+ // todo: validation?
+ // invalidate(true);
+ }
+
+ this.selected.setHours(hours);
+ this.invalidHours = (this.selected < this.min || this.selected > this.max);
+ if (this.invalidHours) {
+ // todo: validation?
+ // invalidate(true);
+ return;
+ } else {
+ this.refresh(/*'h'*/);
+ }
+ }
+
+ public hoursOnBlur(): void {
+ if (this.readonlyInput) {
+ return;
+ }
+
+ // todo: binded with validation
+ if (!this.invalidHours && parseInt(this.hours, 10) < 10) {
+ this.hours = this.pad(this.hours);
+ }
+ }
+
+ public updateMinutes(): void {
+ if (this.readonlyInput) {
+ return;
+ }
+
+ let minutes = this.getMinutesFromTemplate();
+ let hours = this.getHoursFromTemplate();
+ this.invalidMinutes = !isDefined(minutes);
+ this.invalidHours = !isDefined(hours);
+
+ if (this.invalidMinutes || this.invalidHours) {
+ // TODO: needed a validation functionality.
+ return;
+ // todo: validation
+ // invalidate(undefined, true);
+ }
+
+ this.selected.setMinutes(minutes);
+ this.invalidMinutes = (this.selected < this.min || this.selected > this.max);
+ if (this.invalidMinutes) {
+ // todo: validation
+ // invalidate(undefined, true);
+ return;
+ } else {
+ this.refresh(/*'m'*/);
+ }
+ }
+
+ public minutesOnBlur(): void {
+ if (this.readonlyInput) {
+ return;
+ }
+
+ if (!this.invalidMinutes && parseInt(this.minutes, 10) < 10) {
+ this.minutes = this.pad(this.minutes);
+ }
+ }
+
+ public incrementHours(): void {
+ if (!this.noIncrementHours()) {
+ this.addMinutesToSelected(this.hourStep * 60);
+ }
+ }
+
+ public decrementHours(): void {
+ if (!this.noDecrementHours()) {
+ this.addMinutesToSelected(-this.hourStep * 60);
+ }
+ }
+
+ public incrementMinutes(): void {
+ if (!this.noIncrementMinutes()) {
+ this.addMinutesToSelected(this.minuteStep);
+ }
+ }
+
+ public decrementMinutes(): void {
+ if (!this.noDecrementMinutes()) {
+ this.addMinutesToSelected(-this.minuteStep);
+ }
+ }
+
+ public noIncrementHours(): boolean {
+ let incrementedSelected = addMinutes(this.selected, this.hourStep * 60);
+ return incrementedSelected > this.max ||
+ (incrementedSelected < this.selected && incrementedSelected < this.min);
+ }
+
+ public noDecrementHours(): boolean {
+ let decrementedSelected = addMinutes(this.selected, -this.hourStep * 60);
+ return decrementedSelected < this.min ||
+ (decrementedSelected > this.selected && decrementedSelected > this.max);
+ }
+
+ public noIncrementMinutes(): boolean {
+ let incrementedSelected = addMinutes(this.selected, this.minuteStep);
+ return incrementedSelected > this.max ||
+ (incrementedSelected < this.selected && incrementedSelected < this.min);
+ }
+
+ public noDecrementMinutes(): boolean {
+ let decrementedSelected = addMinutes(this.selected, -this.minuteStep);
+ return decrementedSelected < this.min ||
+ (decrementedSelected > this.selected && decrementedSelected > this.max);
+
+ }
+
+ public toggleMeridian(): void {
+ if (!this.noToggleMeridian()) {
+ let sign = this.selected.getHours() < 12 ? 1 : -1;
+ this.addMinutesToSelected(12 * 60 * sign);
+ }
+ }
+
+ public noToggleMeridian(): boolean {
+ if (this.readonlyInput) {
+ return true;
+ }
+
+ if (this.selected.getHours() < 13) {
+ return addMinutes(this.selected, 12 * 60) > this.max;
+ } else {
+ return addMinutes(this.selected, -12 * 60) < this.min;
+ }
+ }
+
+ protected refresh(/*type?:string*/): void {
+ // this.makeValid();
+ this.updateTemplate();
+ this.onChange(this.selected);
+ }
+
+ protected updateTemplate(/*keyboardChange?:any*/): void {
+ let hours = this.selected.getHours();
+ let minutes = this.selected.getMinutes();
+
+ if (this.showMeridian) {
+ // Convert 24 to 12 hour system
+ hours = (hours === 0 || hours === 12) ? 12 : hours % 12;
+ }
+
+ // this.hours = keyboardChange === 'h' ? hours : this.pad(hours);
+ // if (keyboardChange !== 'm') {
+ // this.minutes = this.pad(minutes);
+ // }
+ this.hours = this.pad(hours);
+ this.minutes = this.pad(minutes);
+
+ if (!this.meridians) {
+ this.meridians = this.config.meridians;
+ }
+
+ this.meridian = this.selected.getHours() < 12
+ ? this.meridians[0]
+ : this.meridians[1];
+ }
+
+ protected getHoursFromTemplate(): number {
+ let hours = parseInt(this.hours, 10);
+ let valid = this.showMeridian
+ ? (hours > 0 && hours < 13)
+ : (hours >= 0 && hours < 24);
+ if (!valid) {
+ return void 0;
+ }
+
+ if (this.showMeridian) {
+ if (hours === 12) {
+ hours = 0;
+ }
+ if (this.meridian === this.meridians[1]) {
+ hours = hours + 12;
+ }
+ }
+ return hours;
+ }
+
+ protected getMinutesFromTemplate(): number {
+ let minutes = parseInt(this.minutes, 10);
+ return (minutes >= 0 && minutes < 60) ? minutes : undefined;
+ }
+
+ protected pad(value: string|number): string {
+ return (isDefined(value) && value.toString().length < 2)
+ ? '0' + value
+ : value.toString();
+ }
+
+ protected addMinutesToSelected(minutes: any): void {
+ this.selected = addMinutes(this.selected, minutes);
+ this.refresh();
+ }
+}
diff --git a/src/old-timepicker/timepicker.config.ts b/src/old-timepicker/timepicker.config.ts
new file mode 100644
index 0000000000..d976e4a588
--- /dev/null
+++ b/src/old-timepicker/timepicker.config.ts
@@ -0,0 +1,26 @@
+import { Injectable } from '@angular/core';
+
+/** Provides default configuration values for timepicker */
+@Injectable()
+export class TimepickerConfig {
+ /** hours change step */
+ public hourStep: number = 1;
+ /** hours change step */
+ public minuteStep: number = 5;
+ /** if true works in 12H mode and displays AM/PM. If false works in 24H mode and hides AM/PM */
+ public showMeridian: boolean = true;
+ /** meridian labels based on locale */
+ public meridians:string[] = ['AM', 'PM'];
+ /** if true hours and minutes fields will be readonly */
+ public readonlyInput: boolean = false;
+ /** if true scroll inside hours and minutes inputs will change time */
+ public mousewheel: boolean = true;
+ /** if true up/down arrowkeys inside hours and minutes inputs will change time */
+ public arrowkeys: boolean = true;
+ /** if true spinner arrows above and below the inputs will be shown */
+ public showSpinners: boolean = true;
+ /** minimum time user can select */
+ public min: number = void 0;
+ /** maximum time user can select */
+ public max: number = void 0;
+}
diff --git a/src/old-timepicker/timepicker.module.ts b/src/old-timepicker/timepicker.module.ts
new file mode 100644
index 0000000000..3c39a0b4cc
--- /dev/null
+++ b/src/old-timepicker/timepicker.module.ts
@@ -0,0 +1,19 @@
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { NgModule, ModuleWithProviders } from '@angular/core';
+import { TimepickerComponent } from './timepicker.component';
+import { TimepickerConfig } from './timepicker.config';
+
+@NgModule({
+ imports: [CommonModule, FormsModule],
+ declarations: [TimepickerComponent],
+ exports: [TimepickerComponent, FormsModule]
+})
+export class TimepickerModule {
+ public static forRoot(): ModuleWithProviders {
+ return {
+ ngModule: TimepickerModule,
+ providers: [TimepickerConfig]
+ };
+ }
+}
diff --git a/src/spec/timepicker/timepicker.component.spec.ts b/src/spec/timepicker/timepicker.component.spec.ts
new file mode 100644
index 0000000000..72fbccdfab
--- /dev/null
+++ b/src/spec/timepicker/timepicker.component.spec.ts
@@ -0,0 +1,175 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TimepickerComponent } from '../../timepicker/timepicker.component';
+import { TimepickerConfig } from '../../timepicker/timepicker.config';
+import { TimepickerActions } from '../../timepicker/reducer/timepicker.actions';
+import { Data } from '@angular/router';
+
+fdescribe('Component: timepicker', () => {
+ let fixture: ComponentFixture;
+ let context: TimepickerComponent;
+ let nativeEl: any;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ declarations: [TimepickerComponent],
+ providers: [
+ TimepickerConfig,
+ TimepickerActions
+ ]
+ });
+
+ fixture = TestBed.createComponent(TimepickerComponent);
+ context = fixture.componentInstance;
+ nativeEl = fixture.nativeElement;
+ fixture.detectChanges();
+ });
+
+ it('should prevDef', () => {
+ context.prevDef(new Event('customWheel'));
+ });
+
+ it('should wheelSign', () => {
+ context.wheelSign(new Event('customWheel'));
+ });
+
+ it('should canBeChanged wheel', () => {
+ context.mousewheel = false;
+ context.canBeChanged('wheel');
+ });
+
+ it('should canBeChanged key', () => {
+ context.arrowkeys = false;
+ context.canBeChanged('key');
+ });
+
+ it('should canBeChanged true', () => {
+ context.readonlyInput = true;
+ context.canBeChanged();
+ });
+
+ it('should changeHours', () => {
+ context.changeHours(3);
+ });
+
+ it('should changeHours canIncrementHours', () => {
+ context.canIncrementHours = false;
+ context.changeHours(3);
+ });
+
+ it('should changeHours canDecrementHours', () => {
+ context.canDecrementHours = false;
+ context.changeHours(-3);
+ });
+
+ it('should changeHours wheel', () => {
+ context.mousewheel = false;
+ context.changeHours(3, 'wheel');
+ });
+
+ it('should changeMinutes', () => {
+ context.changeMinutes(3);
+ });
+
+ it('should changeMinutes canIncrementHours', () => {
+ context.canIncrementMinutes = false;
+ context.changeMinutes(3);
+ });
+
+ it('should changeMinutes canDecrementHours', () => {
+ context.canDecrementMinutes = false;
+ context.changeMinutes(-3);
+ });
+
+ it('should changeMinutes wheel', () => {
+ context.mousewheel = false;
+ context.changeMinutes(3, 'wheel');
+ });
+
+ it('should changeSeconds', () => {
+ context.changeSeconds(3);
+ });
+
+ it('should changeSeconds canIncrementHours', () => {
+ context.canIncrementSeconds = false;
+ context.changeSeconds(3);
+ });
+
+ it('should changeSeconds canDecrementHours', () => {
+ context.canDecrementSeconds = false;
+ context.changeSeconds(-3);
+ });
+
+ it('should changeSeconds wheel', () => {
+ context.mousewheel = false;
+ context.changeSeconds(3, 'wheel');
+ });
+
+ it('should updateHours', () => {
+ context.readonlyInput = true;
+ context.updateHours('0');
+ });
+
+ it('should updateHours', () => {
+ context.updateHours('');
+ context.updateHours('-1');
+ context.updateHours('1');
+ });
+
+ it('should updateMinutes', () => {
+ context.readonlyInput = true;
+ context.updateMinutes('0');
+ });
+
+ it('should updateMinutes', () => {
+ context.updateMinutes('');
+ context.updateMinutes('-1');
+ context.updateMinutes('1');
+ });
+
+ it('should updateSeconds', () => {
+ context.readonlyInput = true;
+ context.updateSeconds('0');
+ });
+
+ it('should updateSeconds', () => {
+ context.updateSeconds('');
+ context.updateSeconds('-1');
+ context.updateSeconds('1');
+ });
+
+ it('should toggleMeridian true', () => {
+ context.showMeridian = true;
+ context.toggleMeridian();
+ });
+
+ it('should toggleMeridian false', () => {
+ context.showMeridian = false;
+ context.toggleMeridian();
+ });
+
+ it('should writeValue', () => {
+ context.writeValue('1');
+ });
+
+ it('should writeValue', () => {
+ context.writeValue('');
+ });
+
+ it('should registerOnChange', () => {
+ context.registerOnChange((val: any) => val);
+ });
+
+ it('should registerOnTouched', () => {
+ context.registerOnTouched(() => true);
+ });
+
+ it('should setDisabledState', () => {
+ context.setDisabledState(true);
+ });
+
+ it('should showMeridian change', () => {
+ context.showMeridian = false;
+ (context as any)._renderTime('-1');
+ });
+});
diff --git a/src/timepicker/index.ts b/src/timepicker/index.ts
index fe8a961676..9a3e8c1661 100644
--- a/src/timepicker/index.ts
+++ b/src/timepicker/index.ts
@@ -1,3 +1,5 @@
-export { TimepickerConfig } from './timepicker.config';
export { TimepickerComponent } from './timepicker.component';
+export { TimepickerActions } from './reducer/timepicker.actions';
+export { TimepickerStore } from './reducer/timepicker.store';
+export { TimepickerConfig } from './timepicker.config';
export { TimepickerModule } from './timepicker.module';
diff --git a/src/timepicker/reducer/timepicker.actions.ts b/src/timepicker/reducer/timepicker.actions.ts
new file mode 100644
index 0000000000..8ad1fccae1
--- /dev/null
+++ b/src/timepicker/reducer/timepicker.actions.ts
@@ -0,0 +1,47 @@
+import { Injectable } from '@angular/core';
+import { TimeUnit } from '../timepicker.models';
+import { Action } from '../../mini-ngrx/index';
+
+@Injectable()
+export class TimepickerActions {
+ static readonly WRITE_VALUE = '[timepicker] write value from ng model';
+ static readonly CHANGE_HOURS = '[timepicker] change hours';
+ static readonly CHANGE_MINUTES = '[timepicker] change minutes';
+ static readonly CHANGE_SECONDS = '[timepicker] change seconds';
+ static readonly SET_TIME_UNIT = '[timepicker] set time unit';
+
+ writeValue(value: Date | string) {
+ return {
+ type: TimepickerActions.WRITE_VALUE,
+ payload: value
+ };
+ }
+
+ changeHours(value: number) {
+ return {
+ type: TimepickerActions.CHANGE_HOURS,
+ payload: value
+ };
+ }
+
+ changeMinutes(value: number) {
+ return {
+ type: TimepickerActions.CHANGE_MINUTES,
+ payload: value
+ };
+ }
+
+ changeSeconds(value: number): Action {
+ return {
+ type: TimepickerActions.CHANGE_SECONDS,
+ payload: value
+ };
+ }
+
+ setTimeUnit(value: TimeUnit): Action {
+ return {
+ type: TimepickerActions.SET_TIME_UNIT,
+ payload: value
+ };
+ }
+}
diff --git a/src/timepicker/reducer/timepicker.reducer.ts b/src/timepicker/reducer/timepicker.reducer.ts
new file mode 100644
index 0000000000..f6b4ea7e34
--- /dev/null
+++ b/src/timepicker/reducer/timepicker.reducer.ts
@@ -0,0 +1,39 @@
+import { TimepickerActions } from './timepicker.actions';
+import { changeTime, setTime } from '../timepicker.utils';
+import { Action } from '../../mini-ngrx/index';
+
+export interface TimepickerState {
+ value: Date;
+}
+
+export const initialState = {} as TimepickerState;
+
+export function timepickerReducer(state = initialState, action: Action) {
+ switch (action.type) {
+ case(TimepickerActions.WRITE_VALUE): {
+ return Object.assign({}, state, {value: action.payload});
+ }
+ case (TimepickerActions.CHANGE_HOURS): {
+ const _newTime = changeTime(state.value, {hour: action.payload});
+
+ return Object.assign({}, state, {value: _newTime});
+ }
+ case (TimepickerActions.CHANGE_MINUTES): {
+ const _newTime = changeTime(state.value, {minute: action.payload});
+
+ return Object.assign({}, state, {value: _newTime});
+ }
+ case (TimepickerActions.CHANGE_SECONDS): {
+ const _newTime = changeTime(state.value, {seconds: action.payload});
+
+ return Object.assign({}, state, {value: _newTime});
+ }
+ case (TimepickerActions.SET_TIME_UNIT): {
+ const _newTime = setTime(state.value, action.payload);
+
+ return Object.assign({}, state, {value: _newTime});
+ }
+ default:
+ return state;
+ }
+}
diff --git a/src/timepicker/reducer/timepicker.store.ts b/src/timepicker/reducer/timepicker.store.ts
new file mode 100644
index 0000000000..4191ef4b00
--- /dev/null
+++ b/src/timepicker/reducer/timepicker.store.ts
@@ -0,0 +1,16 @@
+import { Injectable } from '@angular/core';
+import { timepickerReducer, TimepickerState, initialState } from './timepicker.reducer';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+
+import { Action } from '../../mini-ngrx/index';
+import { MiniStore } from '../../mini-ngrx/store.class';
+import { MiniState } from '../../mini-ngrx/state.class';
+
+@Injectable()
+export class TimepickerStore extends MiniStore {
+ constructor() {
+ const _dispatcher = new BehaviorSubject({type: '[mini-ngrx] dispatcher init'});
+ const state = new MiniState(initialState, _dispatcher, timepickerReducer);
+ super(_dispatcher, timepickerReducer, state);
+ }
+}
diff --git a/src/timepicker/timepicker-controls.util.ts b/src/timepicker/timepicker-controls.util.ts
new file mode 100644
index 0000000000..01e550e702
--- /dev/null
+++ b/src/timepicker/timepicker-controls.util.ts
@@ -0,0 +1,89 @@
+import { setTime } from './timepicker.utils';
+import { TimepickerComponentState, TimepickerControls } from './timepicker.models';
+
+export function timepickerControls(state: TimepickerComponentState): TimepickerControls {
+ const {min, max, value, hourStep, minuteStep, secondsStep, showSeconds} = state;
+ const res = {} as TimepickerControls;
+
+ if (!min && !max) {
+ res.canIncrementHours = true;
+ res.canIncrementMinutes = true;
+ res.canIncrementSeconds = true;
+ res.canDecrementHours = true;
+ res.canDecrementMinutes = true;
+ res.canDecrementSeconds = true;
+
+ return res;
+ }
+
+ const hour = value.getHours();
+ const minute = value.getMinutes();
+ const seconds = showSeconds ? value.getSeconds() : 0;
+
+// compare dates
+ if (max) {
+ const _newHour = setTime(max, {
+ hour: hour + hourStep,
+ minute, seconds
+ });
+ // res.canIncrementHours = max.getHours() >= (value.getHours() + hourStep);
+ res.canIncrementHours = max >= _newHour;
+ if (res.canIncrementHours) {
+ res.canIncrementMinutes = true;
+ res.canIncrementSeconds = true;
+ } else {
+ const _newMinutes = setTime(max, {
+ hour,
+ minute: minute + minuteStep,
+ seconds
+ });
+ // res.canIncrementMinutes = max.getMinutes() >= (value.getMinutes() + minuteStep);
+ res.canIncrementMinutes = max >= _newMinutes;
+ if (res.canIncrementMinutes) {
+ res.canIncrementSeconds = true;
+ } else {
+ const _newSeconds = setTime(max, {
+ hour,
+ minute,
+ seconds: seconds + secondsStep
+ });
+
+ res.canIncrementSeconds = max >= _newSeconds;
+ }
+ }
+ }
+
+ if (min) {
+ const _newHour = setTime(min, {
+ hour: hour - hourStep,
+ minute, seconds
+ });
+ // res.canIncrementHours = min.getHours() >= (value.getHours() + hourStep);
+ res.canDecrementHours = min <= _newHour;
+ if (res.canDecrementHours) {
+ res.canDecrementMinutes = true;
+ res.canDecrementSeconds = true;
+ } else {
+ const _newMinutes = setTime(min, {
+ hour,
+ minute: minute - minuteStep,
+ seconds
+ });
+ // res.canDecrementMinutes = min.getMinutes() <= (value.getMinutes() + minuteStep);
+ res.canDecrementMinutes = min <= _newMinutes;
+ if (res.canDecrementMinutes) {
+ res.canDecrementSeconds = true;
+ } else {
+ const _newSeconds = setTime(min, {
+ hour,
+ minute,
+ seconds: seconds - secondsStep
+ });
+
+ res.canDecrementSeconds = min <= _newSeconds;
+ }
+ }
+ }
+
+ return res;
+}
diff --git a/src/timepicker/timepicker.component.ts b/src/timepicker/timepicker.component.ts
index 2815233587..54d55b405c 100644
--- a/src/timepicker/timepicker.component.ts
+++ b/src/timepicker/timepicker.component.ts
@@ -1,386 +1,416 @@
-// tslint:disable max-file-line-count
-import { Component, Input, OnInit, forwardRef } from '@angular/core';
+/* tslint:disable:no-forward-ref max-file-line-count */
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
+import { timepickerControls } from './timepicker-controls.util';
+
+import { TimepickerActions } from './reducer/timepicker.actions';
import { TimepickerConfig } from './timepicker.config';
+import { TimepickerStore } from './reducer/timepicker.store';
+import { isValidDate, padNumber, parseTime } from './timepicker.utils';
+import { TimepickerControls } from './timepicker.models';
export const TIMEPICKER_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
+ // tslint:disable-next-line
useExisting: forwardRef(() => TimepickerComponent),
multi: true
};
-// todo: refactor directive has to many functions! (extract to stateless helper)
-// todo: use moment js?
-// todo: implement `time` validator
-// todo: replace increment/decrement blockers with getters, or extract
-// todo: unify work with selected
-function isDefined(value: any): boolean {
- return typeof value !== 'undefined';
-}
-
-function addMinutes(date: any, minutes: number): Date {
- let dt = new Date(date.getTime() + minutes * 60000);
- let newDate = new Date(date);
- newDate.setHours(dt.getHours(), dt.getMinutes());
- return newDate;
-}
-
@Component({
selector: 'timepicker',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ providers: [TIMEPICKER_CONTROL_VALUE_ACCESSOR, TimepickerStore],
template: `
- `,
- providers: [TIMEPICKER_CONTROL_VALUE_ACCESSOR]
+ `
})
-export class TimepickerComponent implements ControlValueAccessor, OnInit {
+export class TimepickerComponent implements ControlValueAccessor, TimepickerControls {
/** hours change step */
- @Input() public hourStep: number;
+ @Input() hourStep: number;
/** hours change step */
- @Input() public minuteStep: number;
+ @Input() minuteStep: number;
+ /** seconds change step */
+ @Input() secondsStep: number;
/** if true hours and minutes fields will be readonly */
- @Input() public readonlyInput: boolean;
+ @Input() readonlyInput: boolean;
/** if true scroll inside hours and minutes inputs will change time */
- @Input() public mousewheel: boolean;
+ @Input() mousewheel: boolean;
/** if true up/down arrowkeys inside hours and minutes inputs will change time */
- @Input() public arrowkeys: boolean;
+ @Input() arrowkeys: boolean;
/** if true spinner arrows above and below the inputs will be shown */
- @Input() public showSpinners: boolean;
+ @Input() showSpinners: boolean;
+ @Input() showMeridian: boolean;
+ @Input() showSeconds: boolean;
+
+ /** meridian labels based on locale */
+ @Input() meridians: string[];
+
/** minimum time user can select */
- @Input() public min: Date;
+ @Input() min: Date;
/** maximum time user can select */
- @Input() public max: Date;
- /** meridian labels based on locale */
- @Input() public meridians: string[];
+ @Input() max: Date;
- /** if true works in 12H mode and displays AM/PM. If false works in 24H mode and hides AM/PM */
- @Input()
- public get showMeridian(): boolean {
- return this._showMeridian;
- }
+ // ui variables
+ hours: string;
+ minutes: string;
+ seconds: string;
+ meridian: string;
- public set showMeridian(value: boolean) {
- this._showMeridian = value;
- // || !this.$error.time
- // if (true) {
- this.updateTemplate();
- return;
- // }
- // Evaluate from template
- /*let hours = this.getHoursFromTemplate();
- let minutes = this.getMinutesFromTemplate();
- if (isDefined(hours) && isDefined(minutes)) {
- this.selected.setHours(hours);
- this.refresh();
- }*/
+ get isSpinnersVisible(): boolean {
+ return this.showSpinners && !this.readonlyInput;
}
- public onChange: any = Function.prototype;
- public onTouched: any = Function.prototype;
-
- // input values
- public hours: string;
- public minutes: string;
+ // min\max validation for input fields
+ invalidHours = false;
+ invalidMinutes = false;
+ invalidSeconds = false;
- // validation
- public invalidHours: any;
- public invalidMinutes: any;
+ // time picker controls state
+ canIncrementHours: boolean;
+ canIncrementMinutes: boolean;
+ canIncrementSeconds: boolean;
- public meridian: any; // ??
+ canDecrementHours: boolean;
+ canDecrementMinutes: boolean;
+ canDecrementSeconds: boolean;
- // result value
- protected _selected: Date = new Date();
- protected _showMeridian: boolean;
+ // control value accessor methods
+ onChange: any = Function.prototype;
+ onTouched: any = Function.prototype;
- protected get selected(): Date {
- return this._selected;
+ constructor(_config: TimepickerConfig,
+ _cd: ChangeDetectorRef,
+ private _store: TimepickerStore,
+ private _timepickerActions: TimepickerActions) {
+ Object.assign(this, _config);
+ _store
+ .select((state) => state.value)
+ .subscribe((value) => {
+ // update UI values if date changed
+ this._renderTime(value);
+ this._renderControls(value);
+ this.onChange(value);
+ _cd.markForCheck();
+ });
}
- protected set selected(v: Date) {
- if (v) {
- this._selected = v;
- this.updateTemplate();
- this.onChange(this.selected);
- }
+ prevDef($event: any) {
+ $event.preventDefault();
}
- protected config: TimepickerConfig;
-
- public constructor(_config: TimepickerConfig) {
- this.config = _config;
- Object.assign(this, _config);
+ wheelSign($event: any): number {
+ return Math.sign($event.deltaY as number) * -1;
}
- // todo: add formatter value to Date object
- public ngOnInit(): void {
- // todo: take in account $locale.DATETIME_FORMATS.AMPMS;
- if (this.mousewheel) {
- // this.setupMousewheelEvents();
+ canBeChanged(source?: 'wheel' | 'key'): boolean {
+ if (source === 'wheel' && !this.mousewheel) {
+ return false;
}
- if (this.arrowkeys) {
- // this.setupArrowkeyEvents();
+ if (source === 'key' && !this.arrowkeys) {
+ return false;
}
- // this.setupInputEvents();
+ if (this.readonlyInput) {
+ return false;
+ }
+
+ return true;
}
- public writeValue(v: any): void {
- if (v === this.selected) {
+ changeHours(step: number, source?: 'wheel' | 'key'): void {
+ if (!this.canBeChanged(source)) {
return;
}
- if (v && v instanceof Date) {
- this.selected = v;
+
+ if (step > 0 && !this.canIncrementHours) {
return;
}
- this.selected = v ? new Date(v) : void 0;
- }
-
- public registerOnChange(fn: (_: any) => {}): void {
- this.onChange = fn;
- }
-
- public registerOnTouched(fn: () => {}): void {
- this.onTouched = fn;
- }
-
- public setDisabledState(isDisabled: boolean): void {
- this.readonlyInput = isDisabled;
- }
-
- public updateHours(): void {
- if (this.readonlyInput) {
+ if (step < 0 && !this.canDecrementHours) {
return;
}
- let hours = this.getHoursFromTemplate();
- let minutes = this.getMinutesFromTemplate();
- this.invalidHours = !isDefined(hours);
- this.invalidMinutes = !isDefined(minutes);
+ this._store.dispatch(this._timepickerActions.changeHours(step));
+ }
- if (this.invalidHours || this.invalidMinutes) {
- // TODO: needed a validation functionality.
+ changeMinutes(step: number, source?: 'wheel' | 'key'): void {
+ if (!this.canBeChanged(source)) {
return;
- // todo: validation?
- // invalidate(true);
}
- this.selected.setHours(hours);
- this.invalidHours = (this.selected < this.min || this.selected > this.max);
- if (this.invalidHours) {
- // todo: validation?
- // invalidate(true);
+ if (step > 0 && !this.canIncrementMinutes) {
return;
- } else {
- this.refresh(/*'h'*/);
}
- }
-
- public hoursOnBlur(): void {
- if (this.readonlyInput) {
+ if (step < 0 && !this.canDecrementMinutes) {
return;
}
- // todo: binded with validation
- if (!this.invalidHours && parseInt(this.hours, 10) < 10) {
- this.hours = this.pad(this.hours);
- }
+ this._store.dispatch(this._timepickerActions.changeMinutes(step));
}
- public updateMinutes(): void {
- if (this.readonlyInput) {
+ changeSeconds(step: number, source?: 'wheel' | 'key'): void {
+ if (!this.canBeChanged(source)) {
return;
}
- let minutes = this.getMinutesFromTemplate();
- let hours = this.getHoursFromTemplate();
- this.invalidMinutes = !isDefined(minutes);
- this.invalidHours = !isDefined(hours);
-
- if (this.invalidMinutes || this.invalidHours) {
- // TODO: needed a validation functionality.
+ if (step > 0 && !this.canIncrementSeconds) {
return;
- // todo: validation
- // invalidate(undefined, true);
}
-
- this.selected.setMinutes(minutes);
- this.invalidMinutes = (this.selected < this.min || this.selected > this.max);
- if (this.invalidMinutes) {
- // todo: validation
- // invalidate(undefined, true);
+ if (step < 0 && !this.canDecrementSeconds) {
return;
- } else {
- this.refresh(/*'m'*/);
}
+
+ this._store.dispatch(this._timepickerActions.changeSeconds(step));
}
- public minutesOnBlur(): void {
- if (this.readonlyInput) {
+ updateHours(hour: string): void {
+ if (!this.canBeChanged()) {
return;
}
- if (!this.invalidMinutes && parseInt(this.minutes, 10) < 10) {
- this.minutes = this.pad(this.minutes);
- }
- }
+ const dex = 10;
+ const _hoursPerDay = 24;
+ const _newHour = parseInt(hour, dex);
- public incrementHours(): void {
- if (!this.noIncrementHours()) {
- this.addMinutesToSelected(this.hourStep * 60);
- }
- }
+ if (isNaN(_newHour) || _newHour < 0 || _newHour > _hoursPerDay) {
+ this.hours = '';
+ this.invalidHours = true;
- public decrementHours(): void {
- if (!this.noDecrementHours()) {
- this.addMinutesToSelected(-this.hourStep * 60);
+ return;
}
+ this.invalidHours = false;
+ this._store.dispatch(this._timepickerActions
+ .setTimeUnit({hour: _newHour % _hoursPerDay}));
}
- public incrementMinutes(): void {
- if (!this.noIncrementMinutes()) {
- this.addMinutesToSelected(this.minuteStep);
- }
- }
+ updateMinutes(minute: string) {
+ const dex = 10;
+ const _minutesPerHour = 60;
+ const _newMinute = parseInt(minute, dex);
- public decrementMinutes(): void {
- if (!this.noDecrementMinutes()) {
- this.addMinutesToSelected(-this.minuteStep);
+ if (isNaN(_newMinute) || _newMinute < 0 || _newMinute > _minutesPerHour) {
+ this.minutes = '';
+ this.invalidMinutes = true;
+
+ return;
}
- }
- public noIncrementHours(): boolean {
- let incrementedSelected = addMinutes(this.selected, this.hourStep * 60);
- return incrementedSelected > this.max ||
- (incrementedSelected < this.selected && incrementedSelected < this.min);
+ this.invalidMinutes = false;
+ this._store.dispatch(this._timepickerActions
+ .setTimeUnit({minute: _newMinute % _minutesPerHour}));
}
- public noDecrementHours(): boolean {
- let decrementedSelected = addMinutes(this.selected, -this.hourStep * 60);
- return decrementedSelected < this.min ||
- (decrementedSelected > this.selected && decrementedSelected > this.max);
- }
+ updateSeconds(seconds: string) {
+ const dex = 10;
+ const _secondsPerMinute = 60;
+ const _newSeconds = parseInt(seconds, dex);
- public noIncrementMinutes(): boolean {
- let incrementedSelected = addMinutes(this.selected, this.minuteStep);
- return incrementedSelected > this.max ||
- (incrementedSelected < this.selected && incrementedSelected < this.min);
- }
+ if (isNaN(_newSeconds) || _newSeconds < 0 || _newSeconds > _secondsPerMinute) {
+ this.minutes = '';
+ this.invalidMinutes = true;
- public noDecrementMinutes(): boolean {
- let decrementedSelected = addMinutes(this.selected, -this.minuteStep);
- return decrementedSelected < this.min ||
- (decrementedSelected > this.selected && decrementedSelected > this.max);
+ return;
+ }
+ this.invalidMinutes = false;
+ this._store.dispatch(this._timepickerActions
+ .setTimeUnit({minute: _newSeconds % _secondsPerMinute}));
}
- public toggleMeridian(): void {
- if (!this.noToggleMeridian()) {
- let sign = this.selected.getHours() < 12 ? 1 : -1;
- this.addMinutesToSelected(12 * 60 * sign);
+ toggleMeridian(): void {
+ if (!this.showMeridian) {
+ return;
}
- }
- public noToggleMeridian(): boolean {
- if (this.readonlyInput) {
- return true;
- }
+ const _hoursPerDayHalf = 12;
+ this._store.dispatch(this._timepickerActions.changeHours(_hoursPerDayHalf));
+ }
- if (this.selected.getHours() < 13) {
- return addMinutes(this.selected, 12 * 60) > this.max;
- } else {
- return addMinutes(this.selected, -12 * 60) < this.min;
+ /**
+ * Write a new value to the element.
+ */
+ writeValue(obj: any): void {
+ if (isValidDate(obj)) {
+ this._store.dispatch(this._timepickerActions.writeValue(parseTime(obj)));
}
}
- protected refresh(/*type?:string*/): void {
- // this.makeValid();
- this.updateTemplate();
- this.onChange(this.selected);
+ /**
+ * Set the function to be called when the control receives a change event.
+ */
+ registerOnChange(fn: (_: any) => {}): void {
+ this.onChange = fn;
}
- protected updateTemplate(/*keyboardChange?:any*/): void {
- let hours = this.selected.getHours();
- let minutes = this.selected.getMinutes();
+ /**
+ * Set the function to be called when the control receives a touch event.
+ */
+ registerOnTouched(fn: () => {}): void {
+ this.onTouched = fn;
+ }
- if (this.showMeridian) {
- // Convert 24 to 12 hour system
- hours = (hours === 0 || hours === 12) ? 12 : hours % 12;
- }
+ /**
+ * 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;
+ }
- // this.hours = keyboardChange === 'h' ? hours : this.pad(hours);
- // if (keyboardChange !== 'm') {
- // this.minutes = this.pad(minutes);
- // }
- this.hours = this.pad(hours);
- this.minutes = this.pad(minutes);
+ private _renderTime(value: string | Date): void {
+ if (!isValidDate(value)) {
+ this.hours = '';
+ this.minutes = '';
+ this.seconds = '';
+ this.meridian = this.meridians[0];
- if (!this.meridians) {
- this.meridians = this.config.meridians;
+ return;
}
- this.meridian = this.selected.getHours() < 12
- ? this.meridians[0]
- : this.meridians[1];
- }
-
- protected getHoursFromTemplate(): number {
- let hours = parseInt(this.hours, 10);
- let valid = this.showMeridian
- ? (hours > 0 && hours < 13)
- : (hours >= 0 && hours < 24);
- if (!valid) {
- return void 0;
- }
+ const _value = parseTime(value);
+ const _hoursPerDayHalf = 12;
+ let _hours = _value.getHours();
if (this.showMeridian) {
- if (hours === 12) {
- hours = 0;
- }
- if (this.meridian === this.meridians[1]) {
- hours = hours + 12;
+ this.meridian = this.meridians[_hours >= _hoursPerDayHalf ? 1 : 0];
+ _hours = _hours % _hoursPerDayHalf;
+ // should be 12 PM, not 00 PM
+ if (_hours === 0) {
+ _hours = _hoursPerDayHalf;
}
}
- return hours;
- }
-
- protected getMinutesFromTemplate(): number {
- let minutes = parseInt(this.minutes, 10);
- return (minutes >= 0 && minutes < 60) ? minutes : undefined;
- }
- protected pad(value: string|number): string {
- return (isDefined(value) && value.toString().length < 2)
- ? '0' + value
- : value.toString();
+ this.hours = padNumber(_hours);
+ this.minutes = padNumber(_value.getMinutes());
+ this.seconds = padNumber(_value.getUTCSeconds());
}
- protected addMinutesToSelected(minutes: any): void {
- this.selected = addMinutes(this.selected, minutes);
- this.refresh();
+ private _renderControls(value: Date): void {
+ const {min, max, hourStep, minuteStep, secondsStep, showSeconds} = this;
+ const controlsState = timepickerControls({
+ value, min, max, hourStep, minuteStep, secondsStep, showSeconds
+ });
+ Object.assign(this, controlsState);
}
}
diff --git a/src/timepicker/timepicker.config.ts b/src/timepicker/timepicker.config.ts
index d976e4a588..aaea9c00a4 100644
--- a/src/timepicker/timepicker.config.ts
+++ b/src/timepicker/timepicker.config.ts
@@ -4,23 +4,23 @@ import { Injectable } from '@angular/core';
@Injectable()
export class TimepickerConfig {
/** hours change step */
- public hourStep: number = 1;
+ hourStep = 1;
/** hours change step */
- public minuteStep: number = 5;
+ minuteStep = 5;
/** if true works in 12H mode and displays AM/PM. If false works in 24H mode and hides AM/PM */
- public showMeridian: boolean = true;
+ showMeridian = true;
/** meridian labels based on locale */
- public meridians:string[] = ['AM', 'PM'];
+ meridians = ['AM', 'PM'];
/** if true hours and minutes fields will be readonly */
- public readonlyInput: boolean = false;
+ readonlyInput = false;
/** if true scroll inside hours and minutes inputs will change time */
- public mousewheel: boolean = true;
+ mousewheel = true;
/** if true up/down arrowkeys inside hours and minutes inputs will change time */
- public arrowkeys: boolean = true;
+ arrowkeys = true;
/** if true spinner arrows above and below the inputs will be shown */
- public showSpinners: boolean = true;
+ showSpinners = true;
/** minimum time user can select */
- public min: number = void 0;
+ min: Date;
/** maximum time user can select */
- public max: number = void 0;
+ max: Date;
}
diff --git a/src/timepicker/timepicker.models.ts b/src/timepicker/timepicker.models.ts
new file mode 100644
index 0000000000..f8df456cf4
--- /dev/null
+++ b/src/timepicker/timepicker.models.ts
@@ -0,0 +1,25 @@
+export interface TimeUnit {
+ hour?: number;
+ minute?: number;
+ seconds?: number;
+}
+
+export interface TimepickerControls {
+ canIncrementHours: boolean;
+ canIncrementMinutes: boolean;
+ canIncrementSeconds: boolean;
+
+ canDecrementHours: boolean;
+ canDecrementMinutes: boolean;
+ canDecrementSeconds: boolean;
+}
+
+export interface TimepickerComponentState {
+ value: Date;
+ min: Date;
+ max: Date;
+ hourStep: number;
+ minuteStep: number;
+ secondsStep: number;
+ showSeconds: boolean;
+}
diff --git a/src/timepicker/timepicker.module.ts b/src/timepicker/timepicker.module.ts
index 3c39a0b4cc..f0fb71564c 100644
--- a/src/timepicker/timepicker.module.ts
+++ b/src/timepicker/timepicker.module.ts
@@ -1,19 +1,21 @@
+import { ModuleWithProviders, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
-import { FormsModule } from '@angular/forms';
-import { NgModule, ModuleWithProviders } from '@angular/core';
+
import { TimepickerComponent } from './timepicker.component';
+import { TimepickerActions } from './reducer/timepicker.actions';
import { TimepickerConfig } from './timepicker.config';
+import { TimepickerStore } from './reducer/timepicker.store';
@NgModule({
- imports: [CommonModule, FormsModule],
+ imports: [CommonModule],
declarations: [TimepickerComponent],
- exports: [TimepickerComponent, FormsModule]
+ exports: [TimepickerComponent]
})
export class TimepickerModule {
- public static forRoot(): ModuleWithProviders {
+ static forRoot(): ModuleWithProviders {
return {
ngModule: TimepickerModule,
- providers: [TimepickerConfig]
+ providers: [TimepickerConfig, TimepickerActions, TimepickerStore]
};
}
}
diff --git a/src/timepicker/timepicker.utils.ts b/src/timepicker/timepicker.utils.ts
new file mode 100644
index 0000000000..992b2f82a0
--- /dev/null
+++ b/src/timepicker/timepicker.utils.ts
@@ -0,0 +1,88 @@
+import { TimeUnit } from './timepicker.models';
+
+export function isValidDate(value?: string | Date): boolean {
+ if (!value) {
+ return false;
+ }
+
+ if (value instanceof Date && isNaN(value.getHours())) {
+ return false;
+ }
+
+ if (typeof value === 'string') {
+ return isValidDate(new Date(value));
+ }
+
+ return true;
+}
+
+export function parseTime(value: string | Date): Date {
+ if (typeof value === 'string') {
+ return new Date(value);
+ }
+
+ return value;
+}
+
+export function changeTime(value: Date, diff: TimeUnit): Date {
+ if (!value) {
+ const _value = new Date();
+
+ return changeTime(new Date(_value.getFullYear(), _value.getMonth(), _value.getDate(),
+ 0, 0, 0, _value.getMilliseconds()), diff);
+ }
+
+ const _hoursPerDay = 24;
+ // const _minutesPerHour = 60;
+ // const _secondsPerMinute = 60;
+
+ let hour = value.getHours();
+ let minutes = value.getMinutes();
+ let seconds = value.getSeconds();
+
+ if (diff.hour) {
+ hour = (hour + diff.hour) % _hoursPerDay;
+ if (hour < 0) {
+ hour += _hoursPerDay;
+ }
+ }
+
+ if (diff.minute) {
+ minutes = (minutes + diff.minute);
+ // minutes = (minutes + diff.minute) % _minutesPerHour;
+ // if (minutes < 0) {
+ // minutes += _minutesPerHour;
+ // }
+ }
+
+ if (diff.seconds) {
+ seconds = (seconds + diff.seconds);
+ // seconds = (seconds + diff.seconds) % _secondsPerMinute;
+ // if (seconds < 0) {
+ // seconds += _secondsPerMinute;
+ // }
+ }
+
+ return new Date(value.getFullYear(), value.getMonth(), value.getDate(),
+ hour, minutes, seconds, value.getMilliseconds());
+}
+
+export function setTime(value: Date, opts: TimeUnit): Date {
+ if (!value) {
+ return value;
+ }
+
+ const hour = (opts.hour || opts.hour === 0) ? opts.hour : value.getHours();
+ const minute = (opts.minute || opts.minute === 0) ? opts.minute : value.getMinutes();
+ const seconds = (opts.seconds || opts.seconds === 0) ? opts.seconds : value.getSeconds();
+
+ return new Date(value.getFullYear(), value.getMonth(), value.getDate(),
+ hour, minute, seconds, value.getMilliseconds());
+}
+
+export function padNumber(value: number): string {
+ const _value = value.toString();
+ if (_value.length > 1) { return _value; }
+
+ return `0${_value}`;
+}