diff --git a/.angular-cli.json b/.angular-cli.json
index b52807775a..7f646ebc57 100644
--- a/.angular-cli.json
+++ b/.angular-cli.json
@@ -18,6 +18,7 @@
"prefix": "",
"mobile": false,
"styles": [
+ "../../src/datepicker/bs-datepicker.css"
],
"scripts": [],
"environmentSource": "environments/environment.ts",
diff --git a/.travis.yml b/.travis.yml
index c5d6dc4072..b532e7da1e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,6 @@
sudo: false
language: node_js
-node_js: "7"
+node_js: "8"
dist: precise
env:
@@ -19,7 +19,9 @@ script:
- rm -rf node_modules/ngx-bootstrap
- npm i ./dist
- npm run demo.build
- - npm run test-coverage
+ # istanbul is broken, should be fixed
+ #- npm run test-coverage
+ - ./node_modules/.bin/ng test -sr
after_success:
- ./node_modules/.bin/codecov
diff --git a/demo/src/app/app.module.ts b/demo/src/app/app.module.ts
index 80dc539422..143ef6255f 100644
--- a/demo/src/app/app.module.ts
+++ b/demo/src/app/app.module.ts
@@ -10,24 +10,7 @@ import { MainMenuComponent } from './common/main-menu/main-menu.component';
import { TopMenuComponent } from './common/top-menu/top-menu.component';
import { SearchFilterPipe } from './common/main-menu/search-filter.pipe';
import { AppFooterComponent } from './common/app-footer/app-footer.component';
-// will be lazy loaded later
-// import { DemoAccordionModule } from './components/+accordion';
-// import { DemoAlertsModule } from './components/+alerts';
-// import { DemoButtonsModule } from './components/+buttons';
-// import { DemoCarouselModule } from './components/+carousel';
-// import { DemoCollapseModule } from './components/+collapse';
-// import { DemoDatepickerModule } from './components/+datepicker';
-// import { DemoDropdownModule } from './components/+dropdown';
-// import { DemoModalModule } from './components/+modal';
-// import { DemoPaginationModule } from './components/+pagination';
-// import { DemoPopoverModule } from './components/+popover/index';
-// import { DemoProgressbarModule } from './components/+progressbar';
-// import { DemoRatingModule } from './components/+rating';
-// import { DemoSortableModule } from './components/+sortable';
-// import { DemoTabsModule } from './components/+tabs';
-// import { DemoTimepickerModule } from './components/+timepicker/index';
-// import { DemoTooltipModule } from './components/+tooltip/index';
-// import { DemoTypeaheadModule } from './components/+typeahead/index';
+
import { NgApiDocModule } from './api-docs/index';
import { NgApiDoc } from './api-docs/api-docs.model';
import { ngdoc } from '../ng-api-doc';
@@ -46,25 +29,7 @@ import { ngdoc } from '../ng-api-doc';
BrowserModule,
FormsModule,
RouterModule.forRoot(routes, {useHash: true}),
- Ng2PageScrollModule.forRoot(),
- // will be lazy loaded later on
- // DemoAccordionModule,
- // DemoAlertsModule,
- // DemoButtonsModule,
- // DemoCarouselModule,
- // DemoCollapseModule,
- // DemoDatepickerModule,
- // DemoDropdownModule,
- // DemoModalModule,
- // DemoPaginationModule,
- // DemoPopoverModule,
- // DemoProgressbarModule,
- // DemoRatingModule,
- // DemoSortableModule,
- // DemoTabsModule,
- // DemoTimepickerModule,
- // DemoTooltipModule,
- // DemoTypeaheadModule
+ Ng2PageScrollModule.forRoot()
],
providers: [
{provide: NgApiDoc, useValue: ngdoc}
diff --git a/demo/src/app/app.routing.ts b/demo/src/app/app.routing.ts
index 1ac627dca5..8d5a73525c 100644
--- a/demo/src/app/app.routing.ts
+++ b/demo/src/app/app.routing.ts
@@ -1,21 +1,4 @@
import { GettingStartedComponent } from './getting-started/getting-started.component';
-// import { AccordionSectionComponent } from './components/accordion/accordion-section.component';
-// import { AlertsSectionComponent } from './components/+alerts/alerts-section.component';
-// import { ButtonsSectionComponent } from './components/+buttons/buttons-section.component';
-// import { CarouselSectionComponent } from './components/+carousel/carousel-section.component';
-// import { CollapseSectionComponent } from './components/+collapse/collapse-section.component';
-// import { DatepickerSectionComponent } from './components/+datepicker/datepicker-section.component';
-// import { DropdownSectionComponent } from './components/+dropdown/dropdown-section.component';
-// import { ModalSectionComponent } from './components/+modal/modal-section.component';
-// import { ProgressbarSectionComponent } from './components/+progressbar/progressbar-section.component';
-// import { PaginationSectionComponent } from './components/+pagination/pagination-section.component';
-// import { RatingSectionComponent } from './components/+rating/rating-section.component';
-// import { SortableSectionComponent } from './components/+sortable/sortable-section.component';
-// import { TabsSectionComponent } from './components/+tabs/tabs-section.component';
-// import { TimepickerSectionComponent } from './components/+timepicker/timepicker-section.component';
-// import { TooltipSectionComponent } from './components/+tooltip/tooltip-section.component';
-// import { TypeaheadSectionComponent } from './components/+typeahead/typeahead-section.component';
-// import { PopoverSectionComponent } from './components/+popover/popover-section.component';
export const routes = [
{
diff --git a/demo/src/app/components/+datepicker/datepicker-section.component.ts b/demo/src/app/components/+datepicker/datepicker-section.component.ts
index d1cd7bb491..0aa4375274 100644
--- a/demo/src/app/components/+datepicker/datepicker-section.component.ts
+++ b/demo/src/app/components/+datepicker/datepicker-section.component.ts
@@ -30,7 +30,11 @@ let titleDoc = require('html-loader!markdown-loader!./docs/title.md');
Examples
-
+
+
+
+
+
diff --git a/demo/src/app/components/+datepicker/demo-datepicker.module.ts b/demo/src/app/components/+datepicker/demo-datepicker.module.ts
index 3be1a70b95..f0d5d18b84 100644
--- a/demo/src/app/components/+datepicker/demo-datepicker.module.ts
+++ b/demo/src/app/components/+datepicker/demo-datepicker.module.ts
@@ -2,20 +2,23 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
-import { DatepickerModule } from 'ngx-bootstrap/datepicker';
+import { DatepickerModule, BsDatepickerModule } from 'ngx-bootstrap/datepicker';
import { SharedModule } from '../../shared';
import { DatepickerSectionComponent } from './datepicker-section.component';
import { DEMO_COMPONENTS } from './demos';
import { routes } from './demo-datepicker.routes';
+import { DemoDatePickerPopupComponent } from './demos/bs-popup/date-picker-popup';
@NgModule({
declarations:[
+ DemoDatePickerPopupComponent,
DatepickerSectionComponent,
...DEMO_COMPONENTS
],
imports:[
DatepickerModule.forRoot(),
+ BsDatepickerModule.forRoot(),
CommonModule,
FormsModule,
SharedModule,
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
new file mode 100644
index 0000000000..c66c0c2f4f
--- /dev/null
+++ b/demo/src/app/components/+datepicker/demos/bs-popup/date-picker-popup.html
@@ -0,0 +1,9 @@
+{{bsValue}}
+
+
+
+
+
+{{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
new file mode 100644
index 0000000000..0066e72377
--- /dev/null
+++ b/demo/src/app/components/+datepicker/demos/bs-popup/date-picker-popup.ts
@@ -0,0 +1,10 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'demo-date-picker-popup',
+ templateUrl: './date-picker-popup.html'
+})
+export class DemoDatePickerPopupComponent {
+ public bsValue: any = new Date(2017, 7, 4);
+ public bsRangeValue: any = [new Date(2017, 7, 4), new Date(2017, 7, 20)];
+}
diff --git a/demo/src/app/components/+datepicker/demos/datepicker-demo.component.ts b/demo/src/app/components/+datepicker/demos/datepicker-demo.component.ts
index 2f37a57bab..b1c6581b81 100644
--- a/demo/src/app/components/+datepicker/demos/datepicker-demo.component.ts
+++ b/demo/src/app/components/+datepicker/demos/datepicker-demo.component.ts
@@ -1,5 +1,4 @@
import { Component } from '@angular/core';
-import * as moment from 'moment';
@Component({
selector: 'datepicker-demo',
@@ -41,8 +40,7 @@ export class DatepickerDemoComponent {
}
public d20090824(): void {
- this.dt = moment('2009-08-24', 'YYYY-MM-DD')
- .toDate();
+ this.dt = new Date(2009,7,24);
}
public disableTomorrow(): void {
diff --git a/demo/src/app/components/+datepicker/demos/index.ts b/demo/src/app/components/+datepicker/demos/index.ts
index 6ba54cda43..17a76d259c 100644
--- a/demo/src/app/components/+datepicker/demos/index.ts
+++ b/demo/src/app/components/+datepicker/demos/index.ts
@@ -8,5 +8,9 @@ export const DEMOS = {
old: {
component: require('!!raw-loader?lang=typescript!./datepicker-demo.component.ts'),
html: require('!!raw-loader?lang=markup!./datepicker-demo.component.html')
+ },
+ pop: {
+ component: require('!!raw-loader?lang=typescript!./bs-popup/date-picker-popup.ts'),
+ html: require('!!raw-loader?lang=markup!./bs-popup/date-picker-popup.html')
}
};
diff --git a/demo/src/ng-api-doc.ts b/demo/src/ng-api-doc.ts
index 1b1871a684..a52f0552d7 100644
--- a/demo/src/ng-api-doc.ts
+++ b/demo/src/ng-api-doc.ts
@@ -131,6 +131,20 @@ export const ngdoc: any = {
}
]
},
+ "LocaleOptionsFormat": {
+ "fileName": "src/bs-moment/locale/locale.class.ts",
+ "className": "LocaleOptionsFormat",
+ "description": "",
+ "methods": [],
+ "properties": []
+ },
+ "LocaleData": {
+ "fileName": "src/datepicker/models/index.ts",
+ "className": "LocaleData",
+ "description": "",
+ "methods": [],
+ "properties": []
+ },
"ButtonCheckboxDirective": {
"fileName": "src/buttons/button-checkbox.directive.ts",
"className": "ButtonCheckboxDirective",
@@ -290,13 +304,13 @@ export const ngdoc: any = {
},
{
"name": "getCurrentSlideIndex",
- "description": "Finds and returns index of currently displayed slide\n@returns {number}
\n",
+ "description": "Finds and returns index of currently displayed slide
\n",
"args": [],
"returnType": "number"
},
{
"name": "isLast",
- "description": "Defines, whether the specified index is last in collection\n@returns {boolean}
\n",
+ "description": "Defines, whether the specified index is last in collection
\n",
"args": [
{
"name": "index",
@@ -304,6 +318,44 @@ export const ngdoc: any = {
}
],
"returnType": "boolean"
+ },
+ {
+ "name": "findNextSlideIndex",
+ "description": "Defines next slide index, depending of direction
\n",
+ "args": [
+ {
+ "name": "direction",
+ "type": "Direction"
+ },
+ {
+ "name": "force",
+ "type": "boolean"
+ }
+ ],
+ "returnType": "number"
+ },
+ {
+ "name": "_select",
+ "description": "Sets a slide, which specified through index, as active
\n",
+ "args": [
+ {
+ "name": "index",
+ "type": "number"
+ }
+ ],
+ "returnType": "void"
+ },
+ {
+ "name": "restartTimer",
+ "description": "Starts loop of auto changing of slides
\n",
+ "args": [],
+ "returnType": "any"
+ },
+ {
+ "name": "resetTimer",
+ "description": "Stops loop of auto changing of slides
\n",
+ "args": [],
+ "returnType": "void"
}
]
},
@@ -423,28 +475,158 @@ export const ngdoc: any = {
"fileName": "src/component-loader/component-loader.factory.ts",
"className": "ComponentLoaderFactory",
"description": "",
+ "methods": [],
+ "properties": []
+ },
+ "BsDatepickerConfig": {
+ "fileName": "src/datepicker/bs-datepicker-config.ts",
+ "className": "BsDatepickerConfig",
+ "description": "",
+ "methods": [],
+ "properties": []
+ },
+ "BsDatepickerComponent": {
+ "fileName": "src/datepicker/bs-datepicker.component.ts",
+ "className": "BsDatepickerComponent",
+ "description": "",
+ "selector": "bs-datepicker",
+ "exportAs": "bsDatepicker",
+ "inputs": [
+ {
+ "name": "container",
+ "defaultValue": "body",
+ "type": "string",
+ "description": "A selector specifying the element the popover should be appended to.\nCurrently only supports "body".
\n"
+ },
+ {
+ "name": "isOpen",
+ "type": "boolean",
+ "description": "Returns whether or not the popover is currently being shown
\n"
+ },
+ {
+ "name": "placement",
+ "defaultValue": "bottom",
+ "type": "\"top\" | \"bottom\" | \"left\" | \"right\"",
+ "description": "Placement of a popover. Accepts: "top", "bottom", "left", "right"
\n"
+ },
+ {
+ "name": "triggers",
+ "defaultValue": "click",
+ "type": "string",
+ "description": "Specifies events that should trigger. Supports a space separated list of\nevent names.
\n"
+ },
+ {
+ "name": "value",
+ "type": "Date",
+ "description": ""
+ }
+ ],
+ "outputs": [
+ {
+ "name": "onHidden",
+ "description": "Emits an event when the popover is hidden
\n"
+ },
+ {
+ "name": "onShown",
+ "description": "Emits an event when the popover is shown
\n"
+ },
+ {
+ "name": "valueChange",
+ "description": ""
+ }
+ ],
+ "properties": [],
"methods": [
{
- "name": "createLoader",
- "description": "@returns {ComponentLoader}
\n",
- "args": [
- {
- "name": "_elementRef",
- "type": "ElementRef"
- },
- {
- "name": "_viewContainerRef",
- "type": "ViewContainerRef"
- },
- {
- "name": "_renderer",
- "type": "Renderer"
- }
- ],
- "returnType": "ComponentLoader"
+ "name": "show",
+ "description": "Opens an element’s datepicker. This is considered a “manual” triggering of\nthe datepicker.
\n",
+ "args": [],
+ "returnType": "void"
+ },
+ {
+ "name": "hide",
+ "description": "Closes an element’s datepicker. This is considered a “manual” triggering of\nthe datepicker.
\n",
+ "args": [],
+ "returnType": "void"
+ },
+ {
+ "name": "toggle",
+ "description": "Toggles an element’s datepicker. This is considered a “manual” triggering of\nthe datepicker.
\n",
+ "args": [],
+ "returnType": "void"
+ }
+ ]
+ },
+ "BsDaterangepickerComponent": {
+ "fileName": "src/datepicker/bs-daterangepicker.component.ts",
+ "className": "BsDaterangepickerComponent",
+ "description": "",
+ "selector": "bs-daterangepicker",
+ "inputs": [
+ {
+ "name": "container",
+ "defaultValue": "body",
+ "type": "string",
+ "description": "A selector specifying the element the popover should be appended to.\nCurrently only supports "body".
\n"
+ },
+ {
+ "name": "isOpen",
+ "type": "boolean",
+ "description": "Returns whether or not the popover is currently being shown
\n"
+ },
+ {
+ "name": "placement",
+ "defaultValue": "bottom",
+ "type": "\"top\" | \"bottom\" | \"left\" | \"right\"",
+ "description": "Placement of a popover. Accepts: "top", "bottom", "left", "right"
\n"
+ },
+ {
+ "name": "triggers",
+ "defaultValue": "click",
+ "type": "string",
+ "description": "Specifies events that should trigger. Supports a space separated list of\nevent names.
\n"
+ },
+ {
+ "name": "value",
+ "type": "Date[]",
+ "description": ""
}
],
- "properties": []
+ "outputs": [
+ {
+ "name": "onHidden",
+ "description": "Emits an event when the popover is hidden
\n"
+ },
+ {
+ "name": "onShown",
+ "description": "Emits an event when the popover is shown
\n"
+ },
+ {
+ "name": "valueChange",
+ "description": ""
+ }
+ ],
+ "properties": [],
+ "methods": [
+ {
+ "name": "show",
+ "description": "Opens an element’s datepicker. This is considered a “manual” triggering of\nthe datepicker.
\n",
+ "args": [],
+ "returnType": "void"
+ },
+ {
+ "name": "hide",
+ "description": "Closes an element’s datepicker. This is considered a “manual” triggering of\nthe datepicker.
\n",
+ "args": [],
+ "returnType": "void"
+ },
+ {
+ "name": "toggle",
+ "description": "Toggles an element’s datepicker. This is considered a “manual” triggering of\nthe datepicker.
\n",
+ "args": [],
+ "returnType": "void"
+ }
+ ]
},
"DatePickerInnerComponent": {
"fileName": "src/datepicker/datepicker-inner.component.ts",
@@ -729,6 +911,83 @@ export const ngdoc: any = {
"properties": [],
"methods": []
},
+ "FlagMonthViewOptions": {
+ "fileName": "src/datepicker/engine/flag-month-view.ts",
+ "className": "FlagMonthViewOptions",
+ "description": "",
+ "methods": [],
+ "properties": []
+ },
+ "DaysCalendarModel": {
+ "fileName": "src/datepicker/models/index.ts",
+ "className": "DaysCalendarModel",
+ "description": "",
+ "methods": [],
+ "properties": []
+ },
+ "DayViewModel": {
+ "fileName": "src/datepicker/models/index.ts",
+ "className": "DayViewModel",
+ "description": "",
+ "methods": [],
+ "properties": []
+ },
+ "WeekViewModel": {
+ "fileName": "src/datepicker/models/index.ts",
+ "className": "WeekViewModel",
+ "description": "",
+ "methods": [],
+ "properties": []
+ },
+ "MonthViewModel": {
+ "fileName": "src/datepicker/models/index.ts",
+ "className": "MonthViewModel",
+ "description": "",
+ "methods": [],
+ "properties": []
+ },
+ "MonthViewOptions": {
+ "fileName": "src/datepicker/models/index.ts",
+ "className": "MonthViewOptions",
+ "description": "",
+ "methods": [],
+ "properties": []
+ },
+ "DatepickerFormatOptions": {
+ "fileName": "src/datepicker/models/index.ts",
+ "className": "DatepickerFormatOptions",
+ "description": "",
+ "methods": [],
+ "properties": []
+ },
+ "DatepickerRenderOptions": {
+ "fileName": "src/datepicker/models/index.ts",
+ "className": "DatepickerRenderOptions",
+ "description": "",
+ "methods": [],
+ "properties": []
+ },
+ "TimeUnit": {
+ "fileName": "src/datepicker/models/index.ts",
+ "className": "TimeUnit",
+ "description": "",
+ "methods": [],
+ "properties": []
+ },
+ "BsNavigationEvent": {
+ "fileName": "src/datepicker/models/index.ts",
+ "className": "BsNavigationEvent",
+ "description": "",
+ "methods": [],
+ "properties": []
+ },
+ "DayHoverEvent": {
+ "fileName": "src/datepicker/models/index.ts",
+ "className": "DayHoverEvent",
+ "description": "",
+ "methods": [],
+ "properties": []
+ },
"MonthPickerComponent": {
"fileName": "src/datepicker/monthpicker.component.ts",
"className": "MonthPickerComponent",
@@ -739,6 +998,179 @@ export const ngdoc: any = {
"properties": [],
"methods": []
},
+ "BsDatepickerActions": {
+ "fileName": "src/datepicker/reducer/bs-datepicker.actions.ts",
+ "className": "BsDatepickerActions",
+ "description": "",
+ "methods": [],
+ "properties": []
+ },
+ "BsDatepickerEffects": {
+ "fileName": "src/datepicker/reducer/bs-datepicker.effects.ts",
+ "className": "BsDatepickerEffects",
+ "description": "",
+ "methods": [],
+ "properties": []
+ },
+ "BsDatepickerStore": {
+ "fileName": "src/datepicker/reducer/bs-datepicker.store.ts",
+ "className": "BsDatepickerStore",
+ "description": "",
+ "methods": [],
+ "properties": []
+ },
+ "BsDatepickerContainerComponent": {
+ "fileName": "src/datepicker/themes/bs/bs-datepicker-container.component.ts",
+ "className": "BsDatepickerContainerComponent",
+ "description": "",
+ "selector": "bs-datepicker-container",
+ "inputs": [
+ {
+ "name": "value",
+ "type": "Date",
+ "description": ""
+ }
+ ],
+ "outputs": [
+ {
+ "name": "valueChange",
+ "description": ""
+ }
+ ],
+ "properties": [],
+ "methods": []
+ },
+ "BsDatepickerDayViewComponent": {
+ "fileName": "src/datepicker/themes/bs/bs-datepicker-day-view.component.ts",
+ "className": "BsDatepickerDayViewComponent",
+ "description": "",
+ "selector": "bs-datepicker-day-view",
+ "inputs": [
+ {
+ "name": "day",
+ "type": "DayViewModel",
+ "description": ""
+ }
+ ],
+ "outputs": [
+ {
+ "name": "onHover",
+ "description": ""
+ },
+ {
+ "name": "onSelect",
+ "description": ""
+ }
+ ],
+ "properties": [],
+ "methods": []
+ },
+ "BsDatepickerMonthViewComponent": {
+ "fileName": "src/datepicker/themes/bs/bs-datepicker-month-view.component.ts",
+ "className": "BsDatepickerMonthViewComponent",
+ "description": "",
+ "selector": "bs-datepicker-month-view",
+ "inputs": [
+ {
+ "name": "month",
+ "type": "MonthViewModel",
+ "description": ""
+ },
+ {
+ "name": "options",
+ "type": "DatepickerRenderOptions",
+ "description": ""
+ }
+ ],
+ "outputs": [
+ {
+ "name": "onHover",
+ "description": ""
+ },
+ {
+ "name": "onSelect",
+ "description": ""
+ }
+ ],
+ "properties": [],
+ "methods": []
+ },
+ "BsDatepickerNavigationViewComponent": {
+ "fileName": "src/datepicker/themes/bs/bs-datepicker-navigation-view.component.ts",
+ "className": "BsDatepickerNavigationViewComponent",
+ "description": "",
+ "selector": "bs-datepicker-navigation-view",
+ "inputs": [
+ {
+ "name": "month",
+ "type": "MonthViewModel",
+ "description": ""
+ }
+ ],
+ "outputs": [
+ {
+ "name": "onNavigate",
+ "description": ""
+ }
+ ],
+ "properties": [],
+ "methods": []
+ },
+ "BsDatepickerViewComponent": {
+ "fileName": "src/datepicker/themes/bs/bs-datepicker-view.component.ts",
+ "className": "BsDatepickerViewComponent",
+ "description": "",
+ "selector": "bs-datepicker-view",
+ "inputs": [
+ {
+ "name": "months",
+ "type": "MonthViewModel[]",
+ "description": ""
+ },
+ {
+ "name": "options",
+ "type": "DatepickerRenderOptions",
+ "description": ""
+ }
+ ],
+ "outputs": [
+ {
+ "name": "onHover",
+ "description": ""
+ },
+ {
+ "name": "onNavigate",
+ "description": ""
+ },
+ {
+ "name": "onSelect",
+ "description": ""
+ }
+ ],
+ "properties": [],
+ "methods": []
+ },
+ "BsDaterangepickerContainerComponent": {
+ "fileName": "src/datepicker/themes/bs/bs-daterangepicker-container.component.ts",
+ "className": "BsDaterangepickerContainerComponent",
+ "description": "",
+ "selector": "bs-daterangepicker-container",
+ "inputs": [
+ {
+ "name": "value",
+ "type": "Date[]",
+ "description": ""
+ }
+ ],
+ "outputs": [
+ {
+ "name": "valueChange",
+ "description": ""
+ }
+ ],
+ "properties": [],
+ "methods": []
+ },
"YearPickerComponent": {
"fileName": "src/datepicker/yearpicker.component.ts",
"className": "YearPickerComponent",
@@ -898,7 +1330,7 @@ export const ngdoc: any = {
"properties": [
{
"name": "dropdownMenu",
- "type": "any",
+ "type": "Promise>",
"description": "Content to be displayed as popover.
\n"
}
]
@@ -929,6 +1361,12 @@ export const ngdoc: any = {
}
],
"returnType": "BsModalRef"
+ },
+ {
+ "name": "checkScrollbar",
+ "description": "AFTER PR MERGE MODAL.COMPONENT WILL BE USING THIS CODE\nScroll bar tricks
\n",
+ "args": [],
+ "returnType": "void"
}
],
"properties": []
@@ -1078,11 +1516,23 @@ export const ngdoc: any = {
],
"returnType": "void"
},
+ {
+ "name": "showElement",
+ "description": "Show dialog
\n",
+ "args": [],
+ "returnType": "void"
+ },
{
"name": "focusOtherModal",
"description": "Events tricks
\n",
"args": [],
"returnType": "void"
+ },
+ {
+ "name": "checkScrollbar",
+ "description": "Scroll bar tricks
\n",
+ "args": [],
+ "returnType": "void"
}
]
},
@@ -1391,13 +1841,6 @@ export const ngdoc: any = {
}
]
},
- "Positioning": {
- "fileName": "src/positioning/ng-positioning.ts",
- "className": "Positioning",
- "description": "@copyright Valor Software\n@copyright Angular ng-bootstrap team
\n",
- "methods": [],
- "properties": []
- },
"PositioningOptions": {
"fileName": "src/positioning/positioning.service.ts",
"className": "PositioningOptions",
@@ -1416,7 +1859,7 @@ export const ngdoc: any = {
},
{
"name": "element",
- "type": "string | ElementRef | HTMLElement",
+ "type": "string | HTMLElement | ElementRef",
"description": "The DOM element, ElementRef, or a selector string of an element which will be moved
\n"
},
{
@@ -1426,7 +1869,7 @@ export const ngdoc: any = {
},
{
"name": "target",
- "type": "string | ElementRef | HTMLElement",
+ "type": "string | HTMLElement | ElementRef",
"description": "The DOM element, ElementRef, or a selector string of an element which the element will be attached to
\n"
},
{
@@ -1672,7 +2115,7 @@ export const ngdoc: any = {
"outputs": [
{
"name": "onChange",
- "description": "fired on array change (reordering, insert, remove), same as ngModelChange
.\n Returns new items collection as a payload.
\n"
+ "description": "fired on array change (reordering, insert, remove), same as ngModelChange
.\nReturns new items collection as a payload.
\n"
}
],
"properties": [],
@@ -2076,59 +2519,59 @@ export const ngdoc: any = {
"name": "tooltipAnimation",
"defaultValue": "true",
"type": "boolean",
- "description": "@deprecated - removed, will be added to configuration
\n"
+ "description": ""
},
{
"name": "tooltipAppendToBody",
"type": "boolean",
- "description": "@deprecated - please use container="body"
instead
\n"
+ "description": ""
},
{
"name": "tooltipClass",
"type": "string",
- "description": "@deprecated - will replaced with customClass
\n"
+ "description": ""
},
{
"name": "tooltipContext",
"type": "any",
- "description": "@deprecated - removed
\n"
+ "description": ""
},
{
"name": "tooltipEnable",
"type": "boolean",
- "description": "@deprecated - please use isDisabled
instead
\n"
+ "description": ""
},
{
"name": "tooltipFadeDuration",
"defaultValue": "150",
"type": "number",
- "description": "@deprecated
\n"
+ "description": ""
},
{
"name": "tooltipHtml",
"type": "string | TemplateRef",
- "description": "@deprecated - please use tooltip
instead
\n"
+ "description": ""
},
{
"name": "tooltipIsOpen",
"type": "boolean",
- "description": "@deprecated - please use isOpen
instead
\n"
+ "description": ""
},
{
"name": "tooltipPlacement",
"type": "string",
- "description": "@deprecated - please use placement
instead
\n"
+ "description": ""
},
{
"name": "tooltipPopupDelay",
"defaultValue": "0",
"type": "number",
- "description": "@deprecated
\n"
+ "description": ""
},
{
"name": "tooltipTrigger",
"type": "string | string[]",
- "description": "@deprecated - please use triggers
instead
\n"
+ "description": ""
},
{
"name": "triggers",
@@ -2151,26 +2594,10 @@ export const ngdoc: any = {
},
{
"name": "tooltipStateChanged",
- "description": "@deprecated
\n"
- }
- ],
- "properties": [
- {
- "name": "_appendToBody",
- "type": "boolean",
- "description": "@deprecated - please use container="body"
instead
\n"
- },
- {
- "name": "_enable",
- "type": "boolean",
- "description": "@deprecated - please use isDisabled
instead
\n"
- },
- {
- "name": "_isOpen",
- "type": "boolean",
- "description": "@deprecated - please use isOpen
instead
\n"
+ "description": ""
}
],
+ "properties": [],
"methods": [
{
"name": "toggle",
@@ -2310,12 +2737,5 @@ export const ngdoc: any = {
}
],
"methods": []
- },
- "Trigger": {
- "fileName": "src/utils/trigger.class.ts",
- "className": "Trigger",
- "description": "@copyright Valor Software\n@copyright Angular ng-bootstrap team
\n",
- "methods": [],
- "properties": []
}
};
diff --git a/package.json b/package.json
index 6dafc89757..ecde384ab1 100644
--- a/package.json
+++ b/package.json
@@ -65,7 +65,7 @@
"@angular/forms": "^2.3.1 || >=4.0.0"
},
"devDependencies": {
- "@angular/cli": "^1.0.6",
+ "@angular/cli": "1.3.0",
"@angular/common": "^2.4.4",
"@angular/compiler": "^2.4.4",
"@angular/compiler-cli": "^2.4.4",
@@ -113,17 +113,17 @@
"markdown-loader": "github:valorkin/markdown-loader",
"marked": "0.3.6",
"ng2-page-scroll": "4.0.0-beta.7",
- "ngm-cli": "0.5.2",
+ "ngm-cli": "0.6.1",
"npm-run-all": "4.0.2",
"protractor": "5.1.1",
"reflect-metadata": "0.1.10",
"require-dir": "0.3.1",
- "rxjs": "5.3.0",
+ "rxjs": "5.4.3",
"ts-helpers": "^1.1.1",
"tslint": "4.5.1",
"tslint-config-valorsoft": "1.2.0",
"typedoc": "0.5.9",
- "typescript": "2.0.10",
+ "typescript": "2.4.2",
"wallaby-webpack": "0.0.37",
"webdriver-manager": "12.0.4",
"webpack-bundle-analyzer": "2.8.2",
diff --git a/src/bs-moment/LICENSE b/src/bs-moment/LICENSE
new file mode 100644
index 0000000000..86b546c2ff
--- /dev/null
+++ b/src/bs-moment/LICENSE
@@ -0,0 +1,26 @@
+The MIT License (MIT)
+
+Copyright (c) Valor Software
+Copyright (c) Dmitriy Shekhovtsov
+Copyright (c) JS Foundation and other contributors
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/src/bs-moment/format-functions.ts b/src/bs-moment/format-functions.ts
new file mode 100644
index 0000000000..697b33e1dc
--- /dev/null
+++ b/src/bs-moment/format-functions.ts
@@ -0,0 +1,63 @@
+import { Locale } from './locale/locale.class';
+import { DateFormatterFn } from '../datepicker/models/index';
+import { zeroFill } from './utils';
+import { isFunction } from './utils/type-checks';
+
+export let formatFunctions: {[key:string]: (date: Date, locale: Locale) => string} = {};
+export let formatTokenFunctions: { [key: string]: DateFormatterFn } = {};
+
+export const formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g;
+
+// token: 'M'
+// padded: ['MM', 2]
+// ordinal: 'Mo'
+// callback: function () { this.month() + 1 }
+export function addFormatToken(token: string,
+ padded: {[key: number]: any},
+ ordinal: string,
+ callback: DateFormatterFn): void {
+ let func: DateFormatterFn = callback;
+ if (token) {
+ formatTokenFunctions[token] = func;
+ }
+ if (padded as {[key: number]: any}) {
+ let key = padded[0] as string;
+ formatTokenFunctions[key] = function (date: Date, format: string, locale?: Locale): string {
+ return zeroFill(func.apply(null, arguments), padded[1] as number, padded[2] as boolean);
+ };
+ }
+ if (ordinal) {
+ formatTokenFunctions[ordinal] = function (date: Date, format: string, locale: Locale): string {
+ // todo: fix this
+ return locale.ordinal(func.apply(null, arguments), token);
+ };
+ }
+}
+
+export function makeFormatFunction(format: string): (date: Date, locale: Locale) => string {
+ const array: string[] = format.match(formattingTokens);
+ const length = array.length;
+ const formatArr: (string[] | DateFormatterFn[]) = new Array(length);
+ for (let i = 0; i < length; i++) {
+ formatArr[i] = formatTokenFunctions[array[i]]
+ ? formatTokenFunctions[array[i]]
+ : removeFormattingTokens(array[i]);
+ }
+
+ return function (date: Date, locale: Locale): string {
+ let output = '';
+ for (let j = 0; j < length; j++) {
+ output += isFunction(formatArr[j] as DateFormatterFn)
+ ? (formatArr[j] as DateFormatterFn).call(null, date, format, locale)
+ : formatArr[j];
+ }
+ return output;
+ };
+}
+
+function removeFormattingTokens(input: string): string {
+ if (input.match(/\[[\s\S]/)) {
+ return input.replace(/^\[|\]$/g, '');
+ }
+ return input.replace(/\\/g, '');
+}
diff --git a/src/bs-moment/format.ts b/src/bs-moment/format.ts
new file mode 100644
index 0000000000..a39b17ad6a
--- /dev/null
+++ b/src/bs-moment/format.ts
@@ -0,0 +1,28 @@
+// moment.js
+// version : 2.18.1
+// authors : Tim Wood, Iskren Chernev, Moment.js contributors
+// license : MIT
+// momentjs.com
+
+import { formatFunctions, makeFormatFunction } from './format-functions';
+import './locale';
+import { Locale } from './locale/locale.class';
+import { getLocale } from './locale/locales.service';
+import './units';
+import { isDateValid } from './utils/type-checks';
+
+export function formatDate(date: Date, format: string, locale = 'en'): string {
+ const _locale = getLocale(locale);
+ const output = formatMoment(date, format, _locale);
+ return _locale.postformat(output);
+}
+
+// format date using native date object
+export function formatMoment(date: Date, format: string, locale: Locale) {
+ if (!isDateValid(date)) {
+ return locale.invalidDate;
+ }
+
+ formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format);
+ return formatFunctions[format](date, locale);
+}
diff --git a/src/bs-moment/i18n/ar.ts b/src/bs-moment/i18n/ar.ts
new file mode 100644
index 0000000000..40a92af378
--- /dev/null
+++ b/src/bs-moment/i18n/ar.ts
@@ -0,0 +1,131 @@
+// moment.js locale configuration
+// locale : Arabic [ar]
+// author : Abdel Said: https://github.com/abdelsaid
+// author : Ahmed Elkhatib
+// author : forabi https://github.com/forabi
+
+const symbolMap: { [key: string]: string } = {
+ '1': '١',
+ '2': '٢',
+ '3': '٣',
+ '4': '٤',
+ '5': '٥',
+ '6': '٦',
+ '7': '٧',
+ '8': '٨',
+ '9': '٩',
+ '0': '٠'
+};
+const numberMap: { [key: string]: string } = {
+ '١': '1',
+ '٢': '2',
+ '٣': '3',
+ '٤': '4',
+ '٥': '5',
+ '٦': '6',
+ '٧': '7',
+ '٨': '8',
+ '٩': '9',
+ '٠': '0'
+};
+const pluralForm = function (n: number): number {
+ return n === 0 ? 0 : n === 1 ? 1 : n === 2 ? 2 : n % 100 >= 3 && n % 100 <= 10 ? 3 : n % 100 >= 11 ? 4 : 5;
+};
+const plurals = {
+ s: ['أقل من ثانية', 'ثانية واحدة', ['ثانيتان', 'ثانيتين'], '%d ثوان', '%d ثانية', '%d ثانية'],
+ m: ['أقل من دقيقة', 'دقيقة واحدة', ['دقيقتان', 'دقيقتين'], '%d دقائق', '%d دقيقة', '%d دقيقة'],
+ h: ['أقل من ساعة', 'ساعة واحدة', ['ساعتان', 'ساعتين'], '%d ساعات', '%d ساعة', '%d ساعة'],
+ d: ['أقل من يوم', 'يوم واحد', ['يومان', 'يومين'], '%d أيام', '%d يومًا', '%d يوم'],
+ M: ['أقل من شهر', 'شهر واحد', ['شهران', 'شهرين'], '%d أشهر', '%d شهرا', '%d شهر'],
+ y: ['أقل من عام', 'عام واحد', ['عامان', 'عامين'], '%d أعوام', '%d عامًا', '%d عام']
+};
+const pluralize = function (u) {
+ return function (number, withoutSuffix, string, isFuture) {
+ const f = pluralForm(number);
+ let str = plurals[u][pluralForm(number)];
+ if (f === 2) {
+ str = str[withoutSuffix ? 0 : 1];
+ }
+ return str.replace(/%d/i, number);
+ };
+};
+const months = [
+ 'كانون الثاني يناير',
+ 'شباط فبراير',
+ 'آذار مارس',
+ 'نيسان أبريل',
+ 'أيار مايو',
+ 'حزيران يونيو',
+ 'تموز يوليو',
+ 'آب أغسطس',
+ 'أيلول سبتمبر',
+ 'تشرين الأول أكتوبر',
+ 'تشرين الثاني نوفمبر',
+ 'كانون الأول ديسمبر'
+];
+
+export const arLocale = {
+ abbr: 'ar',
+ months: months,
+ monthsShort: months,
+ weekdays: 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'),
+ weekdaysShort: 'أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت'.split('_'),
+ weekdaysMin: 'ح_ن_ث_ر_خ_ج_س'.split('_'),
+ weekdaysParseExact: true,
+ longDateFormat: {
+ LT: 'HH:mm',
+ LTS: 'HH:mm:ss',
+ L: 'D/\u200FM/\u200FYYYY',
+ LL: 'D MMMM YYYY',
+ LLL: 'D MMMM YYYY HH:mm',
+ LLLL: 'dddd D MMMM YYYY HH:mm'
+ },
+ meridiemParse: /ص|م/,
+ isPM: function (input) {
+ return 'م' === input;
+ },
+ meridiem: function (hour, minute, isLower) {
+ if (hour < 12) {
+ return 'ص';
+ } else {
+ return 'م';
+ }
+ },
+ calendar: {
+ sameDay: '[اليوم عند الساعة] LT',
+ nextDay: '[غدًا عند الساعة] LT',
+ nextWeek: 'dddd [عند الساعة] LT',
+ lastDay: '[أمس عند الساعة] LT',
+ lastWeek: 'dddd [عند الساعة] LT',
+ sameElse: 'L'
+ },
+ relativeTime: {
+ future: 'بعد %s',
+ past: 'منذ %s',
+ s: pluralize('s'),
+ m: pluralize('m'),
+ mm: pluralize('m'),
+ h: pluralize('h'),
+ hh: pluralize('h'),
+ d: pluralize('d'),
+ dd: pluralize('d'),
+ M: pluralize('M'),
+ MM: pluralize('M'),
+ y: pluralize('y'),
+ yy: pluralize('y')
+ },
+ preparse: function (string) {
+ return string.replace(/[١٢٣٤٥٦٧٨٩٠]/g, function (match) {
+ return numberMap[match];
+ }).replace(/،/g, ',');
+ },
+ postformat: function (string) {
+ return string.replace(/\d/g, function (match) {
+ return symbolMap[match];
+ }).replace(/,/g, '،');
+ },
+ week: {
+ dow: 6, // Saturday is the first day of the week.
+ doy: 12 // The week that contains Jan 1st is the first week of the year.
+ }
+};
diff --git a/src/bs-moment/locale/en.ts b/src/bs-moment/locale/en.ts
new file mode 100644
index 0000000000..24b936086a
--- /dev/null
+++ b/src/bs-moment/locale/en.ts
@@ -0,0 +1,14 @@
+import { getSetGlobalLocale } from './locales.service';
+import { toInt } from '../utils/type-checks';
+
+getSetGlobalLocale('en', {
+ dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/,
+ ordinal(num: number): string {
+ const b = num % 10;
+ const output = (toInt(num % 100 / 10) === 1) ? 'th' :
+ (b === 1) ? 'st' :
+ (b === 2) ? 'nd' :
+ (b === 3) ? 'rd' : 'th';
+ return num + output;
+ }
+});
diff --git a/src/bs-moment/locale/index.ts b/src/bs-moment/locale/index.ts
new file mode 100644
index 0000000000..3286a63a30
--- /dev/null
+++ b/src/bs-moment/locale/index.ts
@@ -0,0 +1 @@
+import './en';
diff --git a/src/bs-moment/locale/locale.class.ts b/src/bs-moment/locale/locale.class.ts
new file mode 100644
index 0000000000..f4cebeb60e
--- /dev/null
+++ b/src/bs-moment/locale/locale.class.ts
@@ -0,0 +1,149 @@
+import { weekOfYear } from '../units/week-calendar-utils';
+import { isArray, isFunction } from '../utils/type-checks';
+import { getDayOfWeek, getMonth } from '../utils/date-getters';
+
+export interface LocaleOptionsFormat {
+ format: string[];
+ standalone: string[];
+ isFormat: RegExp;
+}
+
+export type LocaleOptions = string[] | LocaleOptionsFormat;
+
+const MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/;
+export const defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_');
+export const defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_');
+export const defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_');
+export const defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_');
+export const defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_');
+
+export interface LocaleData {
+ [key: string]: any;
+
+ invalidDate?: string;
+ abbr?: string;
+
+ months?: LocaleOptions;
+ monthsShort?: string[];
+ weekdays?: LocaleOptions;
+ weekdaysMin?: string[];
+ weekdaysShort?: string[];
+ week?: { dow: number, doy: number };
+
+ dayOfMonthOrdinalParse?: RegExp;
+ ordinal?: (num: number) => string;
+ postformat?: (num: string) => string;
+}
+
+export class Locale {
+ [key: string]: any;
+ _abbr: string;
+ _config: LocaleData;
+ invalidDate: string;
+
+ private _months: LocaleOptions;
+ private _monthsShort: LocaleOptions;
+
+ private _weekdays: LocaleOptions;
+ private _weekdaysShort: string[];
+ private _weekdaysMin: string[];
+ private _week: { dow: number, doy: number };
+
+ private _ordinal: string;
+
+ constructor(config: LocaleData) {
+ if (!!config) {
+ this.set(config);
+ }
+ }
+
+ set (config: LocaleData): void {
+ for (const i in config) {
+ if (!config.hasOwnProperty(i)) {
+ continue;
+ }
+ const prop = (config[i] as any);
+ const key = isFunction(prop) ? i : `_${i}`;
+ this[key] = prop;
+ }
+
+ this._config = config;
+ }
+
+ // Months
+ // LOCALES
+ months(date?: Date, format?: string): string | string[] {
+ if (!date) {
+ return isArray(this._months)
+ ? (this._months as string[])
+ : (this._months as LocaleOptionsFormat).standalone;
+ }
+
+ if (isArray(this._months)) {
+ return (this._months as string[])[getMonth(date)];
+ }
+
+ const key = ((this._months as LocaleOptionsFormat).isFormat || MONTHS_IN_FORMAT)
+ .test(format) ? 'format' : 'standalone';
+ return ((this._months as any)[key] as string[])[getMonth(date)];
+ }
+
+ monthsShort(date?: Date, format?: string): string | string[] {
+ if (!date) {
+ return isArray(this._monthsShort)
+ ? (this._monthsShort as string[])
+ : (this._monthsShort as LocaleOptionsFormat).standalone;
+ }
+
+ if (isArray(this._monthsShort)) {
+ return (this._monthsShort as string[])[getMonth(date)];
+ }
+ let key = MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone';
+ return ((this._monthsShort as any)[key] as string[])[getMonth(date)];
+ }
+
+ // Days of week
+// LOCALES
+
+ weekdays(date?: Date, format?: string): string | string[] {
+ const _isArray = isArray(this._weekdays as string[]);
+ if (!date) {
+ return _isArray
+ ? this._weekdays as string[]
+ : (this._weekdays as LocaleOptionsFormat).standalone;
+ }
+
+ if (_isArray) {
+ return (this._weekdays as string[])[getDayOfWeek(date)];
+ }
+
+ const _key = (this._weekdays as LocaleOptionsFormat).isFormat.test(format) ? 'format' : 'standalone';
+ return ((this._weekdays as any)[_key] as string[])[getDayOfWeek(date)];
+ }
+
+ weekdaysMin(date?: Date): string | string[] {
+ return (date) ? this._weekdaysShort[getDayOfWeek(date)] : this._weekdaysShort;
+ }
+
+ weekdaysShort(date?: Date): string | string[] {
+ return (date) ? this._weekdaysMin[getDayOfWeek(date)] : this._weekdaysMin;
+ }
+
+ week(date: Date): number {
+ return weekOfYear(date, this._week.dow, this._week.doy).week;
+ }
+
+ firstDayOfWeek(): number {
+ return this._week.dow;
+ }
+
+ firstDayOfYear(): number {
+ return this._week.doy;
+ }
+
+ ordinal(num: number, token?: string): string {
+ return this._ordinal.replace('%d', num.toString(10));
+ }
+
+ postformat(str: string) { return str; }
+}
diff --git a/src/bs-moment/locale/locale.defaults.ts b/src/bs-moment/locale/locale.defaults.ts
new file mode 100644
index 0000000000..27e72143dd
--- /dev/null
+++ b/src/bs-moment/locale/locale.defaults.ts
@@ -0,0 +1,32 @@
+import {
+ defaultLocaleMonths, defaultLocaleMonthsShort, defaultLocaleWeekdays, defaultLocaleWeekdaysMin,
+ defaultLocaleWeekdaysShort,
+ LocaleData
+} from './locale.class';
+
+export const defaultInvalidDate = 'Invalid date';
+
+export const defaultLocaleWeek = {
+ dow : 0, // Sunday is the first day of the week.
+ doy : 6 // The week that contains Jan 1st is the first week of the year.
+};
+
+export const baseConfig: LocaleData = {
+ // calendar: defaultCalendar,
+ // longDateFormat: defaultLongDateFormat,
+ invalidDate: defaultInvalidDate,
+ // ordinal: defaultOrdinal,
+ // dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse,
+ // relativeTime: defaultRelativeTime,
+
+ months: defaultLocaleMonths,
+ monthsShort: defaultLocaleMonthsShort,
+
+ week: defaultLocaleWeek,
+
+ weekdays: defaultLocaleWeekdays,
+ weekdaysMin: defaultLocaleWeekdaysMin,
+ weekdaysShort: defaultLocaleWeekdaysShort,
+
+ // meridiemParse: defaultLocaleMeridiemParse
+};
diff --git a/src/bs-moment/locale/locales.service.ts b/src/bs-moment/locale/locales.service.ts
new file mode 100644
index 0000000000..b6e4d66e5e
--- /dev/null
+++ b/src/bs-moment/locale/locales.service.ts
@@ -0,0 +1,95 @@
+// internal storage for locale config files
+import { Locale, LocaleData } from './locale.class';
+import { baseConfig } from './locale.defaults';
+import { hasOwnProp, isObject, isUndefined } from '../utils/type-checks';
+
+const locales: { [key: string]: Locale } = {};
+const localeFamilies: { [key: string]: Locale } = {};
+let globalLocale: Locale;
+
+function chooseLocale(name: string) {
+ return locales[name];
+}
+
+// returns locale data
+export function getLocale(key: string): Locale {
+
+ if (!key) {
+ return globalLocale;
+ }
+
+ return chooseLocale(key);
+}
+
+export function listLocales(): string[] {
+ return Object.keys(locales);
+}
+
+export function mergeConfigs(parentConfig: LocaleData, childConfig: LocaleData) {
+ const res: { [key: string]: any } = Object.assign({}, parentConfig);
+
+ for (const childProp in childConfig) {
+ if (!hasOwnProp(childConfig, childProp)) {
+ continue;
+ }
+ if (isObject(parentConfig[childProp]) && isObject(childConfig[childProp])) {
+ (res[childProp]) = {};
+ Object.assign(res[childProp], parentConfig[childProp]);
+ Object.assign(res[childProp], childConfig[childProp]);
+ } else if (childConfig[childProp] != null) {
+ (res[childProp] ) = childConfig[childProp];
+ } else {
+ delete res[childProp];
+ }
+ }
+ for (const parentProp in parentConfig) {
+ if (hasOwnProp(parentConfig, parentProp) &&
+ !hasOwnProp(childConfig, parentProp) &&
+ isObject(parentConfig[parentProp])) {
+ // make sure changes to properties don't modify parent config
+ (res[parentProp] ) = Object.assign({}, res[parentProp]);
+ }
+ }
+ return res;
+}
+
+// This function will load locale and then set the global locale. If
+// no arguments are passed in, it will simply return the current global
+// locale key.
+export function getSetGlobalLocale(key: string, values?: LocaleData): string {
+ let data: Locale;
+ if (key) {
+ data = isUndefined(values) ? getLocale(key) : defineLocale(key, values);
+
+ if (data) {
+ globalLocale = data;
+ }
+ }
+
+ return globalLocale._abbr;
+}
+
+export function defineLocale(name: string, config?: LocaleData): Locale {
+ if (config === null) {
+ // useful for testing
+ delete locales[name];
+ return null;
+ }
+
+ config.abbr = name;
+
+ locales[name] = new Locale(mergeConfigs(baseConfig, config));
+
+ if (localeFamilies[name]) {
+ localeFamilies[name].forEach(function (x: Locale) {
+ defineLocale(x.name, x.config);
+ });
+ }
+
+ // backwards compat for now: also set the locale
+ // make sure we set the locale AFTER all child locales have been
+ // created, so we won't end up with the child locale set.
+ getSetGlobalLocale(name);
+
+ return locales[name];
+}
diff --git a/src/bs-moment/units/day-of-month.ts b/src/bs-moment/units/day-of-month.ts
new file mode 100644
index 0000000000..a0a1af36ec
--- /dev/null
+++ b/src/bs-moment/units/day-of-month.ts
@@ -0,0 +1,6 @@
+import { addFormatToken } from '../format-functions';
+import { getDate } from '../utils/date-getters';
+
+addFormatToken('D', ['DD', 2], 'Do', function (date: Date): string {
+ return getDate(date).toString(10);
+});
diff --git a/src/bs-moment/units/day-of-week.ts b/src/bs-moment/units/day-of-week.ts
new file mode 100644
index 0000000000..6e9566e63c
--- /dev/null
+++ b/src/bs-moment/units/day-of-week.ts
@@ -0,0 +1,35 @@
+import { addFormatToken } from '../format-functions';
+import { Locale } from '../locale/locale.class';
+import { getDayOfWeek } from '../utils/date-getters';
+
+// FORMATTING
+addFormatToken('d', null, 'do', function (date: Date): string {
+ return getDayOfWeek(date).toString(10);
+});
+
+addFormatToken('dd', null, null, function (date: Date, format: string, locale?: Locale): string {
+ return locale.weekdaysMin(date) as string;
+});
+
+addFormatToken('ddd', null, null, function (date: Date, format: string, locale?: Locale): string {
+ return locale.weekdaysShort(date) as string;
+});
+
+addFormatToken('dddd', null, null, function (date: Date, format: string, locale?: Locale): string {
+ return locale.weekdays(date, format) as string;
+});
+
+addFormatToken('e', null, null, function (date: Date): string {
+ return getDayOfWeek(date).toString(10);
+});
+addFormatToken('E', null, null, function (date: Date): string {
+ return getISODayOfWeek(date).toString(10);
+});
+
+export function getLocaleDayOfWeek(date: Date, locale: Locale): number {
+ return (getDayOfWeek(date) + 7 - locale.firstDayOfWeek()) % 7;
+}
+
+export function getISODayOfWeek(date: Date): number {
+ return getDayOfWeek(date) || 7;
+}
diff --git a/src/bs-moment/units/day-of-year.ts b/src/bs-moment/units/day-of-year.ts
new file mode 100644
index 0000000000..e344317317
--- /dev/null
+++ b/src/bs-moment/units/day-of-year.ts
@@ -0,0 +1,13 @@
+import { addFormatToken } from '../format-functions';
+
+// FORMATTING
+addFormatToken('DDD', ['DDDD', 3], 'DDDo', function (date: Date): string {
+ return getDayOfYear(date).toString(10);
+});
+
+export function getDayOfYear(date: Date) {
+ const start = new Date(date.getFullYear(), 0, 0);
+ const diff = date.getTime() - start.getTime();
+ const oneDay = 1000 * 60 * 60 * 24;
+ return Math.round(diff / oneDay);
+}
diff --git a/src/bs-moment/units/index.ts b/src/bs-moment/units/index.ts
new file mode 100644
index 0000000000..b72bd38374
--- /dev/null
+++ b/src/bs-moment/units/index.ts
@@ -0,0 +1,7 @@
+import './day-of-month';
+import './day-of-week';
+import './day-of-year';
+import './month';
+import './week';
+import './week-calendar-utils';
+import './year';
diff --git a/src/bs-moment/units/month.ts b/src/bs-moment/units/month.ts
new file mode 100644
index 0000000000..8e698edc03
--- /dev/null
+++ b/src/bs-moment/units/month.ts
@@ -0,0 +1,28 @@
+import { addFormatToken } from '../format-functions';
+import { isLeapYear } from './year';
+import { Locale } from '../locale/locale.class';
+import { mod } from '../utils';
+import { getMonth } from '../utils/date-getters';
+
+export function daysInMonth(year: number, month: number): number {
+ if (isNaN(year) || isNaN(month)) {
+ return NaN;
+ }
+ const modMonth = mod(month, 12);
+ year += (month - modMonth) / 12;
+ return modMonth === 1 ? (isLeapYear(year) ? 29 : 28) : (31 - modMonth % 7 % 2);
+}
+
+// FORMATTING
+
+addFormatToken('M', ['MM', 2], 'Mo', function (date: Date, format: string): string {
+ return (getMonth(date) + 1).toString();
+});
+
+addFormatToken('MMM', null, null, function (date: Date, format: string, locale?: Locale): string {
+ return locale.monthsShort(date, format) as string;
+});
+
+addFormatToken('MMMM', null, null, function (date: Date, format: string, locale?: Locale): string {
+ return locale.months(date, format) as string;
+});
diff --git a/src/bs-moment/units/week-calendar-utils.ts b/src/bs-moment/units/week-calendar-utils.ts
new file mode 100644
index 0000000000..14a9b50ada
--- /dev/null
+++ b/src/bs-moment/units/week-calendar-utils.ts
@@ -0,0 +1,75 @@
+/**
+ *
+ * @param {number} year
+ * @param {number} dow - start-of-first-week
+ * @param {number} doy - start-of-year
+ * @returns {number}
+ */
+import { createUTCDate } from '../utils';
+import { daysInYear } from './year';
+import { getDayOfYear } from './day-of-year';
+import { getFullYear } from '../utils/date-getters';
+
+function firstWeekOffset(year: number, dow: number, doy: number): number {
+ // first-week day -- which january is always in the first week (4 for iso, 1 for other)
+ const fwd = 7 + dow - doy;
+ // first-week day local weekday -- which local weekday is fwd
+ const fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7;
+
+ return -fwdlw + fwd - 1;
+}
+
+// https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
+export function dayOfYearFromWeeks(year: number, week: number, weekday: number,
+ dow: number, doy: number): { year: number, dayOfYear: number } {
+ const localWeekday = (7 + weekday - dow) % 7;
+ const weekOffset = firstWeekOffset(year, dow, doy);
+ const dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset;
+ let resYear: number;
+ let resDayOfYear: number;
+
+ if (dayOfYear <= 0) {
+ resYear = year - 1;
+ resDayOfYear = daysInYear(resYear) + dayOfYear;
+ } else if (dayOfYear > daysInYear(year)) {
+ resYear = year + 1;
+ resDayOfYear = dayOfYear - daysInYear(year);
+ } else {
+ resYear = year;
+ resDayOfYear = dayOfYear;
+ }
+
+ return {
+ year: resYear,
+ dayOfYear: resDayOfYear
+ };
+}
+
+export function weekOfYear(date: Date, dow: number, doy: number): { week: number, year: number } {
+ const weekOffset = firstWeekOffset(getFullYear(date), dow, doy);
+ const week = Math.floor((getDayOfYear(date) - weekOffset - 1) / 7) + 1;
+ let resWeek: number;
+ let resYear: number;
+
+ if (week < 1) {
+ resYear = getFullYear(date) - 1;
+ resWeek = week + weeksInYear(resYear, dow, doy);
+ } else if (week > weeksInYear(getFullYear(date), dow, doy)) {
+ resWeek = week - weeksInYear(getFullYear(date), dow, doy);
+ resYear = getFullYear(date) + 1;
+ } else {
+ resYear = getFullYear(date);
+ resWeek = week;
+ }
+
+ return {
+ week: resWeek,
+ year: resYear
+ };
+}
+
+export function weeksInYear(year: number, dow: number, doy: number): number {
+ const weekOffset = firstWeekOffset(year, dow, doy);
+ const weekOffsetNext = firstWeekOffset(year + 1, dow, doy);
+ return (daysInYear(year) - weekOffset + weekOffsetNext) / 7;
+}
diff --git a/src/bs-moment/units/week.ts b/src/bs-moment/units/week.ts
new file mode 100644
index 0000000000..cb65863f3c
--- /dev/null
+++ b/src/bs-moment/units/week.ts
@@ -0,0 +1,18 @@
+import { addFormatToken } from '../format-functions';
+import { Locale } from '../locale/locale.class';
+import { weekOfYear } from './week-calendar-utils';
+
+addFormatToken('w', ['ww', 2], 'wo', function (date: Date, format: string, locale: Locale): string {
+ return getWeek(date, locale).toString(10);
+});
+addFormatToken('W', ['WW', 2], 'Wo', function (date: Date): string {
+ return getISOWeek(date).toString(10);
+});
+
+export function getWeek(date: Date, locale: Locale): number {
+ return locale.week(date);
+}
+
+export function getISOWeek(date: Date): number {
+ return weekOfYear(date, 1, 4).week;
+}
diff --git a/src/bs-moment/units/year.ts b/src/bs-moment/units/year.ts
new file mode 100644
index 0000000000..6696dbaac4
--- /dev/null
+++ b/src/bs-moment/units/year.ts
@@ -0,0 +1,29 @@
+import { addFormatToken } from '../format-functions';
+import { getFullYear } from '../utils/date-getters';
+
+// FORMATTING
+
+function getYear(date: Date): string {
+ return getFullYear(date).toString();
+}
+
+addFormatToken('Y', null, null, function (date: Date): string {
+ const y = getFullYear(date);
+ return y <= 9999 ? '' + y : '+' + y;
+});
+
+addFormatToken(null, ['YY', 2], null, function (date: Date): string {
+ return (getFullYear(date) % 100).toString(10);
+});
+
+addFormatToken(null, ['YYYY', 4], null, getYear);
+addFormatToken(null, ['YYYYY', 5], null, getYear);
+addFormatToken(null, ['YYYYYY', 6, true], null, getYear);
+
+export function daysInYear(year: number): number {
+ return isLeapYear(year) ? 366 : 365;
+}
+
+export function isLeapYear(year: number): boolean {
+ return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+}
diff --git a/src/bs-moment/utils.ts b/src/bs-moment/utils.ts
new file mode 100644
index 0000000000..5c76a21613
--- /dev/null
+++ b/src/bs-moment/utils.ts
@@ -0,0 +1,27 @@
+export function zeroFill(number: number, targetLength: number, forceSign: boolean): string {
+ const absNumber = '' + Math.abs(number);
+ const zerosToFill = targetLength - absNumber.length;
+ const sign = number >= 0;
+ return (sign ? (forceSign ? '+' : '') : '-') +
+ Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber;
+}
+
+export function mod(n: number, x: number): number {
+ return ((n % x) + x) % x;
+}
+
+export function absFloor(number: number): number {
+ return number < 0
+ ? Math.ceil(number) || 0
+ : Math.floor(number);
+}
+
+export function createUTCDate (y?: number, m?: number, d?: number, h?: number, M?: number, s?: number, ms?: number) {
+ const date = new Date(Date.UTC.apply(null, arguments));
+
+ // the Date.UTC function remaps years 0-99 to 1900-1999
+ if (y < 100 && y >= 0 && isFinite(date.getUTCFullYear())) {
+ date.setUTCFullYear(y);
+ }
+ return date;
+}
diff --git a/src/bs-moment/utils/date-getters.ts b/src/bs-moment/utils/date-getters.ts
new file mode 100644
index 0000000000..339e244350
--- /dev/null
+++ b/src/bs-moment/utils/date-getters.ts
@@ -0,0 +1,38 @@
+import { createDate } from '../../datepicker/utils/date-utils';
+
+export function getDayOfWeek(date: Date, isUTC = false): number {
+ return isUTC ? date.getUTCDay() : date.getDay();
+}
+export function getDate(date: Date, isUTC = false): number {
+ return isUTC ? date.getUTCDate() : date.getDate();
+}
+
+export function getMonth(date: Date, isUTC = false): number {
+ return isUTC ? date.getUTCMonth() : date.getMonth();
+}
+
+export function getFullYear(date: Date, isUTC = false): number {
+ return isUTC ? date.getUTCFullYear() : date.getFullYear();
+}
+
+export function getFirstDayOfMonth(date: Date): Date {
+ return createDate(date.getFullYear(), date.getMonth(), 1,
+ date.getHours(), date.getMinutes(), date.getSeconds());
+}
+
+export function daysInMonth(date: Date): number {
+ return _daysInMonth(date.getFullYear(), date.getMonth());
+}
+
+export function _daysInMonth(year: number, month: number): number {
+ return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
+}
+
+export function isFirstDayOfWeek(date: Date, firstDayOfWeek: number): boolean {
+ return date.getDay() === firstDayOfWeek;
+}
+
+export function isSameMonth(date1: Date, date2: Date) {
+ if (!date1 || !date2) {return false;}
+ return getFullYear(date1) === getFullYear(date2) && getMonth(date1) === getMonth(date2);
+}
diff --git a/src/bs-moment/utils/type-checks.ts b/src/bs-moment/utils/type-checks.ts
new file mode 100644
index 0000000000..8be16cf8a3
--- /dev/null
+++ b/src/bs-moment/utils/type-checks.ts
@@ -0,0 +1,37 @@
+import { absFloor } from '../utils';
+
+export function isDateValid(date: Date): boolean {
+ return date && !isNaN(date.getTime());
+}
+export function isFunction(fn: Function): fn is Function {
+ return fn instanceof Function || Object.prototype.toString.call(fn) === '[object Function]';
+}
+
+export function isArray(input: any): boolean {
+ return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]';
+}
+
+export function hasOwnProp(a: any/*object*/, b: string | number): boolean {
+ return Object.prototype.hasOwnProperty.call(a, b);
+}
+
+export function isObject(input: any/*object*/): boolean {
+ // IE8 will treat undefined and null as object if it wasn't for
+ // input != null
+ return input != null && Object.prototype.toString.call(input) === '[object Object]';
+}
+
+export function isUndefined(input: any): boolean {
+ return input === void 0;
+}
+
+export function toInt(argumentForCoercion: string | number): number {
+ const coercedNumber = +argumentForCoercion;
+ let value = 0;
+
+ if (coercedNumber !== 0 && isFinite(coercedNumber)) {
+ value = absFloor(coercedNumber);
+ }
+
+ return value;
+}
diff --git a/src/datepicker/bs-datepicker-config.ts b/src/datepicker/bs-datepicker-config.ts
new file mode 100644
index 0000000000..41d7a6847f
--- /dev/null
+++ b/src/datepicker/bs-datepicker-config.ts
@@ -0,0 +1,4 @@
+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
new file mode 100644
index 0000000000..71f321c50e
--- /dev/null
+++ b/src/datepicker/bs-datepicker.component.ts
@@ -0,0 +1,141 @@
+import {
+ Component, ComponentRef,
+ ElementRef,
+ EventEmitter,
+ Input,
+ OnDestroy,
+ OnInit,
+ Output,
+ Renderer,
+ ViewContainerRef
+} from '@angular/core';
+import { ComponentLoader } from '../component-loader/component-loader.class';
+import { ComponentLoaderFactory } from '../component-loader/component-loader.factory';
+import { BsDatepickerContainerComponent } from './themes/bs/bs-datepicker-container.component';
+import { Subscription } from 'rxjs/Subscription';
+import 'rxjs/add/operator/filter';
+
+@Component({
+ selector: 'bs-datepicker',
+ exportAs: 'bsDatepicker',
+ template: ''
+})
+export class BsDatepickerComponent implements OnInit, OnDestroy {
+ /**
+ * Placement of a popover. Accepts: "top", "bottom", "left", "right"
+ */
+ @Input() placement: 'top' | 'bottom' | 'left' | 'right' = 'bottom';
+ /**
+ * Specifies events that should trigger. Supports a space separated list of
+ * event names.
+ */
+ @Input() triggers = 'click';
+ /**
+ * A selector specifying the element the popover should be appended to.
+ * Currently only supports "body".
+ */
+ @Input() container = 'body';
+
+ /**
+ * Returns whether or not the popover is currently being shown
+ */
+ @Input()
+ public get isOpen(): boolean {
+ return this._datepicker.isShown;
+ }
+
+ public set isOpen(value: boolean) {
+ if (value) { this.show(); } else { this.hide(); }
+ }
+
+ /**
+ * Emits an event when the popover is shown
+ */
+ @Output() onShown: EventEmitter;
+ /**
+ * Emits an event when the popover is hidden
+ */
+ @Output() onHidden: EventEmitter;
+
+ // here will be parsed options and set defaults
+ // @Input() config: BsDatePickerOptions;
+ // configChange: EventEmitter = new EventEmitter();
+
+ @Input() value: Date;
+ @Output() valueChange: EventEmitter = new EventEmitter();
+
+ protected subscriptions: Subscription[] = [];
+
+ private _datepicker: ComponentLoader;
+ private _datepickerRef: ComponentRef;
+
+ constructor(_elementRef: ElementRef,
+ _renderer: Renderer,
+ _viewContainerRef: ViewContainerRef,
+ cis: ComponentLoaderFactory) {
+ 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;
+
+ this.valueChange
+ .filter(value => !!value)
+ .subscribe(value => this.hide());
+ }
+
+ /**
+ * Opens an element’s datepicker. This is considered a “manual” triggering of
+ * the datepicker.
+ */
+ show(): void {
+ if (this._datepicker.isShown) {
+ return;
+ }
+
+ this._datepickerRef = this._datepicker
+ .attach(BsDatepickerContainerComponent)
+ .to(this.container)
+ .position({attachment: this.placement})
+ .show({placement: this.placement});
+
+ // link with datepicker
+ this._datepickerRef.instance.value = this.value;
+ this.subscriptions.push(this._datepickerRef.instance
+ .valueChange.subscribe((value: Date) => this.valueChange.emit(value)));
+ }
+
+ /**
+ * Closes an element’s datepicker. This is considered a “manual” triggering of
+ * the datepicker.
+ */
+ hide(): void {
+ if (this.isOpen) {
+ this._datepicker.hide();
+ }
+ }
+
+ /**
+ * Toggles an element’s datepicker. This is considered a “manual” triggering of
+ * the datepicker.
+ */
+ toggle(): void {
+ if (this.isOpen) {
+ return this.hide();
+ }
+
+ this.show();
+ }
+
+ ngOnInit(): any {
+ this._datepicker.listen({
+ triggers: this.triggers,
+ show: () => this.show()
+ });
+ }
+
+ ngOnDestroy(): any {
+ this._datepicker.dispose();
+ }
+}
diff --git a/src/datepicker/bs-datepicker.css b/src/datepicker/bs-datepicker.css
new file mode 100644
index 0000000000..36397545f9
--- /dev/null
+++ b/src/datepicker/bs-datepicker.css
@@ -0,0 +1,553 @@
+.bs-datepicker {
+ display: inline-block;
+ vertical-align: top;
+ min-width: 279px;
+ min-height: 250px;
+ background: #fff;
+ box-shadow: 0 10px 20px rgba(84, 112, 139, 0.1);
+ position: relative;
+ z-index: 1;
+}
+
+.bs-datepicker:after {
+ clear: both;
+ content: '';
+ display: block;
+}
+
+.bs-datepicker bs-day-picker {
+ float: left;
+}
+
+.bs-datepicker-head {
+ min-width: 270px;
+ height: 50px;
+ padding: 10px;
+ border-radius: 3px 3px 0 0;
+ text-align: justify;
+}
+
+.bs-datepicker-head:after {
+ content: "";
+ display: inline-block;
+ vertical-align: top;
+ width: 100%;
+}
+
+.bs-datepicker-head button {
+ display: inline-block;
+ vertical-align: top;
+ padding: 0;
+ height: 30px;
+ line-height: 30px;
+ border: 0;
+ background: transparent;
+ text-align: center;
+ cursor: pointer;
+ color: #fff;
+ transition: 0.3s;
+}
+
+.bs-datepicker-head button.next,
+.bs-datepicker-head button.previous {
+ border-radius: 50%;
+ width: 30px;
+ height: 30px;
+}
+
+.bs-datepicker-head button.next span,
+.bs-datepicker-head button.previous span {
+ font-size: 28px;
+ line-height: 1;
+ display: inline-block;
+ position: relative;
+ top: -1px;
+}
+
+.bs-datepicker-head button.current {
+ border-radius: 15px;
+ max-width: 155px;
+ padding: 0 13px;
+}
+
+.bs-datepicker-head.years button.current,
+.bs-datepicker-head.months button.current {
+ width: 155px;
+ padding: 0;
+}
+
+.bs-datepicker button:hover,
+.bs-datepicker button:focus,
+.bs-datepicker button:active,
+.bs-datepicker input:hover,
+.bs-datepicker input:focus,
+.bs-datepicker input:active,
+.bs-datepicker-btns button:hover,
+.bs-datepicker-btns button:focus,
+.bs-datepicker-btns button:active,
+.bs-datepicker-predefined-btns button:active,
+.bs-datepicker-predefined-btns button:focus {
+ outline: none;
+}
+
+.bs-datepicker-head button:hover,
+.bs-datepicker-btns button.colored:hover:after {
+ background-color: rgba(0, 0, 0, 0.1);
+}
+
+.bs-datepicker-head button:active,
+.bs-datepicker-btns button.colored:active:after {
+ background-color: rgba(0, 0, 0, 0.2);
+}
+
+.bs-datepicker-body {
+ padding: 10px;
+ border-radius: 0 0 3px 3px;
+ height: 230px;
+ border: 1px solid #eee;
+}
+
+.bs-datepicker-body .days.weeks {
+ position: relative;
+ z-index: 1;
+}
+
+.bs-datepicker-body table {
+ width: 100%;
+ border-collapse: separate;
+}
+
+.bs-datepicker-body table th {
+ font-size: 13px;
+ color: #9aaec1;
+ font-weight: 400;
+ text-align: center;
+}
+
+.bs-datepicker-body table td {
+ color: #54708b;
+ text-align: center;
+ position: relative;
+}
+
+.bs-datepicker-body table td span {
+ display: block;
+ margin: 0 auto;
+ font-size: 13px;
+ width: 32px;
+ height: 32px;
+ line-height: 32px;
+ border-radius: 50%;
+ position: relative;
+ /*z-index: 1;*/
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+}
+
+.bs-datepicker-body table td:not(.disabled) span {
+ cursor: pointer;
+}
+
+.bs-datepicker-body table.days td span.is-highlighted:not(.disabled):not(.selected),
+.bs-datepicker-body table:not(.days) td span.is-highlighted:not(.disabled):not(.selected),
+.bs-datepicker-body table.days td.is-highlighted:not(.disabled):not(.selected) span,
+.bs-datepicker-body table:not(.days) td.is-highlighted:not(.disabled):not(.selected) span {
+ background-color: #e9edf0;
+ transition: 0s;
+}
+
+.bs-datepicker-body table td span.disabled,
+.bs-datepicker-body table td.disabled span {
+ color: #9aaec1;
+}
+
+.bs-datepicker-body table td span.selected,
+.bs-datepicker-body table td.selected span {
+ color: #fff;
+}
+
+.bs-datepicker-body table td.active {
+ position: relative;
+}
+
+.bs-datepicker-body table td span.active.select-start:after,
+.bs-datepicker-body table td span.active.select-end:after,
+.bs-datepicker-body table td.active.select-start span:after,
+.bs-datepicker-body table td.active.select-end span:after {
+ content: "";
+ display: block;
+ position: absolute;
+ z-index: -1;
+ width: 100%;
+ height: 100%;
+ transition: 0.3s;
+ top: 0;
+ border-radius: 50%;
+}
+
+.bs-datepicker-body table td:before,
+.bs-datepicker-body table td span:before {
+ content: "";
+ display: block;
+ position: absolute;
+ z-index: -1;
+ top: 6px;
+ bottom: 6px;
+ left: -2px;
+ right: -2px;
+ box-sizing: content-box;
+ background: transparent;
+}
+.bs-datepicker-body table.days span.select-start {
+ z-index: 2;
+}
+
+.bs-datepicker-body table.days td.active:not(.select-start):before,
+.bs-datepicker-body table.days td.in-range:not(.select-start):before,
+.bs-datepicker-body table.days span.active:not(.select-start):before,
+.bs-datepicker-body table.days span.in-range:not(.select-start):before {
+ background: #e9edf0;
+}
+
+.bs-datepicker-body table.days span.is-highlighted.in-range:before,
+.bs-datepicker-body table.days span.in-range.select-end:before {
+ background: none;
+ right: 0;
+ left: 0;
+}
+
+.bs-datepicker-body table td.active.select-start + td.active:before {
+ left: -20%;
+}
+
+.bs-datepicker-body table.days td.select-start + td.select-end:before,
+.bs-datepicker-body table.days td.select-start + td.is-highlighted:before,
+.bs-datepicker-body table.days td.active + td.is-highlighted:before,
+.bs-datepicker-body table.days td.active + td.select-end:before,
+.bs-datepicker-body table.days td.in-range + td.is-highlighted:before,
+.bs-datepicker-body table.days td.in-range + td.select-end:before {
+ background: #e9edf0;
+ width: 100%;
+}
+
+.bs-datepicker-body table tr td:last-child.active:before {
+ border-radius: 0 3px 3px 0;
+ width: 125%;
+ left: -25%;
+}
+
+.bs-datepicker-body table.weeks tr td:nth-child(2).active:before {
+ border-radius: 3px 0 0 3px;
+ left: 0;
+ width: 100%;
+}
+
+.bs-datepicker-body table:not(.weeks) tr td:first-child:before {
+ border-radius: 3px 0 0 3px;
+}
+
+.bs-datepicker-body table td.active.select-start:before {
+ left: 35%;
+}
+
+.bs-datepicker-body table td.active.select-end:before {
+ left: -85%;
+}
+
+.bs-datepicker-body table td span[class*="select-"]
+.bs-datepicker-body table td[class*="select-"] span {
+ border-radius: 50%;
+ color: #fff;
+}
+
+.label-default ~ .bs-datepicker-body table td span.selected,
+.label-default ~ .bs-datepicker-body table td span[class*="select-"]:after,
+.label-default ~ .bs-datepicker-body table td.selected span,
+.label-default ~ .bs-datepicker-body table td[class*="select-"] span:after {
+ background-color: #777;
+}
+
+.label-primary ~ .bs-datepicker-body table td span.selected,
+.label-primary ~ .bs-datepicker-body table td span[class*="select-"]:after,
+.label-primary ~ .bs-datepicker-body table td.selected span,
+.label-primary ~ .bs-datepicker-body table td[class*="select-"] span:after {
+ background-color: #337ab7;
+}
+
+.label-success ~ .bs-datepicker-body table td span.selected,
+.label-success ~ .bs-datepicker-body table td span[class*="select-"]:after,
+.label-success ~ .bs-datepicker-body table td.selected span,
+.label-success ~ .bs-datepicker-body table td[class*="select-"] span:after {
+ background-color: #5cb85c;
+}
+
+.label-danger ~ .bs-datepicker-body table td span.selected,
+.label-danger ~ .bs-datepicker-body table td span[class*="select-"]:after,
+.label-danger ~ .bs-datepicker-body table td.selected span,
+.label-danger ~ .bs-datepicker-body table td[class*="select-"] span:after {
+ background-color: #d9534f;
+}
+
+.label-warning ~ .bs-datepicker-body table td span.selected,
+.label-warning ~ .bs-datepicker-body table td span[class*="select-"]:after,
+.label-warning ~ .bs-datepicker-body table td.selected span,
+.label-warning ~ .bs-datepicker-body table td[class*="select-"] span:after {
+ background-color: #f0ad4e;
+}
+
+.label-info ~ .bs-datepicker-body table td span.selected,
+.label-info ~ .bs-datepicker-body table td span[class*="select-"]:after,
+.label-info ~ .bs-datepicker-body table td.selected span,
+.label-info ~ .bs-datepicker-body table td[class*="select-"] span:after {
+ background-color: #5bc0de;
+}
+
+.label-default ~ .bs-datepicker-body table td.week span {
+ color: #777;
+}
+
+.label-primary ~ .bs-datepicker-body table td.week span {
+ color: #337ab7;
+}
+
+.label-success ~ .bs-datepicker-body table td.week span {
+ color: #5cb85c;
+}
+
+.label-danger ~ .bs-datepicker-body table td.week span {
+ color: #d9534f;
+}
+
+.label-warning ~ .bs-datepicker-body table td.week span {
+ color: #f0ad4e;
+}
+
+.label-info ~ .bs-datepicker-body table td.week span {
+ color: #5bc0de;
+}
+
+.bs-datepicker-body table.months td span,
+.bs-datepicker-body table.years td span {
+ width: 46px;
+ height: 46px;
+ line-height: 45px;
+ margin: 0 auto;
+}
+
+.bs-datepicker-body table.months tr:not(:last-child) td span,
+.bs-datepicker-body table.years tr:not(:last-child) td span {
+ margin-bottom: 8px;
+}
+
+.bs-datepicker.bs-timepicker:after {
+ content: '';
+ display: block;
+ clear: both;
+}
+
+.bs-datepicker.bs-timepicker,
+.bs-datepicker.bs-padding {
+ padding: 15px;
+}
+
+.bs-datepicker.bs-timepicker .bs-datepicker-body {
+ border: 1px solid #eee;
+ float: left;
+}
+
+.bs-datepicker .current-timedate {
+ color: #54708b;
+ font-size: 15px;
+ text-align: center;
+ height: 30px;
+ line-height: 30px;
+ border-radius: 20px;
+ border: 1px solid #eee;
+ margin-bottom: 10px;
+ cursor: pointer;
+ text-transform: uppercase;
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+}
+
+.bs-datepicker .current-timedate span:not(:empty):before {
+ content: "";
+ width: 15px;
+ height: 16px;
+ display: inline-block;
+ margin-right: 4px;
+ vertical-align: text-bottom;
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAQCAYAAADJViUEAAABMklEQVQoU9VTwW3CQBCcOUgBtEBKSAukAnBKME+wFCAlYIhk8sQlxFABtJAScAsuAPBEewYcxCP8ouxrPDsza61uiVN1o6RNHD4htSCmq49RfO71BvMJqBBkITRf1kmUW49nQRC9h1I5AZlBClaL8aP1fKgOOxCx8aSLs+Q19eZuNO8QmPqJRtDFguy7OAcDbJPs+/BKVPDIPrvD2ZJgWAmVe7O0rI0Vqs1seyWUXpuJoppYCa5L+U++NpNPkr5OE2oMdARsb3gykJT5ydZcL8Z9Ww60nxg2LhjON9li9OwXZzo+xLbp3nC2s9CL2RrueGyVrgwNm8HpsCzZ9EEW6kqXlo1GQe03FzP/7W8Hl0dBtu7Bf7zt6mIwvX1RvzDCm7+q3mAW0Dl/GPdUCeXrZLT9BrDrGkm4qlPvAAAAAElFTkSuQmCC);
+}
+
+.bs-timepicker-container {
+ margin-top: 10px;
+ text-align: center;
+}
+
+.bs-timepicker-label {
+ color: #54708b;
+ margin-bottom: 10px;
+}
+
+.bs-timepicker-controls {
+ display: inline-block;
+ vertical-align: top;
+ margin-right: 10px;
+}
+
+.bs-timepicker-controls button {
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+ border: 0;
+ background-color: #e9edf0;
+ color: #54708b;
+ font-size: 16px;
+ font-weight: 700;
+ vertical-align: middle;
+ line-height: 0;
+ padding: 0;
+ transition: 0.3s;
+}
+
+.bs-timepicker-controls button:hover {
+ background-color: #d5dadd;
+}
+
+.bs-timepicker-controls input {
+ width: 35px;
+ height: 25px;
+ border-radius: 13px;
+ text-align: center;
+ border: 1px solid #e9edf0;
+}
+
+.bs-timepicker .switch-time-format {
+ text-transform: uppercase;
+ min-width: 54px;
+ height: 25px;
+ border-radius: 20px;
+ border: 1px solid #e9edf0;
+ background: #fff;
+ color: #54708b;
+ font-size: 13px;
+}
+
+.bs-timepicker .switch-time-format img {
+ vertical-align: initial;
+ margin-left: 4px;
+}
+
+.bs-datepicker-multiple {
+ display: inline-block;
+ border-radius: 4px;
+ box-shadow: 0 3px 11px rgba(33, 37, 39, 0.2);
+ background-color: #fff;
+}
+
+.bs-datepicker-multiple .bs-datepicker {
+ box-shadow: none;
+ position: relative;
+}
+
+.bs-datepicker-multiple .bs-datepicker:not(:last-child) {
+ padding-right: 10px;
+}
+
+.bs-datepicker-multiple .bs-datepicker + .bs-datepicker:after {
+ content: "";
+ display: block;
+ width: 14px;
+ height: 10px;
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAKCAYAAABrGwT5AAAA1ElEQVQoU42RsQrCUAxF77VuDu7O4oMWW//BURBBpZvgKk4uIrjoqKOTf+DopIO4uYggtFTfw3+pkQqCW1/G5J7kJiFy4m5MxUlxAzgIPHX+lzMPzupRYlYgxiR7vqsOP8YKzsTx0yxFMCUZ+q7aZzlr+OvgoWcAFyAHgat2jLWu48252DdqAihDJGSSJNUUxYmQjs3+hPQBlAh2rG2LCOPnaw3IiGDX99TRCs7ASJsNhUOA7d/LcuHvRG22FIZvsNXw1MX6VZExCilOQKEfeLXr/10+aC9Ho7arh7oAAAAASUVORK5CYII=);
+ position: absolute;
+ top: 25px;
+ left: -8px;
+}
+
+.bs-datepicker-container + .bs-datepicker-btns {
+ border-top: 1px solid #e9edf0;
+}
+
+.bs-datepicker-btns {
+ padding: 10px 0;
+ text-align: right;
+ clear: both;
+ border-top: 1px solid #eee;
+}
+
+.bs-datepicker-btns button {
+ padding: 0 16px;
+ border: 0;
+ border-radius: 15px;
+ height: 30px;
+ color: #54708b;
+ position: relative;
+}
+
+.bs-datepicker-btns button:after {
+ content: "";
+ transition: 0.3s;
+ border-radius: 15px;
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.bs-datepicker-btns button span {
+ position: relative;
+ z-index: 1;
+}
+
+.bs-datepicker-btns button.colored {
+ color: #fff;
+}
+
+.bs-datepicker-btns button:not(.colored) {
+ background: transparent;
+}
+
+.bs-datepicker-btns button:not(.colored):hover {
+ text-decoration: underline;
+}
+
+.bs-datepicker-multiple .left {
+ float: left;
+}
+
+.bs-datepicker-multiple .right {
+ float: right;
+}
+
+.bs-datepicker-predefined-btns {
+ padding: 15px;
+}
+
+.bs-datepicker-predefined-btns button {
+ width: 100%;
+ display: block;
+ height: 30px;
+ background-color: #72899f;
+ border-radius: 4px;
+ color: #fff;
+ border: 0;
+ margin-bottom: 10px;
+ padding: 0 18px;
+ text-align: left;
+ transition: 0.3s;
+}
+
+.bs-datepicker-predefined-btns button:active,
+.bs-datepicker-predefined-btns button:hover {
+ background-color: #54708b;
+}
+
+.bs-datepicker .is-other-month {
+ color: rgba(0, 0, 0, 0.25);
+}
+
diff --git a/src/datepicker/bs-datepicker.module.ts b/src/datepicker/bs-datepicker.module.ts
new file mode 100644
index 0000000000..c780d6d42d
--- /dev/null
+++ b/src/datepicker/bs-datepicker.module.ts
@@ -0,0 +1,43 @@
+import { CommonModule } from '@angular/common';
+import { 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 { BsDatepickerDayViewComponent } from './themes/bs/bs-datepicker-day-view.component';
+import { BsDatepickerConfig } from './bs-datepicker-config';
+import { BsDatepickerEffects } from './reducer/bs-datepicker.effects';
+import { BsDaterangepickerContainerComponent } from './themes/bs/bs-daterangepicker-container.component';
+import { BsDaterangepickerComponent } from './bs-daterangepicker.component';
+import { BsDatepickerComponent } from './bs-datepicker.component';
+import { ComponentLoaderFactory } from '../component-loader/component-loader.factory';
+import { PositioningService } from '../positioning/positioning.service';
+
+@NgModule({
+ imports: [CommonModule],
+ declarations: [
+ BsDatepickerMonthViewComponent,
+ BsDatepickerViewComponent,
+ BsDatepickerNavigationViewComponent,
+ BsDatepickerDayViewComponent,
+ BsDatepickerContainerComponent,
+ BsDaterangepickerContainerComponent,
+ BsDatepickerComponent,
+ BsDaterangepickerComponent
+ ],
+ entryComponents: [BsDatepickerContainerComponent, BsDaterangepickerContainerComponent],
+ exports: [BsDatepickerContainerComponent, BsDaterangepickerContainerComponent,
+ BsDatepickerComponent, BsDaterangepickerComponent]
+})
+export class BsDatepickerModule {
+ static forRoot(): ModuleWithProviders {
+ return {
+ ngModule: BsDatepickerModule,
+ providers: [
+ ComponentLoaderFactory, PositioningService,
+ BsDatepickerStore, BsDatepickerActions, BsDatepickerConfig, BsDatepickerEffects]
+ };
+ }
+}
diff --git a/src/datepicker/bs-daterangepicker.component.ts b/src/datepicker/bs-daterangepicker.component.ts
new file mode 100644
index 0000000000..0de11cc4fc
--- /dev/null
+++ b/src/datepicker/bs-daterangepicker.component.ts
@@ -0,0 +1,130 @@
+import {
+ Component, EventEmitter, Input, OnDestroy, OnInit, Output, ComponentRef, ElementRef,
+ Renderer,
+ ViewContainerRef
+} 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';
+
+@Component({selector: 'bs-daterangepicker', template: ''})
+export class BsDaterangepickerComponent implements OnInit, OnDestroy {
+ /**
+ * Placement of a popover. Accepts: "top", "bottom", "left", "right"
+ */
+ @Input() placement: 'top' | 'bottom' | 'left' | 'right' = 'bottom';
+ /**
+ * Specifies events that should trigger. Supports a space separated list of
+ * event names.
+ */
+ @Input() triggers = 'click';
+ /**
+ * A selector specifying the element the popover should be appended to.
+ * Currently only supports "body".
+ */
+ @Input() container = 'body';
+
+ /**
+ * Returns whether or not the popover is currently being shown
+ */
+ @Input()
+ public get isOpen(): boolean {
+ return this._datepicker.isShown;
+ }
+
+ public set isOpen(value: boolean) {
+ if (value) { this.show(); } else { this.hide(); }
+ }
+
+ /**
+ * Emits an event when the popover is shown
+ */
+ @Output() onShown: EventEmitter;
+ /**
+ * Emits an event when the popover is hidden
+ */
+ @Output() onHidden: EventEmitter;
+
+ // here will be parsed options and set defaults
+ // @Input() config: BsDatePickerOptions;
+ // configChange: EventEmitter = new EventEmitter();
+
+ @Input() value: Date[];
+ @Output() valueChange: EventEmitter = new EventEmitter();
+
+ protected subscriptions: Subscription[] = [];
+
+ private _datepicker: ComponentLoader;
+ private _datepickerRef: ComponentRef;
+
+ constructor(_elementRef: ElementRef,
+ _renderer: Renderer,
+ _viewContainerRef: ViewContainerRef,
+ cis: ComponentLoaderFactory) {
+ 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;
+
+ this.valueChange
+ .filter(range => range && range[0] && !!range[1])
+ .subscribe(range => this.hide());
+ }
+
+ /**
+ * Opens an element’s datepicker. This is considered a “manual” triggering of
+ * the datepicker.
+ */
+ show(): void {
+ if (this._datepicker.isShown) {
+ return;
+ }
+
+ this._datepickerRef = this._datepicker
+ .attach(BsDaterangepickerContainerComponent)
+ .to(this.container)
+ .position({attachment: this.placement})
+ .show({placement: this.placement});
+
+ // link with datepicker
+ this._datepickerRef.instance.value = this.value;
+ this.subscriptions.push(this._datepickerRef.instance
+ .valueChange.subscribe((value: Date[]) => this.valueChange.emit(value)));
+ }
+
+ /**
+ * Closes an element’s datepicker. This is considered a “manual” triggering of
+ * the datepicker.
+ */
+ hide(): void {
+ if (this.isOpen) {
+ this._datepicker.hide();
+ }
+ }
+
+ /**
+ * Toggles an element’s datepicker. This is considered a “manual” triggering of
+ * the datepicker.
+ */
+ toggle(): void {
+ if (this.isOpen) {
+ return this.hide();
+ }
+
+ this.show();
+ }
+
+ ngOnInit(): any {
+ this._datepicker.listen({
+ triggers: this.triggers,
+ show: () => this.show()
+ });
+ }
+
+ ngOnDestroy(): any {
+ this._datepicker.dispose();
+ }
+}
diff --git a/src/datepicker/engine/calc-month-view.ts b/src/datepicker/engine/calc-month-view.ts
new file mode 100644
index 0000000000..f0d4f3769a
--- /dev/null
+++ b/src/datepicker/engine/calc-month-view.ts
@@ -0,0 +1,26 @@
+// 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 { changeDate } from '../utils/date-utils';
+
+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 = changeDate(prevValue, {day: 1});
+ }
+ }
+
+ return {
+ daysMatrix: daysCalendar,
+ month: firstDay
+ };
+}
diff --git a/src/datepicker/engine/flag-month-view.ts b/src/datepicker/engine/flag-month-view.ts
new file mode 100644
index 0000000000..13b4761327
--- /dev/null
+++ b/src/datepicker/engine/flag-month-view.ts
@@ -0,0 +1,82 @@
+import { DayViewModel, MonthViewModel, WeekViewModel } from '../models/index';
+import { isSameMonth } from '../../bs-moment/utils/date-getters';
+
+export interface FlagMonthViewOptions {
+ hoveredDate: Date;
+ selectedDate: Date;
+ selectedRange: Date[];
+ displayMonths: number;
+ monthIndex: number;
+}
+
+export function flagMonthView(formattedMonth: MonthViewModel,
+ options: FlagMonthViewOptions): MonthViewModel {
+ 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);
+ // date range picker
+ const isSelectionStart = !isOtherMonth && isSameDate(day.date, options.selectedRange[0]);
+ const isSelectionEnd = !isOtherMonth && isSameDate(day.date, options.selectedRange[1]);
+
+ const isSelected = !isOtherMonth && isSameDate(day.date, options.selectedDate) ||
+ isSelectionStart || isSelectionEnd;
+
+ const isInRange = !isOtherMonth && isDateInRange(day.date, options.selectedRange, options.hoveredDate);
+ // decide update or not
+ const newDay = Object.assign({}, day, {
+ isOtherMonth,
+ isHovered,
+ isSelected,
+ isSelectionStart,
+ isSelectionEnd,
+ isInRange
+ });
+
+ if (day.isOtherMonth !== newDay.isOtherMonth ||
+ day.isHovered !== newDay.isHovered ||
+ day.isSelected !== newDay.isSelected ||
+ day.isSelectionStart !== newDay.isSelectionStart ||
+ day.isSelectionEnd !== newDay.isSelectionEnd ||
+ day.isInRange !== newDay.isInRange) {
+ week.days[dayIndex] = newDay;
+ }
+ });
+ });
+
+ // todo: add check for linked calendars
+ formattedMonth.hideLeftArrow = options.monthIndex > 0
+ && 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;
+ }
+
+ return date.getFullYear() === selectedDate.getFullYear()
+ && date.getMonth() === selectedDate.getMonth()
+ && date.getDate() === selectedDate.getDate();
+}
+
+function isDateInRange(date: Date, selectedRange: Date[], hoveredDate: Date): boolean {
+ if (!date || !selectedRange[0]) {
+ return false;
+ }
+
+ if (selectedRange[1]) {
+ return date > selectedRange[0] && date <= selectedRange[1];
+ }
+
+ if (hoveredDate) {
+ return date > selectedRange[0] && date <= hoveredDate;
+ }
+
+ return false;
+}
diff --git a/src/datepicker/engine/format-month-view.ts b/src/datepicker/engine/format-month-view.ts
new file mode 100644
index 0000000000..056712a843
--- /dev/null
+++ b/src/datepicker/engine/format-month-view.ts
@@ -0,0 +1,28 @@
+import { DatepickerFormatOptions, DaysCalendarModel, MonthViewModel, MonthViewOptions } 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 {
+ return {
+ month: daysCalendar.month,
+ monthTitle: formatDate(daysCalendar.month, formatOptions.monthTitle, formatOptions.locale),
+ yearTitle: formatDate(daysCalendar.month, formatOptions.yearTitle, formatOptions.locale),
+ weekNumbers: getWeekNumbers(daysCalendar.daysMatrix, formatOptions.weekNumbers, formatOptions.locale),
+ weekdays: getLocale(formatOptions.locale).weekdaysShort() as string[],
+ weeks: daysCalendar.daysMatrix
+ .map((week: Date[], weekIndex: number) => ({
+ days: week.map((date: Date, dayIndex: number) => ({
+ date,
+ label: formatDate(date, formatOptions.dayLabel, formatOptions.locale),
+ 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) : '');
+}
diff --git a/src/datepicker/index.ts b/src/datepicker/index.ts
index d75cc47c17..03a1a56953 100644
--- a/src/datepicker/index.ts
+++ b/src/datepicker/index.ts
@@ -1,15 +1,11 @@
-/*
- todo: general:
- 1. Popup
- 2. Keyboard support
- 3. custom-class attribute support
- 4. date-disabled attribute support
- 5. template-url attribute support
- */
export { DatePickerComponent } from './datepicker.component';
export { DatepickerModule } from './datepicker.module';
-export { DayPickerComponent } from './daypicker.component'
-export { MonthPickerComponent } from './monthpicker.component'
-export { YearPickerComponent } from './yearpicker.component'
-export { DateFormatter } from './date-formatter'
+export { DayPickerComponent } from './daypicker.component';
+export { MonthPickerComponent } from './monthpicker.component';
+export { YearPickerComponent } from './yearpicker.component';
+export { DateFormatter } from './date-formatter';
export { DatepickerConfig } from './datepicker.config';
+
+export { BsDatepickerModule } from './bs-datepicker.module';
+export { BsDatepickerComponent } from './bs-datepicker.component';
+export { BsDaterangepickerComponent } from './bs-daterangepicker.component';
diff --git a/src/datepicker/models/index.ts b/src/datepicker/models/index.ts
new file mode 100644
index 0000000000..436c78033f
--- /dev/null
+++ b/src/datepicker/models/index.ts
@@ -0,0 +1,87 @@
+import { Locale } from '../../bs-moment/locale/locale.class';
+
+export interface DaysCalendarModel {
+ daysMatrix: Date[][];
+ month: Date;
+}
+
+export interface DayViewModel {
+ date: Date;
+ label: string;
+ // flag step
+ isDisabled?: boolean;
+ isHovered?: boolean;
+ isOtherMonth?: boolean;
+ isInRange?: boolean;
+ isSelectionStart?: boolean;
+ isSelectionEnd?: boolean;
+ isSelected?: boolean;
+ // day index
+ monthIndex?: number;
+ weekIndex?: number;
+ dayIndex?: number;
+}
+
+export interface WeekViewModel {
+ days: DayViewModel[];
+}
+
+export interface MonthViewModel {
+ weeks: WeekViewModel[];
+ // format step
+ month: Date;
+ monthTitle: string;
+ yearTitle: string;
+ weekNumbers: string[];
+ weekdays: string[];
+ // flag step
+ hideLeftArrow?: boolean;
+ hideRightArrow?: boolean;
+}
+
+export interface MonthViewOptions {
+ width?: number;
+ height?: number;
+ firstDayOfWeek?: number;
+}
+
+export interface DatepickerFormatOptions {
+ locale: string;
+ monthTitle: string;
+ yearTitle: string;
+ dayLabel: string;
+ weekNumbers: string;
+}
+
+export interface DatepickerRenderOptions {
+ showWeekNumbers?: boolean;
+ displayMonths?: number;
+}
+
+export interface TimeUnit {
+ year?: number;
+ month?: number;
+ day?: number;
+ hour?: number;
+ minute?: number;
+ seconds?: number;
+}
+
+export type DateFormatterFn = (date: Date, format: string, locale?: Locale) => string;
+
+export interface LocaleData {
+ invalidDate: string;
+ postformat: (str: string) => string;
+ ordinal: (str: string) => string;
+}
+
+// events
+
+export interface BsNavigationEvent {
+ step: TimeUnit;
+}
+
+export interface DayHoverEvent {
+ day: DayViewModel;
+ isHovered: boolean;
+}
diff --git a/src/datepicker/reducer/_defaults.ts b/src/datepicker/reducer/_defaults.ts
new file mode 100644
index 0000000000..65e03afded
--- /dev/null
+++ b/src/datepicker/reducer/_defaults.ts
@@ -0,0 +1,20 @@
+import { DatepickerFormatOptions, DatepickerRenderOptions, MonthViewOptions } from '../models/index';
+
+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
+};
diff --git a/src/datepicker/reducer/bs-datepicker.actions.ts b/src/datepicker/reducer/bs-datepicker.actions.ts
new file mode 100644
index 0000000000..556f81b29e
--- /dev/null
+++ b/src/datepicker/reducer/bs-datepicker.actions.ts
@@ -0,0 +1,71 @@
+import { Injectable } from '@angular/core';
+import { DatepickerRenderOptions, DayHoverEvent, TimeUnit } from '../models/index';
+import { Action } from '../../mini-ngrx/index';
+
+@Injectable()
+export class BsDatepickerActions {
+ static readonly CALCULATE = '[datepicker] calculate dates matrix';
+ 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 HOVER = '[datepicker] hover date';
+
+ static readonly SELECT_RANGE = '[daterangepicker] select dates range';
+
+ calculate(viewDate: Date): Action {
+ return {
+ type: BsDatepickerActions.CALCULATE,
+ payload: viewDate
+ };
+ }
+
+ format(): Action {
+ return {
+ type: BsDatepickerActions.FORMAT
+ };
+ }
+
+ flag(): Action {
+ return {
+ type: BsDatepickerActions.FLAG
+ };
+ }
+
+ select(date: Date): Action {
+ return {
+ type: BsDatepickerActions.SELECT,
+ payload: date
+ };
+ }
+
+ navigateStep(step: TimeUnit): Action {
+ return {
+ type: BsDatepickerActions.STEP_NAVIGATION,
+ payload: step
+ };
+ }
+
+ renderOptions(options: DatepickerRenderOptions): Action {
+ return {
+ type: BsDatepickerActions.RENDER_OPTIONS,
+ payload: options
+ };
+ }
+
+ // date range picker
+ selectRange(value: Date[]): Action {
+ return {
+ type: BsDatepickerActions.SELECT_RANGE,
+ payload: value
+ };
+ }
+
+ hover(event: DayHoverEvent): Action {
+ return {
+ type: BsDatepickerActions.HOVER,
+ payload: event.isHovered ? event.day.date : null
+ };
+ }
+}
diff --git a/src/datepicker/reducer/bs-datepicker.effects.ts b/src/datepicker/reducer/bs-datepicker.effects.ts
new file mode 100644
index 0000000000..981a61988c
--- /dev/null
+++ b/src/datepicker/reducer/bs-datepicker.effects.ts
@@ -0,0 +1,19 @@
+import { Injectable } from '@angular/core';
+import { BsDatepickerStore } from './bs-datepicker.store';
+import { BsDatepickerActions } from './bs-datepicker.actions';
+
+@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()));
+ // }
+}
diff --git a/src/datepicker/reducer/bs-datepicker.reducer.ts b/src/datepicker/reducer/bs-datepicker.reducer.ts
new file mode 100644
index 0000000000..4480d9e14b
--- /dev/null
+++ b/src/datepicker/reducer/bs-datepicker.reducer.ts
@@ -0,0 +1,75 @@
+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 { changeDate } from '../utils/date-utils';
+import { flagMonthView } from '../engine/flag-month-view';
+
+export function bsDatepickerReducer(state = initialDatepickerState, action: Action): BsDatepickerState {
+ 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 = changeDate(viewDate, {month: 1});
+ }
+ return Object.assign({}, state, {monthsModel});
+ }
+
+ case (BsDatepickerActions.FORMAT): {
+ const formattedMonths = state.monthsModel
+ .map((month, monthIndex) => formatMonthView(month, state.formatOptions, monthIndex));
+ return Object.assign({}, state, {formattedMonths});
+ }
+
+ 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.STEP_NAVIGATION): {
+ const viewDate = changeDate(state.viewDate, action.payload);
+ return Object.assign({}, state, {viewDate});
+ }
+
+ case(BsDatepickerActions.HOVER): {
+ return Object.assign({}, state, {hoveredDate: action.payload});
+ }
+
+ case(BsDatepickerActions.SELECT): {
+ return Object.assign({}, state, {selectedDate: action.payload});
+ }
+
+ case(BsDatepickerActions.RENDER_OPTIONS): {
+ return Object.assign({}, state, {renderOptions: action.payload});
+ }
+
+ // date range picker
+ case(BsDatepickerActions.SELECT_RANGE): {
+ return Object.assign({}, state, {selectedRange: action.payload});
+ }
+
+ default: return state;
+ }
+}
diff --git a/src/datepicker/reducer/bs-datepicker.state.ts b/src/datepicker/reducer/bs-datepicker.state.ts
new file mode 100644
index 0000000000..26693a9b8b
--- /dev/null
+++ b/src/datepicker/reducer/bs-datepicker.state.ts
@@ -0,0 +1,32 @@
+import {
+ DatepickerFormatOptions, DatepickerRenderOptions, DaysCalendarModel, MonthViewModel,
+ MonthViewOptions
+} from '../models/index';
+import { defaultFormatOptions, defaultMonthOptions, defaultRenderOptions } from './_defaults';
+
+export class BsDatepickerState {
+ // initial date of calendar, today by default
+ viewDate: Date;
+ hoveredDate?: Date;
+ selectedDate?: Date;
+
+ monthsModel?: DaysCalendarModel[];
+ formattedMonths?: MonthViewModel[];
+ flaggedMonths?: MonthViewModel[];
+
+ monthViewOptions: MonthViewOptions;
+
+ formatOptions: DatepickerFormatOptions;
+ renderOptions: DatepickerRenderOptions;
+
+ // daterange picker
+ selectedRange?: Date[];
+}
+
+export const initialDatepickerState: BsDatepickerState = {
+ viewDate: new Date(),
+ selectedRange: [],
+ monthViewOptions: defaultMonthOptions,
+ formatOptions: defaultFormatOptions,
+ renderOptions: defaultRenderOptions
+};
diff --git a/src/datepicker/reducer/bs-datepicker.store.ts b/src/datepicker/reducer/bs-datepicker.store.ts
new file mode 100644
index 0000000000..bda7112ed3
--- /dev/null
+++ b/src/datepicker/reducer/bs-datepicker.store.ts
@@ -0,0 +1,16 @@
+import { Injectable } from '@angular/core';
+import { MiniStore } from '../../mini-ngrx/store.class';
+import { BsDatepickerState, initialDatepickerState } from './bs-datepicker.state';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+import { Action } from '../../mini-ngrx/index';
+import { MiniState } from '../../mini-ngrx/state.class';
+import { bsDatepickerReducer } from './bs-datepicker.reducer';
+
+@Injectable()
+export class BsDatepickerStore extends MiniStore {
+ constructor() {
+ const _dispatcher = new BehaviorSubject({type: '[datepicker] dispatcher init'});
+ const state = new MiniState(initialDatepickerState, _dispatcher, bsDatepickerReducer);
+ super(_dispatcher, bsDatepickerReducer, state);
+ }
+}
diff --git a/src/datepicker/themes/bs/bs-datepicker-container.component.ts b/src/datepicker/themes/bs/bs-datepicker-container.component.ts
new file mode 100644
index 0000000000..d8adef94d4
--- /dev/null
+++ b/src/datepicker/themes/bs/bs-datepicker-container.component.ts
@@ -0,0 +1,112 @@
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import { BsDatepickerStore } from '../../reducer/bs-datepicker.store';
+import { BsDatepickerActions } from '../../reducer/bs-datepicker.actions';
+import {
+ BsNavigationEvent, DatepickerRenderOptions, DayHoverEvent, DayViewModel,
+ MonthViewModel
+} from '../../models/index';
+import 'rxjs/add/operator/filter';
+
+@Component({
+ selector: 'bs-datepicker-container',
+ providers: [BsDatepickerStore],
+ template: `
+
+ `,
+ host: {
+ style: 'position: absolute; display: block;'
+ }
+})
+export class BsDatepickerContainerComponent {
+ @Input()
+ set value(value: Date) {
+ this._bsDatepickerStore.dispatch(this._actions.select(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()));
+
+ // on hover
+ this._bsDatepickerStore.select(state => state.hoveredDate)
+ .filter(hoveredDate => !!hoveredDate)
+ .subscribe(hoveredDate =>
+ this._bsDatepickerStore.dispatch(this._actions.flag()));
+ }
+
+ navigateTo(event: BsNavigationEvent): void {
+ this._bsDatepickerStore.dispatch(this._actions.navigateStep(event.step));
+ }
+
+ hoverHandler(event: DayHoverEvent): void {
+ if (event.day.isOtherMonth) {
+ return;
+ }
+ this._bsDatepickerStore.dispatch(this._actions.hover(event));
+ event.day.isHovered = event.isHovered;
+ }
+
+ selectHandler(day: DayViewModel): void {
+ if (day.isOtherMonth) {
+ return;
+ }
+ this._bsDatepickerStore.dispatch(this._actions.select(day.date));
+ }
+}
diff --git a/src/datepicker/themes/bs/bs-datepicker-day-view.component.ts b/src/datepicker/themes/bs/bs-datepicker-day-view.component.ts
new file mode 100644
index 0000000000..4aff0766c4
--- /dev/null
+++ b/src/datepicker/themes/bs/bs-datepicker-day-view.component.ts
@@ -0,0 +1,35 @@
+import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
+import { DayHoverEvent, DayViewModel } from '../../models/index';
+
+@Component({
+ selector: 'bs-datepicker-day-view',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ template: `
+ {{day.label}}
+ `
+})
+export class BsDatepickerDayViewComponent {
+ @Input() day: DayViewModel;
+
+ @Output() onSelect = new EventEmitter();
+ @Output() onHover = new EventEmitter();
+
+ selectDay(day: DayViewModel): void {
+ this.onSelect.emit(day);
+ }
+
+ hoverDay(day: DayViewModel, isHovered: boolean): void {
+ this.onHover.emit({day, isHovered});
+ }
+}
diff --git a/src/datepicker/themes/bs/bs-datepicker-month-view.component.ts b/src/datepicker/themes/bs/bs-datepicker-month-view.component.ts
new file mode 100644
index 0000000000..3e9fcf9186
--- /dev/null
+++ b/src/datepicker/themes/bs/bs-datepicker-month-view.component.ts
@@ -0,0 +1,49 @@
+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] }}
+ |
+
+
+ |
+
+
+
+ `
+})
+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(event: DayHoverEvent): void {
+ this.onHover.emit(event);
+ }
+}
+
diff --git a/src/datepicker/themes/bs/bs-datepicker-navigation-view.component.ts b/src/datepicker/themes/bs/bs-datepicker-navigation-view.component.ts
new file mode 100644
index 0000000000..07cfdb4759
--- /dev/null
+++ b/src/datepicker/themes/bs/bs-datepicker-navigation-view.component.ts
@@ -0,0 +1,31 @@
+import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
+import { BsNavigationEvent, MonthViewModel, TimeUnit } from '../../models/index';
+
+@Component({
+ selector: 'bs-datepicker-navigation-view',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ template: `
+
+
+
+
+ `
+})
+export class BsDatepickerNavigationViewComponent {
+ @Input() month: MonthViewModel;
+ @Output() onNavigate = new EventEmitter();
+
+ navTo(step: TimeUnit): void {
+ this.onNavigate.emit({step});
+ }
+
+ viewMode(v: string) {}
+}
diff --git a/src/datepicker/themes/bs/bs-datepicker-view.component.ts b/src/datepicker/themes/bs/bs-datepicker-view.component.ts
new file mode 100644
index 0000000000..e308d2c8a9
--- /dev/null
+++ b/src/datepicker/themes/bs/bs-datepicker-view.component.ts
@@ -0,0 +1,49 @@
+import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
+import {
+ BsNavigationEvent, DatepickerRenderOptions, DayHoverEvent, DayViewModel,
+ MonthViewModel
+} from '../../models/index';
+
+@Component({
+ selector: 'bs-datepicker-view',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ template: `
+
+ `
+})
+export class BsDatepickerViewComponent {
+ @Input() months: MonthViewModel[];
+
+ @Input() options: DatepickerRenderOptions;
+
+ @Output() onNavigate = new EventEmitter();
+ @Output() onSelect = new EventEmitter();
+ @Output() onHover = new EventEmitter();
+
+ navigateTo(event: BsNavigationEvent): void {
+ this.onNavigate.emit(event);
+ }
+
+ hoverHandler(event: DayHoverEvent): void {
+ this.onHover.emit(event);
+ }
+
+ selectHandler(event: DayViewModel): void {
+ this.onSelect.emit(event);
+ }
+}
diff --git a/src/datepicker/themes/bs/bs-daterangepicker-container.component.ts b/src/datepicker/themes/bs/bs-daterangepicker-container.component.ts
new file mode 100644
index 0000000000..deeb4bd804
--- /dev/null
+++ b/src/datepicker/themes/bs/bs-daterangepicker-container.component.ts
@@ -0,0 +1,133 @@
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import { BsDatepickerStore } from '../../reducer/bs-datepicker.store';
+import { BsDatepickerActions } from '../../reducer/bs-datepicker.actions';
+import {
+ BsNavigationEvent, DatepickerRenderOptions, DayHoverEvent, DayViewModel,
+ MonthViewModel
+} from '../../models/index';
+import 'rxjs/add/operator/filter';
+
+@Component({
+ selector: 'bs-daterangepicker-container',
+ providers: [BsDatepickerStore],
+ template: `
+
+ `,
+ host: {
+ style: 'position: absolute; display: block;'
+ }
+})
+export class BsDaterangepickerContainerComponent implements OnInit {
+ @Input()
+ set value(value: Date[]) {
+ this._bsDatepickerStore.dispatch(this._actions.selectRange(value));
+ }
+
+ @Output() valueChange = new EventEmitter();
+
+ months: MonthViewModel[];
+ options: DatepickerRenderOptions;
+ _rangeStack: Date[] = [];
+
+ 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: 2,
+ showWeekNumbers: true
+ }));
+
+ // on selected date change
+ this._bsDatepickerStore.select(state => state.selectedRange)
+ .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.selectedRange)
+ .filter(selectedRange => !!selectedRange)
+ .subscribe(selectedRange =>
+ this._bsDatepickerStore.dispatch(this._actions.flag()));
+
+ // on hover
+ this._bsDatepickerStore.select(state => state.hoveredDate)
+ .filter(hoveredDate => !!hoveredDate)
+ .subscribe(hoveredDate =>
+ this._bsDatepickerStore.dispatch(this._actions.flag()));
+ }
+
+ ngOnInit() {
+ // this._bsDatepickerStore.dispatch(this._actions.init());
+ }
+
+ navigateTo(event: BsNavigationEvent): void {
+ this._bsDatepickerStore.dispatch(this._actions.navigateStep(event.step));
+ }
+
+ hoverHandler(event: DayHoverEvent): void {
+ if (event.day.isOtherMonth) {
+ return;
+ }
+ this._bsDatepickerStore.dispatch(this._actions.hover(event));
+ event.day.isHovered = event.isHovered;
+ }
+
+ selectHandler(day: DayViewModel): void {
+ if (day.isOtherMonth) {
+ return;
+ }
+
+ if (this._rangeStack.length === 1) {
+ if (day.date >= this._rangeStack[0]) {
+ this._rangeStack = [this._rangeStack[0], day.date];
+ } else {
+ this._rangeStack = [day.date];
+ }
+ }
+
+ if (this._rangeStack.length === 0) {
+ this._rangeStack = [day.date];
+ }
+
+ this._bsDatepickerStore.dispatch(this._actions.selectRange(this._rangeStack));
+
+ if (this._rangeStack.length === 2) {
+ this._rangeStack = [];
+ }
+ }
+}
diff --git a/src/datepicker/utils/bs-calendar-utils.ts b/src/datepicker/utils/bs-calendar-utils.ts
new file mode 100644
index 0000000000..6f28d02fae
--- /dev/null
+++ b/src/datepicker/utils/bs-calendar-utils.ts
@@ -0,0 +1,11 @@
+import { getDayOfWeek, isFirstDayOfWeek } from '../../bs-moment/utils/date-getters';
+import { changeDate } from './date-utils';
+
+export function getStartingDayOfCalendar(date: Date, options: {firstDayOfWeek?: number}): Date {
+ if (isFirstDayOfWeek(date, options.firstDayOfWeek)) {
+ return date;
+ }
+
+ const weekDay = getDayOfWeek(date);
+ return changeDate(date, {day: -weekDay});
+}
diff --git a/src/datepicker/utils/date-utils.ts b/src/datepicker/utils/date-utils.ts
new file mode 100644
index 0000000000..fba50508f4
--- /dev/null
+++ b/src/datepicker/utils/date-utils.ts
@@ -0,0 +1,21 @@
+import { TimeUnit } from '../models/index';
+
+const defaultTimeUnit: TimeUnit = {
+ year: 0, month: 0, day: 0, hour: 0, minute: 0, seconds: 0
+};
+
+export function createDate(year?: number, month = 0, day = 1, hour = 0, minute = 0, seconds = 0): Date {
+ const _date = new Date();
+ return new Date(year || _date.getFullYear(), month, day, hour, minute, seconds);
+}
+
+export function changeDate(date: Date, unit: TimeUnit): Date {
+ const _unit = Object.assign({}, defaultTimeUnit, unit);
+ return createDate(date.getFullYear() + _unit.year,
+ date.getMonth() + _unit.month,
+ date.getDate() + _unit.day,
+ date.getHours() + _unit.hour,
+ date.getMinutes() + _unit.minute,
+ date.getSeconds() + _unit.seconds
+ );
+}
diff --git a/src/index.ts b/src/index.ts
index 9fc9de76a5..c16be5412b 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -36,7 +36,8 @@ export { CollapseDirective, CollapseModule } from './collapse';
export {
DateFormatter, DatePickerComponent, DatepickerConfig, DatepickerModule,
- DayPickerComponent, MonthPickerComponent, YearPickerComponent
+ DayPickerComponent, MonthPickerComponent, YearPickerComponent,
+ BsDatepickerModule
} from './datepicker';
export {