diff --git a/.travis.yml b/.travis.yml
index b532e7da1e..29d95d87a1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,8 +20,7 @@ script:
- npm i ./dist
- npm run demo.build
# istanbul is broken, should be fixed
- #- npm run test-coverage
- - ./node_modules/.bin/ng test -sr
+ - npm run test-coverage
after_success:
- ./node_modules/.bin/codecov
diff --git a/demo/src/app/components/+datepicker/datepicker-section.component.ts b/demo/src/app/components/+datepicker/datepicker-section.component.ts
index 84b12408e1..a32c8320e9 100644
--- a/demo/src/app/components/+datepicker/datepicker-section.component.ts
+++ b/demo/src/app/components/+datepicker/datepicker-section.component.ts
@@ -36,11 +36,11 @@ let titleDoc = require('html-loader!markdown-loader!./docs/title.md');
Notebale change is additional css for it "/datepicker/bs-datepicker.css"
In nearest time will be added:
- - 1. Month and year selection
- - 2. Min/max dates restrcitions
+ 1. Month and year selection
+ 2. Min/max dates restrcitions
- 3. Theming - this will be a small breaking change
- 4. Options to replace any part of template
- - 5. Configuration
+ 5. Configuration
- 6. Integration with forms, only for input fields
- etc.
diff --git a/demo/src/app/components/+datepicker/demos/bs-popup/date-picker-popup.html b/demo/src/app/components/+datepicker/demos/bs-popup/date-picker-popup.html
index 8059e3e166..36f7b343d7 100644
--- a/demo/src/app/components/+datepicker/demos/bs-popup/date-picker-popup.html
+++ b/demo/src/app/components/+datepicker/demos/bs-popup/date-picker-popup.html
@@ -1,10 +1,12 @@
{{bsValue}}
-
@@ -13,9 +15,9 @@
{{bsRangeValue}}
-
diff --git a/demo/src/app/components/+datepicker/demos/bs-popup/date-picker-popup.ts b/demo/src/app/components/+datepicker/demos/bs-popup/date-picker-popup.ts
index c10dea6af9..334bbf64ec 100644
--- a/demo/src/app/components/+datepicker/demos/bs-popup/date-picker-popup.ts
+++ b/demo/src/app/components/+datepicker/demos/bs-popup/date-picker-popup.ts
@@ -5,6 +5,8 @@ import { Component } from '@angular/core';
templateUrl: './date-picker-popup.html'
})
export class DemoDatePickerPopupComponent {
- public bsValue: any ;
- public bsRangeValue: any = [new Date(2017, 7, 4), new Date(2017, 7, 20)];
+ minDate = new Date(2017, 5, 10);
+ maxDate = new Date(2018, 9, 15);
+ bsValue: any ;
+ bsRangeValue: any = [new Date(2017, 7, 4), new Date(2017, 7, 20)];
}
diff --git a/demo/src/assets/css/style.css b/demo/src/assets/css/style.css
index e3d0918931..b1d458a27b 100644
--- a/demo/src/assets/css/style.css
+++ b/demo/src/assets/css/style.css
@@ -150,7 +150,7 @@ a:hover {
position: fixed;
bottom: 0;
left: 0;
- z-index: 999;
+ /*z-index: 999;*/
-webkit-transition: left .5s ease;
-moz-transition: left .5s ease;
-ms-transition: left .5s ease;
@@ -585,6 +585,7 @@ a:hover {
.isOpenMenu #main-menu {
left: 0;
+ z-index: 999;
}
#main {
diff --git a/demo/src/ng-api-doc.ts b/demo/src/ng-api-doc.ts
index 2af2df9573..23548dd4e6 100644
--- a/demo/src/ng-api-doc.ts
+++ b/demo/src/ng-api-doc.ts
@@ -67,17 +67,17 @@ export const ngdoc: any = {
"description": "",
"selector": "alert,ngx-alert",
"inputs": [
+ {
+ "name": "dismissOnTimeout",
+ "type": "string | number",
+ "description": "Number in milliseconds, after which alert will be closed
\n"
+ },
{
"name": "dismissible",
"defaultValue": "false",
"type": "boolean",
"description": "If set, displays an inline "Close" button
\n"
},
- {
- "name": "dismissOnTimeout",
- "type": "string | number",
- "description": "Number in milliseconds, after which alert will be closed
\n"
- },
{
"name": "type",
"defaultValue": "warning",
@@ -111,18 +111,18 @@ export const ngdoc: any = {
"description": "",
"methods": [],
"properties": [
- {
- "name": "dismissible",
- "defaultValue": "false",
- "type": "boolean",
- "description": "is alerts are dismissible by default
\n"
- },
{
"name": "dismissOnTimeout",
"defaultValue": "undefined",
"type": "number",
"description": "default time before alert will dismiss
\n"
},
+ {
+ "name": "dismissible",
+ "defaultValue": "false",
+ "type": "boolean",
+ "description": "is alerts are dismissible by default
\n"
+ },
{
"name": "type",
"defaultValue": "warning",
diff --git a/package.json b/package.json
index 14d19589b6..4d5170ad8c 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,9 @@
"disable-lint": "tslint \"**/*.ts\" -c tslint.json --fix --type-check -t prose -e \"node_modules/**\" -e \"dist/**\" -e \"temp/**\" -e \"scripts/docs/**\"",
"flow.changelog": "conventional-changelog -i CHANGELOG.md -s -p angular",
"flow.github-release": "conventional-github-releaser -p angular",
- "build": "ngm build -p src --clean",
+ "build": "run-s build.ngm build.sass",
+ "build.sass": "node-sass --recursive src --output dist --source-map true --source-map-contents sass",
+ "build.ngm": "ngm build -p src --clean",
"build.watch": "ngm build -p src --watch --skip-bundles",
"start": "ng serve --aot --host 0.0.0.0",
"generate-bs4": "node scripts/generate-bs4.js",
@@ -67,7 +69,7 @@
"@angular/forms": "^2.3.1 || >=4.0.0"
},
"devDependencies": {
- "@angular/cli": "1.3.1",
+ "@angular/cli": "1.3.2",
"@angular/common": "^2.4.4",
"@angular/compiler": "^2.4.4",
"@angular/compiler-cli": "^2.4.4",
@@ -79,10 +81,10 @@
"@angular/platform-browser-dynamic": "^2.4.4",
"@angular/router": "^3.4.4",
"@angular/tsc-wrapped": "0.5.1",
- "@types/jasmine": "2.5.53",
+ "@types/jasmine": "2.5.54",
"@types/marked": "0.3.0",
- "@types/node": "8.0.24",
- "@types/webpack": "3.0.9",
+ "@types/node": "8.0.25",
+ "@types/webpack": "3.0.10",
"bootstrap": "3.3.7",
"classlist-polyfill": "1.2.0",
"codecov": "2.3.0",
@@ -97,8 +99,8 @@
"google-code-prettify": "1.0.5",
"html-loader": "0.5.1",
"intl": "^1.2.5",
- "jasmine": "2.7.0",
- "jasmine-core": "2.7.0",
+ "jasmine": "2.8.0",
+ "jasmine-core": "2.8.0",
"jasmine-data-provider": "2.2.0",
"jasmine-spec-reporter": "4.2.1",
"karma": "1.7.0",
@@ -108,26 +110,26 @@
"karma-jasmine": "^1.0.2",
"karma-jasmine-html-reporter": "^0.2.2",
"karma-remap-istanbul": "0.6.0",
- "karma-sauce-launcher": "1.1.0",
+ "karma-sauce-launcher": "1.2.0",
"lite-server": "2.3.0",
"lodash": "4.17.4",
"markdown-loader": "github:valorkin/markdown-loader",
"marked": "0.3.6",
"ng2-page-scroll": "4.0.0-beta.7",
"ngm-cli": "0.6.1",
- "npm-run-all": "4.0.2",
+ "npm-run-all": "4.1.1",
"protractor": "5.1.2",
"reflect-metadata": "0.1.10",
"require-dir": "0.3.2",
"rxjs": "5.4.3",
"ts-helpers": "^1.1.1",
- "tslint": "5.6.0",
+ "tslint": "5.7.0",
"tslint-config-valorsoft": "2.1.0",
"typedoc": "0.8.0",
"typescript": "2.4.2",
"wallaby-webpack": "0.0.39",
"webdriver-manager": "12.0.6",
"webpack-bundle-analyzer": "2.9.0",
- "zone.js": "0.8.16"
+ "zone.js": "0.8.17"
}
}
diff --git a/src/bs-moment/format-functions.ts b/src/bs-moment/format-functions.ts
index db9db9df32..28d35689cc 100644
--- a/src/bs-moment/format-functions.ts
+++ b/src/bs-moment/format-functions.ts
@@ -1,7 +1,7 @@
import { Locale } from './locale/locale.class';
import { zeroFill } from './utils';
import { isFunction } from './utils/type-checks';
-import { DateFormatterFn } from '../datepicker/models/index';
+import { DateFormatterFn } from './types';
export let formatFunctions: { [key: string]: (date: Date, locale: Locale) => string } = {};
export let formatTokenFunctions: { [key: string]: DateFormatterFn } = {};
diff --git a/src/bs-moment/types.ts b/src/bs-moment/types.ts
index 4d4975ca34..af20250610 100644
--- a/src/bs-moment/types.ts
+++ b/src/bs-moment/types.ts
@@ -1,3 +1,5 @@
+import { Locale } from './locale/locale.class';
+
export type UnitOfTime = 'year' | 'month' | 'week' | 'day' | 'hour' |
'minute' | 'seconds' | 'milliseconds';
@@ -9,3 +11,5 @@ export interface TimeUnit {
minute?: number;
seconds?: number;
}
+
+export type DateFormatterFn = (date: Date, format: string, locale?: Locale) => string;
diff --git a/src/bs-moment/utils/date-compare.ts b/src/bs-moment/utils/date-compare.ts
new file mode 100644
index 0000000000..3b506930c4
--- /dev/null
+++ b/src/bs-moment/utils/date-compare.ts
@@ -0,0 +1,64 @@
+import { UnitOfTime } from '../types';
+import { endOf, startOf } from './start-end-of';
+
+export function isAfter(date1: Date, date2: Date, units: UnitOfTime = 'milliseconds'): boolean {
+ if (!date1 || !date2) {
+ return false;
+ }
+
+ if (units === 'milliseconds') {
+ return date1.valueOf() > date2.valueOf();
+ }
+
+ return date2.valueOf() < startOf(date1, units).valueOf();
+}
+
+export function isBefore(date1: Date, date2: Date, units: UnitOfTime = 'milliseconds'): boolean {
+ if (!date1 || !date2) {
+ return false;
+ }
+
+ if (units === 'milliseconds') {
+ return date1.valueOf() < date2.valueOf();
+ }
+
+ return endOf(date1, units).valueOf() < date2.valueOf();
+}
+
+export function isBetween(date: Date,
+ from: Date,
+ to: Date,
+ units: UnitOfTime,
+ inclusivity = '()'): boolean {
+ const leftBound = inclusivity[0] === '('
+ ? isAfter(date, from, units) :
+ !isBefore(date, from, units);
+ const rightBound = inclusivity[1] === ')'
+ ? isBefore(date, to, units)
+ : !isAfter(date, to, units);
+
+ return leftBound && rightBound;
+}
+
+export function isSame(date1: Date, date2: Date, units: UnitOfTime = 'milliseconds'): boolean {
+ if (!date1 || !date2) {
+ return false;
+ }
+
+ if (units === 'milliseconds') {
+ return date1.valueOf() === date2.valueOf();
+ }
+
+ const inputMs = date2.valueOf();
+
+ return startOf(date1, units).valueOf() <= inputMs
+ && inputMs <= endOf(date1, units).valueOf();
+}
+
+export function isSameOrAfter(date1: Date, date2: Date, units?: UnitOfTime): boolean {
+ return isSame(date1, date2, units) || isAfter(date1, date2, units);
+}
+
+export function isSameOrBefore(date1: Date, date2: Date, units?: UnitOfTime): boolean {
+ return isSame(date1, date2, units) || isBefore(date1, date2, units);
+}
diff --git a/src/bs-moment/utils/date-getters.ts b/src/bs-moment/utils/date-getters.ts
index 7b56026f3a..282d2694b9 100644
--- a/src/bs-moment/utils/date-getters.ts
+++ b/src/bs-moment/utils/date-getters.ts
@@ -1,4 +1,3 @@
-
import { createDate } from './date-setters';
export function getHours(date: Date, isUTC = false): number {
@@ -47,6 +46,23 @@ export function isFirstDayOfWeek(date: Date, firstDayOfWeek: number): boolean {
}
export function isSameMonth(date1: Date, date2: Date) {
- if (!date1 || !date2) {return false;}
- return getFullYear(date1) === getFullYear(date2) && getMonth(date1) === getMonth(date2);
+ if (!date1 || !date2) {return false; }
+
+ return isSameYear(date1, date2) && getMonth(date1) === getMonth(date2);
+}
+
+export function isSameYear(date1: Date, date2: Date) {
+ if (!date1 || !date2) {return false; }
+
+ return getFullYear(date1) === getFullYear(date2);
}
+
+export function isSameDay(date1: Date, date2: Date): boolean {
+ if (!date1 || !date2) {
+ return false;
+ }
+
+ return isSameYear(date1, date2) && isSameMonth(date1, date2)
+ && getDate(date1) === getDate(date2);
+}
+
diff --git a/src/datepicker/base/bs-datepicker-container.ts b/src/datepicker/base/bs-datepicker-container.ts
new file mode 100644
index 0000000000..11085fca03
--- /dev/null
+++ b/src/datepicker/base/bs-datepicker-container.ts
@@ -0,0 +1,58 @@
+// datepicker container component
+/* tslint:disable no-empty */
+import { BsCustomDates } from '../themes/bs/bs-custom-dates-view.component';
+import { BsDatepickerEffects } from '../reducer/bs-datepicker.effects';
+import { Observable } from 'rxjs/Observable';
+import {
+ BsDatepickerViewMode, BsNavigationEvent, CalendarCellViewModel, CellHoverEvent, DatepickerRenderOptions,
+ DaysCalendarViewModel, DayViewModel,
+ MonthsCalendarViewModel,
+ YearsCalendarViewModel
+} from '../models/index';
+
+export abstract class BsDatepickerContainer {
+ /** @deperecated */
+ _customRangesFish: BsCustomDates[] = [
+ {label: 'today', value: new Date()},
+ {label: 'today1', value: new Date()},
+ {label: 'today2', value: new Date()},
+ {label: 'today3', value: new Date()}
+ ];
+
+ _effects: BsDatepickerEffects;
+
+ set minDate(value: Date) {
+ this._effects.setMinDate(value);
+ }
+
+ set maxDate(value: Date) {
+ this._effects.setMaxDate(value);
+ }
+
+
+ viewMode: Observable;
+ daysCalendar: Observable;
+ monthsCalendar: Observable;
+ yearsCalendar: Observable;
+ options: Observable;
+
+ setViewMode(event: BsDatepickerViewMode): void {}
+
+ navigateTo(event: BsNavigationEvent): void {}
+
+ dayHoverHandler(event: CellHoverEvent): void {}
+
+ monthHoverHandler(event: CellHoverEvent): void {}
+
+ yearHoverHandler(event: CellHoverEvent): void {}
+
+ daySelectHandler(day: DayViewModel): void {};
+
+ monthSelectHandler(event: CalendarCellViewModel): void {}
+
+ yearSelectHandler(event: CalendarCellViewModel): void {}
+
+ _stopPropagation(event: any): void {
+ event.stopPropagation();
+ }
+}
diff --git a/src/datepicker/bs-datepicker-config.ts b/src/datepicker/bs-datepicker-config.ts
deleted file mode 100644
index 41d7a6847f..0000000000
--- a/src/datepicker/bs-datepicker-config.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import { Injectable } from '@angular/core';
-
-@Injectable()
-export class BsDatepickerConfig {}
diff --git a/src/datepicker/bs-datepicker.component.ts b/src/datepicker/bs-datepicker.component.ts
index b3149a2855..e787cb0895 100644
--- a/src/datepicker/bs-datepicker.component.ts
+++ b/src/datepicker/bs-datepicker.component.ts
@@ -2,11 +2,11 @@ import {
Component, ComponentRef,
ElementRef,
EventEmitter,
- Input,
+ Input, OnChanges,
OnDestroy,
OnInit,
Output,
- Renderer,
+ Renderer, SimpleChanges,
ViewContainerRef
} from '@angular/core';
import { ComponentLoader } from '../component-loader/component-loader.class';
@@ -14,13 +14,14 @@ import { ComponentLoaderFactory } from '../component-loader/component-loader.fac
import { BsDatepickerContainerComponent } from './themes/bs/bs-datepicker-container.component';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/operator/filter';
+import { BsDatepickerConfig } from './bs-datepicker.config';
@Component({
selector: 'bs-datepicker,[bsDatepicker]',
exportAs: 'bsDatepicker',
template: ' '
})
-export class BsDatepickerComponent implements OnInit, OnDestroy {
+export class BsDatepickerComponent implements OnInit, OnDestroy, OnChanges {
/**
* Placement of a popover. Accepts: "top", "bottom", "left", "right"
*/
@@ -59,7 +60,6 @@ export class BsDatepickerComponent implements OnInit, OnDestroy {
*/
@Output() onHidden: EventEmitter;
- // here will be parsed options and set defaults
// @Input() config: BsDatePickerOptions;
// configChange: EventEmitter = new EventEmitter();
@@ -71,25 +71,50 @@ export class BsDatepickerComponent implements OnInit, OnDestroy {
this.bsValueChange.emit(value);
}
+ @Input() minDate: Date;
+ @Input() maxDate: Date;
+
@Output() bsValueChange: EventEmitter = new EventEmitter();
- protected subscriptions: Subscription[] = [];
+ protected _subs: Subscription[] = [];
private _datepicker: ComponentLoader;
private _datepickerRef: ComponentRef;
- constructor(_elementRef: ElementRef,
+ constructor(private _config: BsDatepickerConfig,
+ _elementRef: ElementRef,
_renderer: Renderer,
_viewContainerRef: ViewContainerRef,
cis: ComponentLoaderFactory) {
+ Object.assign(this, this._config);
this._datepicker = cis
.createLoader(_elementRef, _viewContainerRef, _renderer);
- // .provide({provide: PopoverConfig, useValue: _config});
- // Object.assign(this, _config);
this.onShown = this._datepicker.onShown;
this.onHidden = this._datepicker.onHidden;
}
+ ngOnInit(): any {
+ this._datepicker.listen({
+ outsideClick: this.outsideClick,
+ triggers: this.triggers,
+ show: () => this.show()
+ });
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ if (!this._datepickerRef || !this._datepickerRef.instance) {
+ return;
+ }
+
+ if (changes.minDate) {
+ this._datepickerRef.instance.minDate = this.minDate;
+ }
+
+ if (changes.maxDate) {
+ this._datepickerRef.instance.maxDate = this.maxDate;
+ }
+ }
+
/**
* Opens an element’s datepicker. This is considered a “manual” triggering of
* the datepicker.
@@ -99,23 +124,26 @@ export class BsDatepickerComponent implements OnInit, OnDestroy {
return;
}
+ const config = Object.assign({}, this._config, {
+ value: this._bsValue,
+ minDate: this.minDate || this._config.minDate,
+ maxDate: this.maxDate || this._config.maxDate
+ });
+
this._datepickerRef = this._datepicker
+ .provide({provide: BsDatepickerConfig, useValue: config})
.attach(BsDatepickerContainerComponent)
.to(this.container)
.position({attachment: this.placement})
.show({placement: this.placement});
- // link with datepicker
- // set initial value of picker
- this._datepickerRef.instance.value = this._bsValue;
-
// if date changes from external source (model -> view)
- this.bsValueChange.subscribe((value: Date) => {
+ this._subs.push(this.bsValueChange.subscribe((value: Date) => {
this._datepickerRef.instance.value = value;
- });
+ }));
// if date changes from picker (view -> model)
- this.subscriptions.push(this._datepickerRef.instance
+ this._subs.push(this._datepickerRef.instance
.valueChange.subscribe((value: Date) => {
this.bsValue = value;
this.hide();
@@ -130,6 +158,9 @@ export class BsDatepickerComponent implements OnInit, OnDestroy {
if (this.isOpen) {
this._datepicker.hide();
}
+ for (const sub of this._subs) {
+ sub.unsubscribe();
+ }
}
/**
@@ -144,14 +175,6 @@ export class BsDatepickerComponent implements OnInit, OnDestroy {
this.show();
}
- ngOnInit(): any {
- this._datepicker.listen({
- outsideClick: this.outsideClick,
- triggers: this.triggers,
- show: () => this.show()
- });
- }
-
ngOnDestroy(): any {
this._datepicker.dispose();
}
diff --git a/src/datepicker/bs-datepicker.config.ts b/src/datepicker/bs-datepicker.config.ts
new file mode 100644
index 0000000000..715db9529d
--- /dev/null
+++ b/src/datepicker/bs-datepicker.config.ts
@@ -0,0 +1,25 @@
+import { Injectable } from '@angular/core';
+import { DatepickerFormatOptions, DatepickerRenderOptions } from './models/index';
+
+@Injectable()
+export class BsDatepickerConfig implements DatepickerRenderOptions,
+ DatepickerFormatOptions {
+
+ value?: Date | Date[];
+ minDate?: Date;
+ maxDate?: Date;
+
+ // DatepickerRenderOptions
+ displayMonths = 1;
+ showWeekNumbers = true;
+
+ // DatepickerFormatOptions
+ locale = 'en';
+ monthTitle = 'MMMM';
+ yearTitle = 'YYYY';
+ dayLabel = 'D';
+ monthLabel = 'MMMM';
+ yearLabel = 'YYYY';
+ weekNumbers = 'w';
+
+}
diff --git a/src/datepicker/bs-datepicker.module.ts b/src/datepicker/bs-datepicker.module.ts
index ec8672d8de..7723035f04 100644
--- a/src/datepicker/bs-datepicker.module.ts
+++ b/src/datepicker/bs-datepicker.module.ts
@@ -3,10 +3,8 @@ import { isDevMode, ModuleWithProviders, NgModule } from '@angular/core';
import { BsDatepickerActions } from './reducer/bs-datepicker.actions';
import { BsDatepickerStore } from './reducer/bs-datepicker.store';
import { BsDatepickerContainerComponent } from './themes/bs/bs-datepicker-container.component';
-import { BsDatepickerMonthViewComponent } from './themes/bs/bs-datepicker-month-view.component';
import { BsDatepickerNavigationViewComponent } from './themes/bs/bs-datepicker-navigation-view.component';
-import { BsDatepickerViewComponent } from './themes/bs/bs-datepicker-view.component';
-import { BsDatepickerConfig } from './bs-datepicker-config';
+import { BsDaysCalendarViewComponent } from './themes/bs/bs-days-calendar-view.component';
import { BsDatepickerEffects } from './reducer/bs-datepicker.effects';
import { BsDaterangepickerContainerComponent } from './themes/bs/bs-daterangepicker-container.component';
import { BsDaterangepickerComponent } from './bs-daterangepicker.component';
@@ -14,16 +12,32 @@ import { BsDatepickerComponent } from './bs-datepicker.component';
import { ComponentLoaderFactory } from '../component-loader/component-loader.factory';
import { PositioningService } from '../positioning/positioning.service';
import { BsDatepickerDayDecoratorComponent } from './themes/bs/bs-datepicker-day-decorator.directive';
+import { BsMonthCalendarViewComponent } from './themes/bs/bs-months-calendar-view.component';
+import { BsYearsCalendarViewComponent } from './themes/bs/bs-years-calendar-view.component';
+import { BsCustomDatesViewComponent } from './themes/bs/bs-custom-dates-view.component';
+import { BsCurrentDateViewComponent } from './themes/bs/bs-current-date-view.component';
+import { BsTimepickerViewComponent } from './themes/bs/bs-timepicker-view.component';
+import { BsDatepickerConfig } from './bs-datepicker.config';
+import { BsCalendarLayoutComponent } from './themes/bs/bs-calendar-layout.component';
@NgModule({
imports: [CommonModule],
declarations: [
- BsDatepickerMonthViewComponent,
- BsDatepickerViewComponent,
- BsDatepickerNavigationViewComponent,
BsDatepickerDayDecoratorComponent,
+ BsCurrentDateViewComponent,
+ BsDatepickerNavigationViewComponent,
+ BsTimepickerViewComponent,
+
+ BsCalendarLayoutComponent,
+ BsDaysCalendarViewComponent,
+ BsMonthCalendarViewComponent,
+ BsYearsCalendarViewComponent,
+
+ BsCustomDatesViewComponent,
+
BsDatepickerContainerComponent,
BsDaterangepickerContainerComponent,
+
BsDatepickerComponent,
BsDaterangepickerComponent
],
@@ -39,6 +53,7 @@ export class BsDatepickerModule {
PLEASE, read changelog`);
}
}
+
static forRoot(): ModuleWithProviders {
return {
ngModule: BsDatepickerModule,
diff --git a/src/datepicker/bs-datepicker.scss b/src/datepicker/bs-datepicker.scss
index 88b3c36e82..8de51dc523 100644
--- a/src/datepicker/bs-datepicker.scss
+++ b/src/datepicker/bs-datepicker.scss
@@ -3,15 +3,15 @@
/* .bs-datepicker */
.bs-datepicker {
- display: inline-block;
- vertical-align: top;
- min-width: 279px;
- min-height: 250px;
+ display: flex;
+ align-items: stretch;
+ flex-flow: row wrap;
background: $main-bg;
- box-shadow: 0 10px 20px rgba(84, 112, 139, 0.1);
+ box-shadow: 0 0 10px 0 $main-box-shadow;
position: relative;
z-index: 1;
+
&:after {
clear: both;
content: '';
@@ -97,27 +97,14 @@
padding: 0 13px;
}
}
-
- /* .bs-datepicker-head.years button.current,
- .bs-datepicker-head.months button.current */
- &.years,
- &.months {
- button.current {
- width: 155px;
- padding: 0;
- }
- }
}
- &-head,
- &-btns {
+ &-head {
button {
- &:hover,
- &.colored:hover:after {
+ &:hover {
background-color: rgba(0, 0, 0, 0.1);
}
- :active,
- &.colored:active:after {
+ &:active {
background-color: rgba(0, 0, 0, 0.2);
}
}
@@ -127,7 +114,8 @@
&-body {
padding: 10px;
border-radius: 0 0 3px 3px;
- min-height: 230px;
+ min-height: 232px;
+ min-width: 278px;
border: 1px solid $border-color;
.days.weeks {
@@ -158,9 +146,6 @@
display: block;
margin: 0 auto;
font-size: 13px;
- width: 32px;
- height: 32px;
- line-height: 32px;
border-radius: 50%;
position: relative;
/*z-index: 1;*/
@@ -258,6 +243,10 @@
}
span {
+ width: 32px;
+ height: 32px;
+ line-height: 32px;
+
&.select-start {
z-index: 2;
}
@@ -305,7 +294,6 @@
}
}
- &.months,
&.years {
td {
span {
@@ -324,28 +312,20 @@
}
}
}
- }
- }
- /* .bs-timepicker */
- &.bs-timepicker {
- &:after {
- content: '';
- display: block;
- clear: both;
- }
+ &.months {
+ td {
+ height: 52px;
- .bs-datepicker-body {
- border: 1px solid $border-color;
- float: left;
+ span {
+ padding: 6px;
+ border-radius: 15px;
+ }
+ }
+ }
}
}
- &.bs-timepicker,
- &.bs-padding {
- padding: 15px;
- }
-
/* .current-timedate */
.current-timedate {
color: $font-color-03;
@@ -376,9 +356,11 @@
/* .bs-datepicker-multiple */
&-multiple {
display: inline-block;
- border-radius: 4px;
- box-shadow: 0 3px 11px rgba(33, 37, 39, 0.2);
- background-color: $main-bg;
+ border-radius: 4px 0 0 4px;
+
+ & + & {
+ margin-left: 2px;
+ }
.bs-datepicker {
box-shadow: none;
@@ -410,58 +392,18 @@
}
/* .bs-datepicker-btns */
- &-container + &-btns {
- border-top: 1px solid $border-color;
+ &-container {
+ padding: 15px;
}
- &-btns {
- padding: 10px 0;
- text-align: right;
- clear: both;
- border-top: 1px solid $border-color;
-
- button {
- padding: 0 16px;
- border: 0;
- border-radius: 15px;
- height: 30px;
- color: $font-color-03;
- position: relative;
-
- &:after {
- content: "";
- transition: 0.3s;
- border-radius: 15px;
- width: 100%;
- height: 100%;
- position: absolute;
- top: 0;
- left: 0;
- }
-
- span {
- position: relative;
- z-index: 1;
- }
-
- &.colored {
- color: $font-color-01;
- }
-
- &:not(.colored) {
- background: transparent;
-
- &:hover {
- text-decoration: underline;
- }
- }
- }
+ /*.bs-datepicker-custom-range */
+ &-custom-range {
+ padding: 15px;
+ background: $custom-range-bg;
}
/* .bs-datepicker-predefined-btns */
&-predefined-btns {
- padding: 15px;
-
button {
width: 100%;
display: block;
@@ -476,7 +418,7 @@
transition: 0.3s;
&:active,
- &hover {
+ &:hover {
background-color: $btn-bg2-hover;
}
}
@@ -486,12 +428,25 @@
.is-other-month {
color: rgba(0, 0, 0, 0.25);
}
+
+ /* .bs-datepicker-buttons */
+ &-buttons {
+ display: flex;
+ flex-flow: row wrap;
+ justify-content: flex-end;
+ padding-top: 10px;
+ border-top: 1px solid $border-color;
+
+ .btn-default {
+ margin-left: 10px;
+ }
+ }
}
/* .bs-timepicker */
.bs-timepicker {
&-container {
- text-align: center;
+ padding: 10px 0;
}
&-label {
diff --git a/src/datepicker/bs-daterangepicker.component.ts b/src/datepicker/bs-daterangepicker.component.ts
index 845d707988..c50ea9d2b0 100644
--- a/src/datepicker/bs-daterangepicker.component.ts
+++ b/src/datepicker/bs-daterangepicker.component.ts
@@ -1,19 +1,20 @@
import {
Component, EventEmitter, Input, OnDestroy, OnInit, Output, ComponentRef, ElementRef,
Renderer,
- ViewContainerRef
+ ViewContainerRef, SimpleChanges, OnChanges
} from '@angular/core';
import { BsDaterangepickerContainerComponent } from './themes/bs/bs-daterangepicker-container.component';
import { Subscription } from 'rxjs/Subscription';
import { ComponentLoaderFactory } from '../component-loader/component-loader.factory';
import { ComponentLoader } from '../component-loader/component-loader.class';
+import { BsDatepickerConfig } from './bs-datepicker.config';
@Component({
selector: 'bs-daterangepicker,[bsDaterangepicker]',
exportAs: 'bsDaterangepicker',
template: ' '
})
-export class BsDaterangepickerComponent implements OnInit, OnDestroy {
+export class BsDaterangepickerComponent implements OnInit, OnDestroy, OnChanges {
/**
* Placement of a popover. Accepts: "top", "bottom", "left", "right"
*/
@@ -64,6 +65,9 @@ export class BsDaterangepickerComponent implements OnInit, OnDestroy {
this.bsValueChange.emit(value);
}
+ @Input() minDate: Date;
+ @Input() maxDate: Date;
+
@Output() bsValueChange: EventEmitter = new EventEmitter();
protected subscriptions: Subscription[] = [];
@@ -71,18 +75,40 @@ export class BsDaterangepickerComponent implements OnInit, OnDestroy {
private _datepicker: ComponentLoader;
private _datepickerRef: ComponentRef;
- constructor(_elementRef: ElementRef,
+ constructor(private _config: BsDatepickerConfig,
+ _elementRef: ElementRef,
_renderer: Renderer,
_viewContainerRef: ViewContainerRef,
cis: ComponentLoaderFactory) {
this._datepicker = cis
.createLoader(_elementRef, _viewContainerRef, _renderer);
- // .provide({provide: PopoverConfig, useValue: _config});
- // Object.assign(this, _config);
+ Object.assign(this, _config);
this.onShown = this._datepicker.onShown;
this.onHidden = this._datepicker.onHidden;
}
+ ngOnInit(): any {
+ this._datepicker.listen({
+ outsideClick: this.outsideClick,
+ triggers: this.triggers,
+ show: () => this.show()
+ });
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ if (!this._datepickerRef || !this._datepickerRef.instance) {
+ return;
+ }
+
+ if (changes.minDate) {
+ this._datepickerRef.instance.minDate = this.minDate;
+ }
+
+ if (changes.maxDate) {
+ this._datepickerRef.instance.maxDate = this.maxDate;
+ }
+ }
+
/**
* Opens an element’s datepicker. This is considered a “manual” triggering of
* the datepicker.
@@ -92,16 +118,20 @@ export class BsDaterangepickerComponent implements OnInit, OnDestroy {
return;
}
+ const config = Object.assign({}, this._config, {
+ displayMonths: 2,
+ value: this._bsValue,
+ minDate: this.minDate || this._config.minDate,
+ maxDate: this.maxDate || this._config.maxDate
+ });
+
this._datepickerRef = this._datepicker
+ .provide({provide: BsDatepickerConfig, useValue: config})
.attach(BsDaterangepickerContainerComponent)
.to(this.container)
.position({attachment: this.placement})
.show({placement: this.placement});
- // link with datepicker
- // set initial value of picker
- this._datepickerRef.instance.value = this._bsValue;
-
// if date changes from external source (model -> view)
this.subscriptions.push(this.bsValueChange.subscribe((value: Date[]) => {
this._datepickerRef.instance.value = value;
@@ -139,14 +169,6 @@ export class BsDaterangepickerComponent implements OnInit, OnDestroy {
this.show();
}
- ngOnInit(): any {
- this._datepicker.listen({
- outsideClick: this.outsideClick,
- triggers: this.triggers,
- show: () => this.show()
- });
- }
-
ngOnDestroy(): any {
this._datepicker.dispose();
}
diff --git a/src/datepicker/engine/calc-days-calendar.ts b/src/datepicker/engine/calc-days-calendar.ts
new file mode 100644
index 0000000000..175955f6cf
--- /dev/null
+++ b/src/datepicker/engine/calc-days-calendar.ts
@@ -0,0 +1,24 @@
+// user and model input should handle parsing and validating input values
+// should accept some options
+// todo: split out formatting
+import { DaysCalendarModel, MonthViewOptions } from '../models/index';
+import { getFirstDayOfMonth } from '../../bs-moment/utils/date-getters';
+import { getStartingDayOfCalendar } from '../utils/bs-calendar-utils';
+import { createMatrix } from '../utils/matrix-utils';
+
+export function calcDaysCalendar(startingDate: Date, options: MonthViewOptions): DaysCalendarModel {
+ const firstDay = getFirstDayOfMonth(startingDate);
+ const initialDate = getStartingDayOfCalendar(firstDay, options);
+
+ const matrixOptions = {
+ width: options.width,
+ height: options.height,
+ initialDate, shift: {day: 1}
+ };
+ const daysMatrix = createMatrix(matrixOptions, date => date);
+
+ return {
+ daysMatrix,
+ month: firstDay
+ };
+}
diff --git a/src/datepicker/engine/calc-month-view.ts b/src/datepicker/engine/calc-month-view.ts
deleted file mode 100644
index 982f68bb5f..0000000000
--- a/src/datepicker/engine/calc-month-view.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-// user and model input should handle parsing and validating input values
-// should accept some options
-// todo: split out formatting
-import { DaysCalendarModel, MonthViewOptions } from '../models/index';
-import { getFirstDayOfMonth } from '../../bs-moment/utils/date-getters';
-import { getStartingDayOfCalendar } from '../utils/bs-calendar-utils';
-import { shiftDate } from '../../bs-moment/utils/date-setters';
-
-export function calculateMonthModel(date: Date, options: MonthViewOptions): DaysCalendarModel {
- const firstDay = getFirstDayOfMonth(date);
-
- let prevValue = getStartingDayOfCalendar(firstDay, options);
- const daysCalendar = new Array(options.height);
- for (let i = 0; i < options.height; i++) {
- daysCalendar[i] = new Array(options.width);
- for (let j = 0; j < options.width; j++) {
- daysCalendar[i][j] = prevValue;
- prevValue = shiftDate(prevValue, {day: 1});
- }
- }
-
- return {
- daysMatrix: daysCalendar,
- month: firstDay
- };
-}
diff --git a/src/datepicker/engine/flag-month-view.ts b/src/datepicker/engine/flag-days-calendar.ts
similarity index 53%
rename from src/datepicker/engine/flag-month-view.ts
rename to src/datepicker/engine/flag-days-calendar.ts
index eccd677603..ed8c4a0086 100644
--- a/src/datepicker/engine/flag-month-view.ts
+++ b/src/datepicker/engine/flag-days-calendar.ts
@@ -1,7 +1,12 @@
-import { DayViewModel, MonthViewModel, WeekViewModel } from '../models/index';
-import { isSameMonth } from '../../bs-moment/utils/date-getters';
+import { DayViewModel, DaysCalendarViewModel, WeekViewModel } from '../models/index';
+import { isSameDay, isSameMonth } from '../../bs-moment/utils/date-getters';
+import { isSameOrAfter, isSameOrBefore } from '../../bs-moment/utils/date-compare';
+import { isMonthDisabled } from '../utils/bs-calendar-utils';
+import { shiftDate } from '../../bs-moment/utils/date-setters';
-export interface FlagMonthViewOptions {
+export interface FlagDaysCalendarOptions {
+ minDate: Date;
+ maxDate: Date;
hoveredDate: Date;
selectedDate: Date;
selectedRange: Date[];
@@ -9,31 +14,36 @@ export interface FlagMonthViewOptions {
monthIndex: number;
}
-export function flagMonthView(formattedMonth: MonthViewModel,
- options: FlagMonthViewOptions): MonthViewModel {
+export function flagDaysCalendar(formattedMonth: DaysCalendarViewModel,
+ options: FlagDaysCalendarOptions): DaysCalendarViewModel {
formattedMonth.weeks
.forEach((week: WeekViewModel, weekIndex: number) => {
week.days.forEach((day: DayViewModel, dayIndex: number) => {
// datepicker
const isOtherMonth = !isSameMonth(day.date, formattedMonth.month);
- const isHovered = !isOtherMonth && isSameDate(day.date, options.hoveredDate);
+ const isHovered = !isOtherMonth && isSameDay(day.date, options.hoveredDate);
// date range picker
- const isSelectionStart = !isOtherMonth && isSameDate(day.date, options.selectedRange[0]);
- const isSelectionEnd = !isOtherMonth && isSameDate(day.date, options.selectedRange[1]);
+ const isSelectionStart = !isOtherMonth && isSameDay(day.date, options.selectedRange[0]);
+ const isSelectionEnd = !isOtherMonth && isSameDay(day.date, options.selectedRange[1]);
- const isSelected = !isOtherMonth && isSameDate(day.date, options.selectedDate) ||
+ const isSelected = !isOtherMonth && isSameDay(day.date, options.selectedDate) ||
isSelectionStart || isSelectionEnd;
const isInRange = !isOtherMonth && isDateInRange(day.date, options.selectedRange, options.hoveredDate);
+
+ const isDisabled = isSameOrBefore(day.date, options.minDate, 'day')
+ || isSameOrAfter(day.date, options.maxDate, 'day');
+
// decide update or not
- const newDay = Object.assign(/*{},*/ day, {
+ const newDay = Object.assign({}, day, {
isOtherMonth,
isHovered,
isSelected,
isSelectionStart,
isSelectionEnd,
- isInRange
+ isInRange,
+ isDisabled
});
if (day.isOtherMonth !== newDay.isOtherMonth ||
@@ -41,6 +51,7 @@ export function flagMonthView(formattedMonth: MonthViewModel,
day.isSelected !== newDay.isSelected ||
day.isSelectionStart !== newDay.isSelectionStart ||
day.isSelectionEnd !== newDay.isSelectionEnd ||
+ day.isDisabled !== newDay.isDisabled ||
day.isInRange !== newDay.isInRange) {
week.days[dayIndex] = newDay;
}
@@ -52,17 +63,17 @@ export function flagMonthView(formattedMonth: MonthViewModel,
&& options.monthIndex !== options.displayMonths;
formattedMonth.hideRightArrow = options.monthIndex < options.displayMonths
&& (options.monthIndex + 1) !== options.displayMonths;
- return formattedMonth;
-}
-function isSameDate(date: Date, selectedDate: Date): boolean {
- if (!date || !selectedDate) {
- return false;
- }
+ formattedMonth.disableLeftArrow = isMonthDisabled(
+ shiftDate(formattedMonth.month, {month: -1}),
+ options.minDate,
+ options.maxDate);
+ formattedMonth.disableRightArrow = isMonthDisabled(
+ shiftDate(formattedMonth.month, {month: 1}),
+ options.minDate,
+ options.maxDate);
- return date.getFullYear() === selectedDate.getFullYear()
- && date.getMonth() === selectedDate.getMonth()
- && date.getDate() === selectedDate.getDate();
+ return formattedMonth;
}
function isDateInRange(date: Date, selectedRange: Date[], hoveredDate: Date): boolean {
diff --git a/src/datepicker/engine/flag-months-calendar.ts b/src/datepicker/engine/flag-months-calendar.ts
new file mode 100644
index 0000000000..02ba2a7e14
--- /dev/null
+++ b/src/datepicker/engine/flag-months-calendar.ts
@@ -0,0 +1,45 @@
+import { isSameMonth } from '../../bs-moment/utils/date-getters';
+import { MonthsCalendarViewModel, CalendarCellViewModel } from '../models/index';
+import { isMonthDisabled, isYearDisabled } from '../utils/bs-calendar-utils';
+import { shiftDate } from '../../bs-moment/utils/date-setters';
+
+export interface FlagMonthCalendarOptions {
+ minDate: Date;
+ maxDate: Date;
+ hoveredMonth: Date;
+ displayMonths: number;
+ monthIndex: number;
+}
+
+export function flagMonthsCalendar(monthCalendar: MonthsCalendarViewModel,
+ options: FlagMonthCalendarOptions): MonthsCalendarViewModel {
+ monthCalendar.months
+ .forEach((months: CalendarCellViewModel[], rowIndex: number) => {
+ months.forEach((month: CalendarCellViewModel, monthIndex: number) => {
+ const isHovered = isSameMonth(month.date, options.hoveredMonth);
+ const isDisabled = isMonthDisabled(month.date, options.minDate, options.maxDate);
+ const newMonth = Object.assign(/*{},*/ month, {isHovered, isDisabled});
+ if (month.isHovered !== newMonth.isHovered
+ || month.isDisabled !== newMonth.isDisabled) {
+ monthCalendar.months[rowIndex][monthIndex] = newMonth;
+ }
+ });
+ });
+
+ // todo: add check for linked calendars
+ monthCalendar.hideLeftArrow = options.monthIndex > 0
+ && options.monthIndex !== options.displayMonths;
+ monthCalendar.hideRightArrow = options.monthIndex < options.displayMonths
+ && (options.monthIndex + 1) !== options.displayMonths;
+
+ monthCalendar.disableLeftArrow = isYearDisabled(
+ shiftDate(monthCalendar.months[0][0].date, {year: -1}),
+ options.minDate,
+ options.maxDate);
+ monthCalendar.disableRightArrow = isYearDisabled(
+ shiftDate(monthCalendar.months[0][0].date, {year: 1}),
+ options.minDate,
+ options.maxDate);
+
+ return monthCalendar;
+}
diff --git a/src/datepicker/engine/flag-years-calendar.ts b/src/datepicker/engine/flag-years-calendar.ts
new file mode 100644
index 0000000000..bd9dbbb02b
--- /dev/null
+++ b/src/datepicker/engine/flag-years-calendar.ts
@@ -0,0 +1,47 @@
+import { isSameYear } from '../../bs-moment/utils/date-getters';
+import { YearsCalendarViewModel, CalendarCellViewModel } from '../models/index';
+import { isYearDisabled } from '../utils/bs-calendar-utils';
+import { shiftDate } from '../../bs-moment/utils/date-setters';
+
+export interface FlagYearsCalendarOptions {
+ minDate: Date;
+ maxDate: Date;
+ hoveredYear: Date;
+ displayMonths: number;
+ yearIndex: number;
+}
+
+export function flagYearsCalendar(yearsCalendar: YearsCalendarViewModel, options: FlagYearsCalendarOptions): YearsCalendarViewModel {
+ yearsCalendar.years
+ .forEach((years: CalendarCellViewModel[], rowIndex: number) => {
+ years.forEach((year: CalendarCellViewModel, yearIndex: number) => {
+ const isHovered = isSameYear(year.date, options.hoveredYear);
+ const isDisabled = isYearDisabled(year.date, options.minDate, options.maxDate);
+
+ const newMonth = Object.assign(/*{},*/ year, {isHovered, isDisabled});
+ if (year.isHovered !== newMonth.isHovered
+ || year.isDisabled !== newMonth.isDisabled) {
+ yearsCalendar.years[rowIndex][yearIndex] = newMonth;
+ }
+ });
+ });
+
+ // todo: add check for linked calendars
+ yearsCalendar.hideLeftArrow = options.yearIndex > 0
+ && options.yearIndex !== options.displayMonths;
+ yearsCalendar.hideRightArrow = options.yearIndex < options.displayMonths
+ && (options.yearIndex + 1) !== options.displayMonths;
+
+ yearsCalendar.disableLeftArrow = isYearDisabled(
+ shiftDate(yearsCalendar.years[0][0].date, {year: -1}),
+ options.minDate,
+ options.maxDate);
+ const i = yearsCalendar.years.length - 1;
+ const j = yearsCalendar.years[i].length - 1;
+ yearsCalendar.disableRightArrow = isYearDisabled(
+ shiftDate(yearsCalendar.years[i][j].date, {year: 1}),
+ options.minDate,
+ options.maxDate);
+
+ return yearsCalendar;
+}
diff --git a/src/datepicker/engine/format-month-view.ts b/src/datepicker/engine/format-days-calendar.ts
similarity index 68%
rename from src/datepicker/engine/format-month-view.ts
rename to src/datepicker/engine/format-days-calendar.ts
index 056712a843..f5fa6fdea0 100644
--- a/src/datepicker/engine/format-month-view.ts
+++ b/src/datepicker/engine/format-days-calendar.ts
@@ -1,10 +1,12 @@
-import { DatepickerFormatOptions, DaysCalendarModel, MonthViewModel, MonthViewOptions } from '../models/index';
+import {
+ DatepickerFormatOptions, DaysCalendarModel, DaysCalendarViewModel
+} from '../models/index';
import { formatDate } from '../../bs-moment/format';
import { getLocale } from '../../bs-moment/locale/locales.service';
-export function formatMonthView(daysCalendar: DaysCalendarModel,
- formatOptions: DatepickerFormatOptions,
- monthIndex: number): MonthViewModel {
+export function formatDaysCalendar(daysCalendar: DaysCalendarModel,
+ formatOptions: DatepickerFormatOptions,
+ monthIndex: number): DaysCalendarViewModel {
return {
month: daysCalendar.month,
monthTitle: formatDate(daysCalendar.month, formatOptions.monthTitle, formatOptions.locale),
@@ -19,10 +21,12 @@ export function formatMonthView(daysCalendar: DaysCalendarModel,
monthIndex, weekIndex, dayIndex
}))
})
- ),
+ )
};
}
export function getWeekNumbers(daysMatrix: Date[][], format: string, locale: string): string[] {
- return daysMatrix.map((days: Date[]) => days[0] ? formatDate(days[0], format, locale) : '');
+ return daysMatrix.map((days: Date[]) => days[0]
+ ? formatDate(days[0], format, locale)
+ : '');
}
diff --git a/src/datepicker/engine/format-months-calendar.ts b/src/datepicker/engine/format-months-calendar.ts
new file mode 100644
index 0000000000..ba35d1fc49
--- /dev/null
+++ b/src/datepicker/engine/format-months-calendar.ts
@@ -0,0 +1,27 @@
+import {
+ DatepickerFormatOptions, MonthsCalendarViewModel, CalendarCellViewModel
+} from '../models/index';
+import { startOf } from '../../bs-moment/utils/start-end-of';
+import { shiftDate } from '../../bs-moment/utils/date-setters';
+import { formatDate } from '../../bs-moment/format';
+import { createMatrix } from '../utils/matrix-utils';
+
+const height = 4;
+const width = 3;
+const shift = {month: 1};
+
+export function formatMonthsCalendar(viewDate: Date, formatOptions: DatepickerFormatOptions): MonthsCalendarViewModel {
+ const initialDate = startOf(viewDate, 'year');
+ const matrixOptions = {width, height, initialDate, shift};
+ const monthMatrix = createMatrix(matrixOptions,
+ date => ({
+ date,
+ label: formatDate(date, formatOptions.monthLabel, formatOptions.locale)
+ }));
+
+ return {
+ months: monthMatrix,
+ monthTitle: '',
+ yearTitle: formatDate(viewDate, formatOptions.yearTitle, formatOptions.locale)
+ };
+}
diff --git a/src/datepicker/engine/format-years-calendar.ts b/src/datepicker/engine/format-years-calendar.ts
new file mode 100644
index 0000000000..3edf40c52f
--- /dev/null
+++ b/src/datepicker/engine/format-years-calendar.ts
@@ -0,0 +1,38 @@
+import {
+ DatepickerFormatOptions, YearsCalendarViewModel, CalendarCellViewModel
+} from '../models/index';
+import { shiftDate } from '../../bs-moment/utils/date-setters';
+import { formatDate } from '../../bs-moment/format';
+import { TimeUnit } from '../../bs-moment/types';
+import { createMatrix } from '../utils/matrix-utils';
+
+const height = 4;
+const width = 4;
+export const yearsPerCalendar = height * width;
+const initialShift = (Math.floor(yearsPerCalendar / 2) - 1) * -1;
+const shift = {year: 1};
+
+export function formatYearsCalendar(viewDate: Date, formatOptions: DatepickerFormatOptions): YearsCalendarViewModel {
+
+ const initialDate = shiftDate(viewDate, {year: initialShift});
+ const matrixOptions = {width, height, initialDate, shift};
+ const yearsMatrix = createMatrix(matrixOptions,
+ date => ({
+ date,
+ label: formatDate(date, formatOptions.yearLabel, formatOptions.locale)
+ }));
+ const yearTitle = formatYearRangeTitle(yearsMatrix, formatOptions);
+
+ return {
+ years: yearsMatrix,
+ monthTitle: '',
+ yearTitle
+ };
+}
+
+function formatYearRangeTitle(yearsMatrix: CalendarCellViewModel[][], formatOptions: DatepickerFormatOptions): string {
+ const from = formatDate(yearsMatrix[0][0].date, formatOptions.yearTitle, formatOptions.locale);
+ const to = formatDate(yearsMatrix[height - 1][width - 1].date, formatOptions.yearTitle, formatOptions.locale);
+
+ return `${from} - ${to}`;
+}
diff --git a/src/datepicker/engine/view-mode.ts b/src/datepicker/engine/view-mode.ts
new file mode 100644
index 0000000000..8309f03647
--- /dev/null
+++ b/src/datepicker/engine/view-mode.ts
@@ -0,0 +1,5 @@
+import { BsDatepickerViewMode } from '../models/index';
+
+export function canSwitchMode(mode: BsDatepickerViewMode): boolean {
+ return true;
+}
diff --git a/src/datepicker/models/index.ts b/src/datepicker/models/index.ts
index 60200f7003..cca9d30db7 100644
--- a/src/datepicker/models/index.ts
+++ b/src/datepicker/models/index.ts
@@ -1,17 +1,32 @@
-import { Locale } from '../../bs-moment/locale/locale.class';
import { TimeUnit } from '../../bs-moment/types';
+import { Observable } from 'rxjs/Observable';
+import { EventEmitter } from '@angular/core';
+import { BsDatepickerEffects } from '../reducer/bs-datepicker.effects';
+import { BsCustomDates } from '../themes/bs/bs-custom-dates-view.component';
-export interface DaysCalendarModel {
- daysMatrix: Date[][];
- month: Date;
+export type BsDatepickerViewMode = 'day' | 'month' | 'year';
+
+/** *************** */
+// navigation bar settings
+export interface NavigationViewModel {
+ monthTitle: string;
+ yearTitle: string;
+ hideLeftArrow?: boolean;
+ hideRightArrow?: boolean;
+ disableLeftArrow?: boolean;
+ disableRightArrow?: boolean;
}
-export interface DayViewModel {
+export interface CalendarCellViewModel {
date: Date;
label: string;
- // flag step
isDisabled?: boolean;
isHovered?: boolean;
+}
+
+/** *************** */
+// days matrix: day cell view model
+export interface DayViewModel extends CalendarCellViewModel {
isOtherMonth?: boolean;
isInRange?: boolean;
isSelectionStart?: boolean;
@@ -27,30 +42,58 @@ export interface WeekViewModel {
days: DayViewModel[];
}
-export interface MonthViewModel {
+// todo: split navigation settings
+export interface DaysCalendarViewModel extends NavigationViewModel {
weeks: WeekViewModel[];
- // format step
+ // additional information
month: Date;
- monthTitle: string;
- yearTitle: string;
weekNumbers: string[];
weekdays: string[];
- // flag step
- hideLeftArrow?: boolean;
- hideRightArrow?: boolean;
}
+/** *************** */
+// months calendar
+export interface MonthsCalendarViewModel extends NavigationViewModel {
+ months: CalendarCellViewModel[][];
+}
+
+/** *************** */
+// years calendar
+export interface YearsCalendarViewModel extends NavigationViewModel {
+ years: CalendarCellViewModel[][];
+}
+
+/** *************** */
+
+// math model
+/** *************** */
+
+// days Date's array
+export interface DaysCalendarModel {
+ daysMatrix: Date[][];
+ month: Date;
+}
+
+/** *************** */
+// some func options
export interface MonthViewOptions {
width?: number;
height?: number;
firstDayOfWeek?: number;
}
+/** *************** */
+// rendering options
export interface DatepickerFormatOptions {
locale: string;
+
monthTitle: string;
yearTitle: string;
+
dayLabel: string;
+ monthLabel: string;
+ yearLabel: string;
+
weekNumbers: string;
}
@@ -59,15 +102,23 @@ export interface DatepickerRenderOptions {
displayMonths?: number;
}
-export type DateFormatterFn = (date: Date, format: string, locale?: Locale) => string;
-
+/** *************** */
// events
+/** *************** */
+export enum BsNavigationDirection {UP, DOWN}
+// used for navigation events, to change view date in state
export interface BsNavigationEvent {
- step: TimeUnit;
+ direction?: BsNavigationDirection;
+ step?: TimeUnit;
+}
+
+export interface BsViewNavigationEvent {
+ unit?: TimeUnit;
+ viewMode: BsDatepickerViewMode;
}
-export interface DayHoverEvent {
- day: DayViewModel;
+export interface CellHoverEvent {
+ cell: CalendarCellViewModel;
isHovered: boolean;
}
diff --git a/src/datepicker/reducer/_defaults.ts b/src/datepicker/reducer/_defaults.ts
index 65e03afded..55bba53a72 100644
--- a/src/datepicker/reducer/_defaults.ts
+++ b/src/datepicker/reducer/_defaults.ts
@@ -2,19 +2,5 @@ import { DatepickerFormatOptions, DatepickerRenderOptions, MonthViewOptions } fr
export const defaultMonthOptions: MonthViewOptions = {
width: 7,
- height: 6,
- firstDayOfWeek: 1
-};
-
-export const defaultFormatOptions: DatepickerFormatOptions = {
- locale: 'en',
- monthTitle: 'MMMM',
- yearTitle: 'YYYY',
- dayLabel: 'D',
- weekNumbers: 'w'
-};
-
-export const defaultRenderOptions: DatepickerRenderOptions = {
- displayMonths: 1,
- showWeekNumbers: true
+ height: 6
};
diff --git a/src/datepicker/reducer/bs-datepicker.actions.ts b/src/datepicker/reducer/bs-datepicker.actions.ts
index 327eb2977e..1e85b5b01e 100644
--- a/src/datepicker/reducer/bs-datepicker.actions.ts
+++ b/src/datepicker/reducer/bs-datepicker.actions.ts
@@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
-import { DatepickerRenderOptions, DayHoverEvent } from '../models/index';
-import { Action } from '../../mini-ngrx/index';
import { TimeUnit } from '../../bs-moment/types';
+import { Action } from '../../mini-ngrx/index';
+import { BsDatepickerViewMode, BsViewNavigationEvent, CellHoverEvent, DatepickerRenderOptions } from '../models/index';
@Injectable()
export class BsDatepickerActions {
@@ -9,48 +9,54 @@ export class BsDatepickerActions {
static readonly FORMAT = '[datepicker] format datepicker values';
static readonly FLAG = '[datepicker] set flags';
static readonly SELECT = '[datepicker] select date';
- static readonly STEP_NAVIGATION = '[datepicker] shift view date';
- static readonly RENDER_OPTIONS = '[datepicker] update render options';
+ static readonly NAVIGATE_OFFSET = '[datepicker] shift view date';
+ static readonly NAVIGATE_TO = '[datepicker] change view date';
+ static readonly SET_OPTIONS = '[datepicker] update render options';
static readonly HOVER = '[datepicker] hover date';
+ static readonly CHANGE_VIEWMODE = '[datepicker] switch view mode';
+
+ static readonly SET_MIN_DATE = '[datepicker] set min date';
+ static readonly SET_MAX_DATE = '[datepicker] set max date';
static readonly SELECT_RANGE = '[daterangepicker] select dates range';
- calculate(viewDate: Date): Action {
- return {
- type: BsDatepickerActions.CALCULATE,
- payload: viewDate
- };
- }
+ calculate(): Action {return {type: BsDatepickerActions.CALCULATE}; }
- format(): Action {
+ format(): Action {return {type: BsDatepickerActions.FORMAT}; }
+
+ flag(): Action { return {type: BsDatepickerActions.FLAG}; }
+
+ select(date: Date): Action {
return {
- type: BsDatepickerActions.FORMAT
+ type: BsDatepickerActions.SELECT,
+ payload: date
};
}
- flag(): Action {
+ changeViewMode(event: BsDatepickerViewMode): Action {
return {
- type: BsDatepickerActions.FLAG
+ type: BsDatepickerActions.CHANGE_VIEWMODE,
+ payload: event
};
}
- select(date: Date): Action {
+ navigateTo(event: BsViewNavigationEvent): Action {
return {
- type: BsDatepickerActions.SELECT,
- payload: date
+ type: BsDatepickerActions.NAVIGATE_TO,
+ payload: event
};
}
navigateStep(step: TimeUnit): Action {
return {
- type: BsDatepickerActions.STEP_NAVIGATION,
+ type: BsDatepickerActions.NAVIGATE_OFFSET,
payload: step
};
}
- renderOptions(options: DatepickerRenderOptions): Action {
+ setOptions(options: DatepickerRenderOptions): Action {
return {
- type: BsDatepickerActions.RENDER_OPTIONS,
+ type: BsDatepickerActions.SET_OPTIONS,
payload: options
};
}
@@ -63,10 +69,24 @@ export class BsDatepickerActions {
};
}
- hover(event: DayHoverEvent): Action {
+ hoverDay(event: CellHoverEvent): Action {
return {
type: BsDatepickerActions.HOVER,
- payload: event.isHovered ? event.day.date : null
+ payload: event.isHovered ? event.cell.date : null
+ };
+ }
+
+ minDate(date: Date): Action {
+ return {
+ type: BsDatepickerActions.SET_MIN_DATE,
+ payload: date
+ };
+ }
+
+ maxDate(date: Date): Action {
+ return {
+ type: BsDatepickerActions.SET_MAX_DATE,
+ payload: date
};
}
}
diff --git a/src/datepicker/reducer/bs-datepicker.effects.ts b/src/datepicker/reducer/bs-datepicker.effects.ts
index 981a61988c..4c757f0a27 100644
--- a/src/datepicker/reducer/bs-datepicker.effects.ts
+++ b/src/datepicker/reducer/bs-datepicker.effects.ts
@@ -1,19 +1,214 @@
import { Injectable } from '@angular/core';
-import { BsDatepickerStore } from './bs-datepicker.store';
+import 'rxjs/add/operator/filter';
+import 'rxjs/add/operator/map';
+import { Observable } from 'rxjs/Observable';
+import { getFullYear, getMonth } from '../../bs-moment/utils/date-getters';
+import { BsDatepickerContainer } from '../base/bs-datepicker-container';
+import { BsDatepickerConfig } from '../bs-datepicker.config';
+import {
+ BsDatepickerViewMode,
+ BsNavigationEvent,
+ CalendarCellViewModel,
+ CellHoverEvent,
+ DatepickerRenderOptions,
+ DaysCalendarViewModel,
+ DayViewModel,
+ MonthsCalendarViewModel,
+ YearsCalendarViewModel
+} from '../models/index';
import { BsDatepickerActions } from './bs-datepicker.actions';
+import { BsDatepickerStore } from './bs-datepicker.store';
+import { Subscription } from 'rxjs/Subscription';
@Injectable()
export class BsDatepickerEffects {
- // constructor(private _bsDatepickerStore: BsDatepickerStore,
- // private _actions: BsDatepickerActions) {
- // this.onMonthCalendarCalculation();
- // }
- //
- // onMonthCalendarCalculation() {
- // this._bsDatepickerStore
- // .select(state => state.monthModel)
- // .filter(monthModel => !!monthModel)
- // .subscribe(month =>
- // this._bsDatepickerStore.dispatch(this._actions.format()));
- // }
+
+ viewMode: Observable;
+ daysCalendar: Observable;
+ monthsCalendar: Observable;
+ yearsCalendar: Observable;
+ options: Observable;
+
+ private _store: BsDatepickerStore;
+ private _subs: Subscription[] = [];
+
+ constructor(private _actions: BsDatepickerActions) {
+ }
+
+ init(_bsDatepickerStore: BsDatepickerStore): BsDatepickerEffects {
+ this._store = _bsDatepickerStore;
+
+ return this;
+ }
+
+ /** setters */
+
+ setValue(value: Date) {
+ this._store.dispatch(this._actions.select(value));
+ }
+
+ setMinDate(value: Date): BsDatepickerEffects {
+ this._store.dispatch(this._actions.minDate(value));
+
+ return this;
+ }
+
+ setMaxDate(value: Date): BsDatepickerEffects {
+ this._store.dispatch(this._actions.maxDate(value));
+
+ return this;
+ }
+
+ /* Set rendering options */
+ setOptions(_config: BsDatepickerConfig): BsDatepickerEffects {
+ this._store.dispatch(this._actions.setOptions(_config));
+
+ return this;
+ }
+
+ /** view to mode bindings */
+ setBindings(container: BsDatepickerContainer): BsDatepickerEffects {
+ container.daysCalendar = this._store
+ .select(state => state.flaggedMonths)
+ .filter(months => !!months);
+
+ // month calendar
+ container.monthsCalendar = this._store
+ .select(state => state.flaggedMonthsCalendar)
+ .filter(months => !!months);
+
+ // year calendar
+ container.yearsCalendar = this._store
+ .select(state => state.yearsCalendarFlagged)
+ .filter(years => !!years);
+
+ container.viewMode = this._store
+ .select(state => state.view.mode);
+
+ container.options = this._store.select(state => state.showWeekNumbers)
+ .map(showWeekNumbers => ({showWeekNumbers}));
+
+ return this;
+ }
+
+ /** event handlers*/
+ setEventHandlers(container: BsDatepickerContainer): BsDatepickerEffects {
+ container.setViewMode = (event: BsDatepickerViewMode): void => {
+ this._store.dispatch(this._actions.changeViewMode(event));
+ };
+
+ container.navigateTo = (event: BsNavigationEvent): void => {
+ this._store.dispatch(this._actions.navigateStep(event.step));
+ };
+
+ container.dayHoverHandler = (event: CellHoverEvent): void => {
+ const _cell = event.cell as DayViewModel;
+ if (_cell.isOtherMonth || _cell.isDisabled) {
+ return;
+ }
+
+ this._store.dispatch(this._actions.hoverDay(event));
+ _cell.isHovered = event.isHovered;
+ };
+
+ container.monthHoverHandler = (event: CellHoverEvent): void => {
+ event.cell.isHovered = event.isHovered;
+ };
+
+ container.yearHoverHandler = (event: CellHoverEvent): void => {
+ event.cell.isHovered = event.isHovered;
+ };
+
+ /** select handlers */
+ // container.daySelectHandler = (day: DayViewModel): void => {
+ // if (day.isOtherMonth || day.isDisabled) {
+ // return;
+ // }
+ // this._store.dispatch(this._actions.select(day.date));
+ // };
+
+ container.monthSelectHandler = (event: CalendarCellViewModel): void => {
+ if (event.isDisabled) { return; }
+ this._store.dispatch(this._actions.navigateTo({
+ unit: {month: getMonth(event.date)},
+ viewMode: 'day'
+ }));
+ };
+
+ container.yearSelectHandler = (event: CalendarCellViewModel): void => {
+ if (event.isDisabled) { return; }
+ this._store.dispatch(this._actions.navigateTo({
+ unit: {year: getFullYear(event.date)},
+ viewMode: 'month'
+ }));
+ };
+
+ return this;
+ }
+
+ registerDatepickerSideEffects(): BsDatepickerEffects {
+ this._subs.push(
+ this._store.select(state => state.view)
+ .subscribe(view => {
+ this._store.dispatch(this._actions.calculate());
+ }));
+
+ // format calendar values on month model change
+ this._subs.push(
+ this._store
+ .select(state => state.monthsModel)
+ .filter(monthModel => !!monthModel)
+ .subscribe(month =>
+ this._store.dispatch(this._actions.format())));
+
+ // flag day values
+ this._subs.push(
+ this._store
+ .select(state => state.formattedMonths)
+ .filter(month => !!month)
+ .subscribe(month =>
+ this._store.dispatch(this._actions.flag())));
+
+ // flag day values
+ this._subs.push(
+ this._store.select(state => state.selectedDate)
+ .filter(selectedDate => !!selectedDate)
+ .subscribe(selectedDate =>
+ this._store.dispatch(this._actions.flag())));
+
+ // flag for date range picker
+ this._subs.push(
+ this._store.select(state => state.selectedRange)
+ .filter(selectedRange => !!selectedRange)
+ .subscribe(selectedRange =>
+ this._store.dispatch(this._actions.flag())));
+
+ // monthsCalendar
+ this._subs.push(
+ this._store
+ .select(state => state.monthsCalendar)
+ .subscribe(() => this._store.dispatch(this._actions.flag())));
+
+ // years calendar
+ this._subs.push(
+ this._store
+ .select(state => state.yearsCalendarModel)
+ .filter(state => !!state)
+ .subscribe(() => this._store.dispatch(this._actions.flag())));
+
+ // on hover
+ this._subs.push(
+ this._store.select(state => state.hoveredDate)
+ .filter(hoveredDate => !!hoveredDate)
+ .subscribe(hoveredDate =>
+ this._store.dispatch(this._actions.flag())));
+
+ return this;
+ }
+
+ destroy(): void {
+ for (const sub of this._subs) {
+ sub.unsubscribe();
+ }
+ }
}
diff --git a/src/datepicker/reducer/bs-datepicker.reducer.ts b/src/datepicker/reducer/bs-datepicker.reducer.ts
index 5ba1cbe80b..10fa14f962 100644
--- a/src/datepicker/reducer/bs-datepicker.reducer.ts
+++ b/src/datepicker/reducer/bs-datepicker.reducer.ts
@@ -1,56 +1,70 @@
-import { BsDatepickerState, initialDatepickerState } from './bs-datepicker.state';
+import {
+ BsDatepickerState, initialDatepickerState
+} from './bs-datepicker.state';
import { Action } from '../../mini-ngrx/index';
import { BsDatepickerActions } from './bs-datepicker.actions';
-import { calculateMonthModel } from '../engine/calc-month-view';
-import { formatMonthView } from '../engine/format-month-view';
-import { flagMonthView } from '../engine/flag-month-view';
-import { shiftDate } from '../../bs-moment/utils/date-setters';
+import { calcDaysCalendar } from '../engine/calc-days-calendar';
+import { formatDaysCalendar } from '../engine/format-days-calendar';
+import { flagDaysCalendar } from '../engine/flag-days-calendar';
+import { shiftDate, setDate } from '../../bs-moment/utils/date-setters';
+import { canSwitchMode } from '../engine/view-mode';
+import { formatMonthsCalendar } from '../engine/format-months-calendar';
+import { flagMonthsCalendar } from '../engine/flag-months-calendar';
+import {
+ formatYearsCalendar, yearsPerCalendar
+} from '../engine/format-years-calendar';
+import { flagYearsCalendar } from '../engine/flag-years-calendar';
+import { BsViewNavigationEvent, DatepickerFormatOptions } from '../models/index';
+import { isArray } from '../../bs-moment/utils/type-checks';
export function bsDatepickerReducer(state = initialDatepickerState, action: Action): BsDatepickerState {
+ if (!(/hover/.test(action.type))) {
+ console.log(action);
+ }
switch (action.type) {
-/*
- case (BsDatepickerActions.INIT): {
- const locale = getLocale(state.formatOptions.locale);
- const monthViewOptions = Object.assign({}, state.monthViewOptions, {firstDayOfWeek: locale.firstDayOfWeek()});
- const monthModel = calculateMonthModel(state.viewDate, monthViewOptions);
- return Object.assign({}, state, {locale, monthViewOptions, monthModel});
- }
-*/
-
- case (BsDatepickerActions.CALCULATE): {
- const displayMonths = state.renderOptions.displayMonths;
- const monthsModel = new Array(displayMonths);
- let viewDate = state.viewDate;
-
- for (let monthIndex = 0; monthIndex < displayMonths; monthIndex++) {
- // todo: for unlinked calendars it will be harder
- monthsModel[monthIndex] = calculateMonthModel(viewDate, state.monthViewOptions);
- viewDate = shiftDate(viewDate, {month: 1});
- }
- return Object.assign({}, state, {monthsModel});
+ case(BsDatepickerActions.CALCULATE): {
+ return calculateReducer(state);
}
- case (BsDatepickerActions.FORMAT): {
- const formattedMonths = state.monthsModel
- .map((month, monthIndex) => formatMonthView(month, state.formatOptions, monthIndex));
- return Object.assign({}, state, {formattedMonths});
+ case(BsDatepickerActions.FORMAT): {
+ return formatReducer(state, action);
}
- case (BsDatepickerActions.FLAG): {
- const flaggedMonths = state.formattedMonths
- .map((formattedMonth, monthIndex) => flagMonthView(formattedMonth, {
- hoveredDate: state.hoveredDate,
- selectedDate: state.selectedDate,
- selectedRange: state.selectedRange,
- displayMonths: state.renderOptions.displayMonths,
- monthIndex
- }));
- return Object.assign({}, state, {flaggedMonths});
+ case(BsDatepickerActions.FLAG): {
+ return flagReducer(state, action);
}
- case(BsDatepickerActions.STEP_NAVIGATION): {
- const viewDate = shiftDate(state.viewDate, action.payload);
- return Object.assign({}, state, {viewDate});
+ case(BsDatepickerActions.NAVIGATE_OFFSET): {
+ const date = shiftDate(state.view.date, action.payload);
+ const newState = {
+ view: {
+ mode: state.view.mode,
+ date
+ }
+ };
+
+ return Object.assign({}, state, newState);
+ }
+
+ case(BsDatepickerActions.NAVIGATE_TO): {
+ const payload: BsViewNavigationEvent = action.payload;
+
+ const date = setDate(state.view.date, payload.unit);
+ const mode = payload.viewMode;
+ const newState = {view: {date, mode}};
+
+ return Object.assign({}, state, newState);
+ }
+
+ case(BsDatepickerActions.CHANGE_VIEWMODE): {
+ if (!canSwitchMode(action.payload)) {
+ return state;
+ }
+ const date = state.view.date;
+ const mode = action.payload;
+ const newState = {view: {date, mode}};
+
+ return Object.assign({}, state, newState);
}
case(BsDatepickerActions.HOVER): {
@@ -58,11 +72,42 @@ export function bsDatepickerReducer(state = initialDatepickerState, action: Acti
}
case(BsDatepickerActions.SELECT): {
- return Object.assign({}, state, {selectedDate: action.payload});
+ const newState = {
+ selectedDate: action.payload,
+ view: state.view
+ };
+
+ if (action.payload) {
+ newState.view = {
+ date: action.payload,
+ mode: state.view.mode
+ };
+ }
+
+ return Object.assign({}, state, newState);
}
- case(BsDatepickerActions.RENDER_OPTIONS): {
- return Object.assign({}, state, {renderOptions: action.payload});
+ case(BsDatepickerActions.SET_OPTIONS): {
+ const newState = action.payload;
+ // looks not really good
+ if (newState.value) {
+ newState.view = state.view;
+ if (isArray(newState.value)) {
+ newState.view = {
+ mode: state.view.mode,
+ date: newState.value[0]
+ };
+ newState.selectedRange = newState.value;
+ } else {
+ newState.view = {
+ mode: state.view.mode,
+ date: newState.value
+ };
+ newState.selectedDate = newState.value;
+ }
+ }
+
+ return Object.assign({}, state, newState);
}
// date range picker
@@ -70,6 +115,160 @@ export function bsDatepickerReducer(state = initialDatepickerState, action: Acti
return Object.assign({}, state, {selectedRange: action.payload});
}
- default: return state;
+ case(BsDatepickerActions.SET_MIN_DATE): {
+ return Object.assign({}, state, {
+ minDate: action.payload
+ });
+ }
+ case(BsDatepickerActions.SET_MAX_DATE): {
+ return Object.assign({}, state, {
+ maxDate: action.payload
+ });
+ }
+
+ default:
+ return state;
+ }
+}
+
+function calculateReducer(state: BsDatepickerState): BsDatepickerState {
+ // how many calendars
+ const displayMonths = state.displayMonths;
+ // use selected date on initial rendering if set
+ let viewDate = state.view.date;
+
+ if (state.view.mode === 'day') {
+ const monthsModel = new Array(displayMonths);
+ for (let monthIndex = 0; monthIndex < displayMonths; monthIndex++) {
+ // todo: for unlinked calendars it will be harder
+ monthsModel[monthIndex] = calcDaysCalendar(viewDate, state.monthViewOptions);
+ viewDate = shiftDate(viewDate, {month: 1});
+ }
+
+ return Object.assign({}, state, {monthsModel});
}
+
+ if (state.view.mode === 'month') {
+ const monthsCalendar = new Array(displayMonths);
+ for (let calendarIndex = 0; calendarIndex < displayMonths; calendarIndex++) {
+ // todo: for unlinked calendars it will be harder
+ monthsCalendar[calendarIndex] = formatMonthsCalendar(viewDate, getFormatOptions(state));
+ viewDate = shiftDate(viewDate, {year: 1});
+ }
+
+ return Object.assign({}, state, {monthsCalendar});
+ }
+
+ if (state.view.mode === 'year') {
+ const yearsCalendarModel = new Array(displayMonths);
+
+ for (let calendarIndex = 0; calendarIndex < displayMonths; calendarIndex++) {
+ // todo: for unlinked calendars it will be harder
+ yearsCalendarModel[calendarIndex] = formatYearsCalendar(viewDate, getFormatOptions(state));
+ viewDate = shiftDate(viewDate, {year: yearsPerCalendar});
+ }
+
+ return Object.assign({}, state, {yearsCalendarModel});
+ }
+
+ return state;
+}
+
+function formatReducer(state: BsDatepickerState, action: Action): BsDatepickerState {
+ if (state.view.mode === 'day') {
+ const formattedMonths = state.monthsModel
+ .map((month, monthIndex) => formatDaysCalendar(month, getFormatOptions(state), monthIndex));
+
+ return Object.assign({}, state, {formattedMonths});
+ }
+
+ // how many calendars
+ const displayMonths = state.displayMonths;
+ // check initial rendering
+ // use selected date on initial rendering if set
+ let viewDate = state.view.date;
+
+ if (state.view.mode === 'month') {
+ const monthsCalendar = new Array(displayMonths);
+ for (let calendarIndex = 0; calendarIndex < displayMonths; calendarIndex++) {
+ // todo: for unlinked calendars it will be harder
+ monthsCalendar[calendarIndex] = formatMonthsCalendar(viewDate, getFormatOptions(state));
+ viewDate = shiftDate(viewDate, {year: 1});
+ }
+
+ return Object.assign({}, state, {monthsCalendar});
+ }
+
+ if (state.view.mode === 'year') {
+ const yearsCalendarModel = new Array(displayMonths);
+ for (let calendarIndex = 0; calendarIndex < displayMonths; calendarIndex++) {
+ // todo: for unlinked calendars it will be harder
+ yearsCalendarModel[calendarIndex] = formatYearsCalendar(viewDate, getFormatOptions(state));
+ viewDate = shiftDate(viewDate, {year: 16});
+ }
+
+ return Object.assign({}, state, {yearsCalendarModel});
+ }
+
+ return state;
+}
+
+function flagReducer(state: BsDatepickerState, action: Action): BsDatepickerState {
+ if (state.view.mode === 'day') {
+ const flaggedMonths = state.formattedMonths
+ .map((formattedMonth, monthIndex) => flagDaysCalendar(formattedMonth, {
+ minDate: state.minDate,
+ maxDate: state.maxDate,
+ hoveredDate: state.hoveredDate,
+ selectedDate: state.selectedDate,
+ selectedRange: state.selectedRange,
+ displayMonths: state.displayMonths,
+ monthIndex
+ }));
+
+ return Object.assign({}, state, {flaggedMonths});
+ }
+
+ if (state.view.mode === 'month') {
+ const flaggedMonthsCalendar = state.monthsCalendar
+ .map((formattedMonth, monthIndex) => flagMonthsCalendar(formattedMonth, {
+ minDate: state.minDate,
+ maxDate: state.maxDate,
+ hoveredMonth: state.hoveredMonth,
+ displayMonths: state.displayMonths,
+ monthIndex
+ }));
+
+ return Object.assign({}, state, {flaggedMonthsCalendar});
+ }
+
+ if (state.view.mode === 'year') {
+ const yearsCalendarFlagged = state.yearsCalendarModel
+ .map((formattedMonth, yearIndex) => flagYearsCalendar(formattedMonth, {
+ minDate: state.minDate,
+ maxDate: state.maxDate,
+ hoveredYear: state.hoveredYear,
+ displayMonths: state.displayMonths,
+ yearIndex
+ }));
+
+ return Object.assign({}, state, {yearsCalendarFlagged});
+ }
+
+ return state;
+}
+
+function getFormatOptions(state: BsDatepickerState): DatepickerFormatOptions {
+ return {
+ locale: state.locale,
+
+ monthTitle: state.monthTitle,
+ yearTitle: state.yearTitle,
+
+ dayLabel: state.dayLabel,
+ monthLabel: state.monthLabel,
+ yearLabel: state.yearLabel,
+
+ weekNumbers: state.weekNumbers
+ };
}
diff --git a/src/datepicker/reducer/bs-datepicker.state.ts b/src/datepicker/reducer/bs-datepicker.state.ts
index 26693a9b8b..1e5ceb17a8 100644
--- a/src/datepicker/reducer/bs-datepicker.state.ts
+++ b/src/datepicker/reducer/bs-datepicker.state.ts
@@ -1,32 +1,70 @@
import {
- DatepickerFormatOptions, DatepickerRenderOptions, DaysCalendarModel, MonthViewModel,
- MonthViewOptions
+ BsDatepickerViewMode,
+ DatepickerFormatOptions, DatepickerRenderOptions, DaysCalendarModel,
+ DaysCalendarViewModel, MonthsCalendarViewModel,
+ MonthViewOptions, YearsCalendarViewModel
} from '../models/index';
-import { defaultFormatOptions, defaultMonthOptions, defaultRenderOptions } from './_defaults';
+import { defaultMonthOptions } from './_defaults';
+import { BsDatepickerConfig } from '../bs-datepicker.config';
+
+export interface BsDatepickerViewState {
+ date: Date;
+ mode: BsDatepickerViewMode;
+}
+
+export class BsDatepickerState implements DatepickerRenderOptions, DatepickerFormatOptions {
+ // date picker
+ selectedDate?: Date;
+ // daterange picker
+ selectedRange?: Date[];
-export class BsDatepickerState {
// initial date of calendar, today by default
- viewDate: Date;
+ view: BsDatepickerViewState;
+
+ // bounds
+ minDate?: Date;
+ maxDate?: Date;
+
hoveredDate?: Date;
- selectedDate?: Date;
+ hoveredMonth?: Date;
+ hoveredYear?: Date;
+ // days calendar
monthsModel?: DaysCalendarModel[];
- formattedMonths?: MonthViewModel[];
- flaggedMonths?: MonthViewModel[];
+ formattedMonths?: DaysCalendarViewModel[];
+ flaggedMonths?: DaysCalendarViewModel[];
+
+ // months calendar
+ monthsCalendar?: MonthsCalendarViewModel[];
+ flaggedMonthsCalendar?: MonthsCalendarViewModel[];
+ // years calendar
+ yearsCalendarModel?: YearsCalendarViewModel[];
+ yearsCalendarFlagged?: YearsCalendarViewModel[];
+
+ // options
monthViewOptions: MonthViewOptions;
- formatOptions: DatepickerFormatOptions;
- renderOptions: DatepickerRenderOptions;
+ // DatepickerRenderOptions
+ showWeekNumbers?: boolean;
+ displayMonths?: number;
- // daterange picker
- selectedRange?: Date[];
+ // DatepickerFormatOptions
+ locale: string;
+
+ monthTitle: string;
+ yearTitle: string;
+
+ dayLabel: string;
+ monthLabel: string;
+ yearLabel: string;
+
+ weekNumbers: string;
}
-export const initialDatepickerState: BsDatepickerState = {
- viewDate: new Date(),
- selectedRange: [],
- monthViewOptions: defaultMonthOptions,
- formatOptions: defaultFormatOptions,
- renderOptions: defaultRenderOptions
-};
+export const initialDatepickerState: BsDatepickerState = Object.assign(
+ new BsDatepickerConfig(), {
+ view: {date: new Date(), mode: 'day'} as BsDatepickerViewState,
+ selectedRange: [],
+ monthViewOptions: defaultMonthOptions
+ });
diff --git a/src/datepicker/themes/bs/bs-calendar-layout.component.ts b/src/datepicker/themes/bs/bs-calendar-layout.component.ts
new file mode 100644
index 0000000000..a15a4cecb6
--- /dev/null
+++ b/src/datepicker/themes/bs/bs-calendar-layout.component.ts
@@ -0,0 +1,22 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'bs-calendar-layout',
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+})
+export class BsCalendarLayoutComponent {}
diff --git a/src/datepicker/themes/bs/bs-current-date-view.component.ts b/src/datepicker/themes/bs/bs-current-date-view.component.ts
new file mode 100644
index 0000000000..bf4df639a6
--- /dev/null
+++ b/src/datepicker/themes/bs/bs-current-date-view.component.ts
@@ -0,0 +1,9 @@
+import { Component, Input } from '@angular/core';
+
+@Component({
+ selector: 'bs-current-date',
+ template: `{{ title }}
`
+})
+export class BsCurrentDateViewComponent {
+ @Input() title: string;
+}
diff --git a/src/datepicker/themes/bs/bs-custom-dates-view.component.ts b/src/datepicker/themes/bs/bs-custom-dates-view.component.ts
new file mode 100644
index 0000000000..1f019c2ac8
--- /dev/null
+++ b/src/datepicker/themes/bs/bs-custom-dates-view.component.ts
@@ -0,0 +1,21 @@
+import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
+
+export interface BsCustomDates {
+ label: string;
+ value: Date | Date[];
+}
+
+@Component({
+ selector: 'bs-custom-date-view',
+ template: `
+
+
+
+
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class BsCustomDatesViewComponent {
+ @Input() isCustomRangeShown: true;
+ @Input() ranges: BsCustomDates[];
+}
diff --git a/src/datepicker/themes/bs/bs-datepicker-container.component.ts b/src/datepicker/themes/bs/bs-datepicker-container.component.ts
index ff792b1eae..a922cd2ccd 100644
--- a/src/datepicker/themes/bs/bs-datepicker-container.component.ts
+++ b/src/datepicker/themes/bs/bs-datepicker-container.component.ts
@@ -1,115 +1,70 @@
-import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
-import { BsDatepickerStore } from '../../reducer/bs-datepicker.store';
+import { Component, EventEmitter, OnDestroy, OnInit } from '@angular/core';
+import { BsDatepickerContainer } from '../../base/bs-datepicker-container';
+
+import { BsDatepickerConfig } from '../../bs-datepicker.config';
+import { DayViewModel } from '../../models/index';
import { BsDatepickerActions } from '../../reducer/bs-datepicker.actions';
-import {
- BsNavigationEvent, DatepickerRenderOptions, DayHoverEvent, DayViewModel,
- MonthViewModel
-} from '../../models/index';
-import 'rxjs/add/operator/filter';
+import { BsDatepickerEffects } from '../../reducer/bs-datepicker.effects';
+import { BsDatepickerStore } from '../../reducer/bs-datepicker.store';
+import { Subscription } from 'rxjs/Subscription';
@Component({
selector: 'bs-datepicker-container',
- providers: [BsDatepickerStore],
- template: `
-
- `,
+ providers: [BsDatepickerStore, BsDatepickerEffects],
+ templateUrl: './bs-datepicker-view.html',
host: {
'(click)': '_stopPropagation($event)',
style: 'position: absolute; display: block;'
}
})
-export class BsDatepickerContainerComponent {
- @Input()
+export class BsDatepickerContainerComponent
+ extends BsDatepickerContainer
+ implements OnInit, OnDestroy {
+
set value(value: Date) {
- this._bsDatepickerStore.dispatch(this._actions.select(value));
+ this._effects.setValue(value);
}
- @Output() valueChange = new EventEmitter();
-
- months: MonthViewModel[];
- options: DatepickerRenderOptions;
-
- constructor(private _bsDatepickerStore: BsDatepickerStore,
- private _actions: BsDatepickerActions) {
- // data binding state <--> model
- this._bsDatepickerStore.select(state => state.flaggedMonths)
- .filter(months => !!months)
- .subscribe(months => this.months = months);
-
- this._bsDatepickerStore.select(state => state.renderOptions)
- .filter(options => !!options)
- .subscribe(options => this.options = options);
-
- // set render options
- this._bsDatepickerStore.dispatch(this._actions.renderOptions({
- displayMonths: 1,
- showWeekNumbers: true
- }));
-
- // on selected date change
- this._bsDatepickerStore.select(state => state.selectedDate)
- .subscribe(date => this.valueChange.emit(date));
-
- // TODO: extract effects
- // calculate month model on view model change
- this._bsDatepickerStore
- .select(state => state.viewDate)
- .subscribe(viewDate =>
- this._bsDatepickerStore.dispatch(this._actions.calculate(viewDate)));
-
- // format calendar values on month model change
- this._bsDatepickerStore
- .select(state => state.monthsModel)
- .filter(monthModel => !!monthModel)
- .subscribe(month =>
- this._bsDatepickerStore.dispatch(this._actions.format()));
-
- // flag day values
- this._bsDatepickerStore
- .select(state => state.formattedMonths)
- .filter(month => !!month)
- .subscribe(month =>
- this._bsDatepickerStore.dispatch(this._actions.flag()));
-
- // flag day values
- this._bsDatepickerStore.select(state => state.selectedDate)
- .filter(selectedDate => !!selectedDate)
- .subscribe(selectedDate =>
- this._bsDatepickerStore.dispatch(this._actions.flag()));
+ valueChange: EventEmitter = new EventEmitter();
- // on hover
- this._bsDatepickerStore.select(state => state.hoveredDate)
- .filter(hoveredDate => !!hoveredDate)
- .subscribe(hoveredDate =>
- this._bsDatepickerStore.dispatch(this._actions.flag()));
+ _subs: Subscription[] = [];
+ constructor(private _config: BsDatepickerConfig,
+ private _store: BsDatepickerStore,
+ private _actions: BsDatepickerActions,
+ _effects: BsDatepickerEffects) {
+ super();
+ this._effects = _effects;
}
- navigateTo(event: BsNavigationEvent): void {
- this._bsDatepickerStore.dispatch(this._actions.navigateStep(event.step));
+ ngOnInit(): void {
+ this._effects
+ .init(this._store)
+ // intial state options
+ .setOptions(this._config)
+ // data binding view --> model
+ .setBindings(this)
+ // set event handlers
+ .setEventHandlers(this)
+ .registerDatepickerSideEffects();
+
+ // todo: move it somewhere else
+ // on selected date change
+ this._subs.push(this._store
+ .select(state => state.selectedDate)
+ .subscribe(date => this.valueChange.emit(date)));
}
- hoverHandler(event: DayHoverEvent): void {
- if (event.day.isOtherMonth) {
+ daySelectHandler(day: DayViewModel): void {
+ if (day.isOtherMonth || day.isDisabled) {
return;
}
- this._bsDatepickerStore.dispatch(this._actions.hover(event));
- event.day.isHovered = event.isHovered;
+ this._store.dispatch(this._actions.select(day.date));
}
- selectHandler(day: DayViewModel): void {
- if (day.isOtherMonth) {
- return;
+ ngOnDestroy(): void {
+ for (const sub of this._subs) {
+ sub.unsubscribe();
}
- this._bsDatepickerStore.dispatch(this._actions.select(day.date));
- }
-
- _stopPropagation(event: any): void {
- event.stopPropagation();
+ this._effects.destroy();
}
}
diff --git a/src/datepicker/themes/bs/bs-datepicker-day-decorator.directive.ts b/src/datepicker/themes/bs/bs-datepicker-day-decorator.directive.ts
index 9337eb765c..1cf475debb 100644
--- a/src/datepicker/themes/bs/bs-datepicker-day-decorator.directive.ts
+++ b/src/datepicker/themes/bs/bs-datepicker-day-decorator.directive.ts
@@ -1,5 +1,5 @@
-import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, Directive } from '@angular/core';
-import { DayHoverEvent, DayViewModel } from '../../models/index';
+import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
+import { DayViewModel } from '../../models/index';
@Component({
selector: '[bsDatepickerDayDecorator]',
diff --git a/src/datepicker/themes/bs/bs-datepicker-month-view.component.ts b/src/datepicker/themes/bs/bs-datepicker-month-view.component.ts
deleted file mode 100644
index 41550c1f9b..0000000000
--- a/src/datepicker/themes/bs/bs-datepicker-month-view.component.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
-import { DatepickerRenderOptions, DayHoverEvent, DayViewModel, MonthViewModel } from '../../models/index';
-
-@Component({
- selector: `bs-datepicker-month-view`,
- // FIX: day select and hover should mutate day or use separate component
- // changeDetection: ChangeDetectionStrategy.OnPush,
- template: `
-
-
-
- |
- {{ month.weekdays[i] }}
- |
-
-
-
-
- {{ month.weekNumbers[i] }}
- |
-
- {{ day.label }}
- |
-
-
-
- `
-})
-export class BsDatepickerMonthViewComponent {
- @Input() month: MonthViewModel;
- @Input() options: DatepickerRenderOptions;
-
- @Output() onSelect = new EventEmitter();
- @Output() onHover = new EventEmitter();
-
- selectDay(event: DayViewModel): void {
- this.onSelect.emit(event);
- }
-
- hoverDay(day: DayViewModel, isHovered: boolean): void {
- this.onHover.emit({day, isHovered});
- }
-}
-
diff --git a/src/datepicker/themes/bs/bs-datepicker-navigation-view.component.ts b/src/datepicker/themes/bs/bs-datepicker-navigation-view.component.ts
index efa5aa3441..ac9eaccbf4 100644
--- a/src/datepicker/themes/bs/bs-datepicker-navigation-view.component.ts
+++ b/src/datepicker/themes/bs/bs-datepicker-navigation-view.component.ts
@@ -1,30 +1,49 @@
-import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
-import { BsNavigationEvent, MonthViewModel } from '../../models/index';
-import { TimeUnit } from '../../../bs-moment/types';
+import {
+ ChangeDetectionStrategy, Component, EventEmitter, Input, Output
+} from '@angular/core';
+import {
+ BsDatepickerViewMode, BsNavigationDirection, DaysCalendarViewModel
+} from '../../models/index';
@Component({
selector: 'bs-datepicker-navigation-view',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
-
-