├── src ├── assets │ └── .gitkeep ├── app │ ├── app.component.scss │ ├── app.module.ts │ ├── app.component.spec.ts │ ├── app.component.ts │ └── app.component.html ├── lib │ ├── core │ │ ├── README.md │ │ ├── public-api.ts │ │ ├── dl-date-adapter.ts │ │ ├── dl-date-adapter-number.ts │ │ ├── dl-date-adapter-native.ts │ │ ├── dl-date-adapter-moment.ts │ │ ├── dl-date-time-string-format.ts │ │ ├── dl-date-adapter-string.spec.ts │ │ ├── dl-date-adapter-string.ts │ │ └── dl-date-time-core.module.ts │ ├── dl-date-time-input │ │ ├── README.md │ │ ├── public-api.ts │ │ ├── dl-date-time-input.module.ts │ │ ├── dl-date-time-input-change.ts │ │ ├── specs │ │ │ ├── model-type │ │ │ │ ├── model-type-date.spec.ts │ │ │ │ ├── model-type-number.spec.ts │ │ │ │ ├── model-type-string.spec.ts │ │ │ │ └── model-type-moment.spec.ts │ │ │ └── dl-date-time-input.directive.spec.ts │ │ ├── dl-date-time-input.directive.md │ │ └── dl-date-time-input.directive.ts │ ├── dl-date-time-picker │ │ ├── README.md │ │ ├── dl-date-time-picker-date-button.ts │ │ ├── specs │ │ │ ├── month-constants.ts │ │ │ ├── dispatch-events.ts │ │ │ ├── model-type │ │ │ │ ├── model-type-string.spec.ts │ │ │ │ ├── model-type-moment.spec.ts │ │ │ │ ├── model-type-date.spec.ts │ │ │ │ └── model-type-number.spec.ts │ │ │ ├── select-filter │ │ │ │ └── select-filter.spec.ts │ │ │ ├── start-date │ │ │ │ └── start-date.spec.ts │ │ │ ├── minute-step │ │ │ │ └── minute-step.spec.ts │ │ │ ├── button-classes │ │ │ │ └── button-classes.spec.ts │ │ │ ├── ng-model │ │ │ │ └── ng-model.spec.ts │ │ │ └── max-view │ │ │ │ └── max-view.spec.ts │ │ ├── public-api.ts │ │ ├── dl-date-time-picker-change.ts │ │ ├── dl-date-time-picker.module.ts │ │ ├── dl-date-time-picker.component.scss │ │ ├── dl-date-time-picker-model.ts │ │ ├── dl-date-time-picker.component.html │ │ ├── dl-model-provider.ts │ │ ├── dl-model-provider-hour.ts │ │ ├── dl-model-provider-month.ts │ │ └── dl-model-provider-day.ts │ ├── tsconfig.lib.json │ ├── index.ts │ └── public-api.ts ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── styles.scss ├── tsconfig.app.json ├── tsconfig.spec.json ├── tsconfig.doc.json ├── main.ts ├── browserslist ├── scss │ └── _variables.scss ├── test.ts ├── karma.conf.js ├── index.html └── polyfills.ts ├── screenshots ├── day.png ├── hour.png ├── year.png ├── minute.png ├── month.png └── stars.png ├── ng-package.json ├── cypress ├── tsconfig.json ├── po │ ├── app.po.ts │ └── date-time-picker.po.ts └── e2e │ └── date-time-picker.cy.ts ├── cypress.config.ts ├── scss-bundle.config.json ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── feature.md │ ├── bug.md │ └── question.md ├── workflows │ └── test.yml ├── pull_request_template.md └── contributing.md ├── tsconfig.json ├── .gitignore ├── images └── coverage-badge.svg ├── LICENSE ├── .eslintrc.json ├── CODE_OF_CONDUCT.md ├── package.json ├── angular.json └── README.md /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/lib/core/README.md: -------------------------------------------------------------------------------- 1 | Core library code for `dlDateTime*` components and directives. 2 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dalelotts/angular-bootstrap-datetimepicker/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /screenshots/day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dalelotts/angular-bootstrap-datetimepicker/HEAD/screenshots/day.png -------------------------------------------------------------------------------- /screenshots/hour.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dalelotts/angular-bootstrap-datetimepicker/HEAD/screenshots/hour.png -------------------------------------------------------------------------------- /screenshots/year.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dalelotts/angular-bootstrap-datetimepicker/HEAD/screenshots/year.png -------------------------------------------------------------------------------- /src/lib/dl-date-time-input/README.md: -------------------------------------------------------------------------------- 1 | See [dl-date-time-input.directive.md](./dl-date-time-input.directive.md) 2 | 3 | -------------------------------------------------------------------------------- /screenshots/minute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dalelotts/angular-bootstrap-datetimepicker/HEAD/screenshots/minute.png -------------------------------------------------------------------------------- /screenshots/month.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dalelotts/angular-bootstrap-datetimepicker/HEAD/screenshots/month.png -------------------------------------------------------------------------------- /screenshots/stars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dalelotts/angular-bootstrap-datetimepicker/HEAD/screenshots/stars.png -------------------------------------------------------------------------------- /src/lib/dl-date-time-picker/README.md: -------------------------------------------------------------------------------- 1 | See [dl-date-time-picker.component.md](./dl-date-time-picker.component.md) 2 | 3 | -------------------------------------------------------------------------------- /ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/ng-packagr/ng-package.schema.json", 3 | "lib": { 4 | "entryFile": "src/lib/index.ts" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "include": ["**/*.ts"], 4 | "compilerOptions": { 5 | "sourceMap": false, 6 | "types": ["cypress"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'cypress' 2 | 3 | export default defineConfig({ 4 | e2e: { 5 | baseUrl: 'http://localhost:4200', 6 | supportFile: false 7 | } 8 | }) 9 | -------------------------------------------------------------------------------- /scss-bundle.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "bundlerOptions" : { 3 | "entryFile": "./src/lib/dl-date-time-picker/dl-date-time-picker.component.scss", 4 | "outFile": "dist/dl-date-time-picker.component.scss" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | @import '~bootstrap/dist/css/bootstrap.min.css'; 4 | @import '~open-iconic/font/css/open-iconic-bootstrap.css'; 5 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "main.ts", 9 | "polyfills.ts" 10 | ], 11 | "include": [ 12 | "**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature 3 | about: Propose a new feature for angular-bootstrap-datetimepicker 4 | 5 | --- 6 | 7 | #### Please describe the feature you would like to request. 8 | 9 | 10 | #### What is the use-case or motivation for this proposal? 11 | 12 | 13 | #### Is there anything else I should know? 14 | -------------------------------------------------------------------------------- /src/lib/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "declarationMap": false, 5 | "types": [], 6 | "esModuleInterop": true 7 | }, 8 | "angularCompilerOptions": { 9 | "compilationMode": "partial" 10 | }, 11 | "exclude": [ 12 | "src/test.ts", 13 | "**/*.spec.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/tsconfig.doc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.app.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "baseUrl": "./", 6 | "module": "es2015", 7 | "types": [] 8 | }, 9 | "exclude": [ 10 | "**/*.spec.ts", 11 | "**/app.*", 12 | "**/environments/*", 13 | "**/specs/*", 14 | "test.ts" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/lib/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | export * from './public-api'; 11 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /src/lib/public-api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | export * from './core/public-api'; 11 | export * from './dl-date-time-input/public-api'; 12 | export * from './dl-date-time-picker/public-api'; 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-input/public-api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | export * from './dl-date-time-input-change'; 11 | export * from './dl-date-time-input.directive'; 12 | export * from './dl-date-time-input.module'; 13 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout Repository 17 | uses: actions/checkout@v2 18 | 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: '14.x' 23 | 24 | - name: Install dependencies and Run Tests 25 | run: npm clean-install-test 26 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-picker/dl-date-time-picker-date-button.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents the configuration for a cell in the picker. 3 | */ 4 | export interface DateButton { 5 | 6 | /** 7 | * The accessible label for the cell. 8 | * Used by screen readers. 9 | */ 10 | ariaLabel: string; 11 | /** 12 | * The classes to add to the cell button 13 | */ 14 | classes: {}; 15 | /** 16 | * The text to display in the button. 17 | */ 18 | display: string; 19 | /** 20 | * The date/time value for the button. 21 | */ 22 | value: number; 23 | } 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "sourceMap": true, 8 | "declaration": false, 9 | "downlevelIteration": true, 10 | "experimentalDecorators": true, 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2017", 14 | "module": "es2020", 15 | "esModuleInterop": true, 16 | "lib": [ 17 | "es2018", 18 | "dom" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /cypress/po/app.po.ts: -------------------------------------------------------------------------------- 1 | const selectors = { 2 | dateTimePicker: 'dl-date-time-picker', 3 | selectedDate: '#selectedDate' 4 | }; 5 | 6 | export const appPage = () => { 7 | const appUrl = '/'; 8 | 9 | const navigateTo = (): void => { 10 | cy.visit(appUrl); 11 | }; 12 | 13 | const getDateTimePicker = (): Cypress.Chainable => { 14 | return cy.get(selectors.dateTimePicker); 15 | } 16 | 17 | const getSelectedDate = (): Cypress.Chainable => { 18 | return cy.get(selectors.selectedDate); 19 | } 20 | 21 | return { 22 | navigateTo, 23 | getDateTimePicker, 24 | getSelectedDate 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug 3 | about: Report a bug found in angular-bootstrap-datetimepicker or the docs 4 | 5 | --- 6 | 7 | #### What is the expected behavior? 8 | 9 | 10 | #### What is the current behavior? 11 | 12 | 13 | #### What are the steps to reproduce? 14 | Providing a StackBlitz reproduction is the *best* way to share your issue.
15 | You can fork the demo on StackBlitz: https://stackblitz.com/github/dalelotts/angular-bootstrap-datetimepicker-demo
16 | 17 | 18 | #### Which versions of angular-bootstrap-datetimepicker, OS, TypeScript, browsers are affected? 19 | 20 | 21 | #### Is there anything else we should know? 22 | -------------------------------------------------------------------------------- /src/lib/core/public-api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | export * from './dl-date-adapter'; 11 | export * from './dl-date-adapter-moment'; 12 | export * from './dl-date-adapter-native'; 13 | export * from './dl-date-adapter-number'; 14 | export * from './dl-date-adapter-string'; 15 | export * from './dl-date-time-core.module'; 16 | export * from './dl-date-time-string-format'; 17 | 18 | -------------------------------------------------------------------------------- /src/scss/_variables.scss: -------------------------------------------------------------------------------- 1 | $dl-abdtp-date-button-border-radius: 999px !default; 2 | $dl-abdtp-date-button-color: hsla(0, 0%, 0%, 0.87) !default; 3 | $dl-abdtp-disabled: rgba(0, 0, 0, 0.25) !default; 4 | $dl-abdtp-hover-background: hsla(0, 0%, 0%, 0.04) !default; 5 | $dl-abdtp-now-border-color: rgba(0, 0, 0, 0.25) !default; 6 | $dl-abdtp-past-future-color: hsla(0, 0%, 0%, 0.04) !default; 7 | $dl-abdtp-selected-background: rgba(0, 82, 204, 0.75) !default; 8 | $dl-abdtp-selected-color: #ffffff !default; 9 | $dl-abdtp-selected-hover-background: #0052cc !default; 10 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {FormsModule} from '@angular/forms'; 3 | import {BrowserModule} from '@angular/platform-browser'; 4 | import {DlDateTimeDateModule} from '../lib'; 5 | import {DlDateTimeInputModule} from '../lib'; 6 | import {DlDateTimePickerModule} from '../lib'; 7 | 8 | import {AppComponent} from './app.component'; 9 | 10 | @NgModule({ 11 | declarations: [ 12 | AppComponent 13 | ], 14 | imports: [ 15 | BrowserModule, 16 | FormsModule, 17 | DlDateTimeDateModule, 18 | DlDateTimePickerModule, 19 | DlDateTimeInputModule 20 | ], 21 | providers: [], 22 | bootstrap: [AppComponent] 23 | }) 24 | export class AppModule { 25 | } 26 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-picker/specs/month-constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | /** 11 | * When constructing a Date, the month is zero-based. This can be confusing, since people are 12 | * used to seeing them one-based. So we create these aliases to make writing the tests easier. 13 | */ 14 | export const JAN = 0, FEB = 1, MAR = 2, APR = 3, MAY = 4, JUN = 5, JUL = 6, AUG = 7, SEP = 8, 15 | OCT = 9, NOV = 10, DEC = 11; 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question about how to use angular-bootstrap-datetimepicker 4 | 5 | --- 6 | 7 | # Please do not ask questions here 8 | 9 | A support question starts with `how`, `why`, or some other statement indicating you are trying to understand how to use this component. 10 | 11 | **This repository's issues are reserved for feature requests and bug reports.** 12 | 13 | **Support questions here will be CLOSED without explanation.** 14 | 15 | For support questions, please use one of these channels: 16 | 17 | - Gitter: https://gitter.im/dalelotts/angular-bootstrap-datetimepicker 18 | - Stack Overflow: http://stackoverflow.com/search?q=angular-bootstrap-datetimepicker 19 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /documentation 6 | /tmp 7 | /out-tsc 8 | 9 | # dependencies 10 | /node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.angular/cache 30 | /.sass-cache 31 | /connect.lock 32 | /coverage 33 | /libpeerconnection.log 34 | npm-debug.log 35 | yarn-error.log 36 | testem.log 37 | /typings 38 | 39 | # System Files 40 | .DS_Store 41 | Thumbs.db 42 | 43 | *.metadata.json 44 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /images/coverage-badge.svg: -------------------------------------------------------------------------------- 1 | documentation95% -------------------------------------------------------------------------------- /src/lib/core/dl-date-adapter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Determines the model type of the Date/Time picker another type. 3 | */ 4 | export abstract class DlDateAdapter { 5 | 6 | /** 7 | * Create a new instance of a `D` type from milliseconds. 8 | * @param milliseconds 9 | * a moment in time value as milliseconds (local time zone) 10 | * @returns 11 | * an instance of `D` for the specified moment in time. 12 | */ 13 | abstract fromMilliseconds(milliseconds: number): D; 14 | 15 | /** 16 | * Returns a moment in time value as milliseconds (local time zone). 17 | * @param value 18 | * a moment in time value as `D` or `null`. 19 | * @returns 20 | * a moment in` for the specified value or `null` 21 | */ 22 | abstract toMilliseconds(value: D | null): number | null; 23 | } 24 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-input/dl-date-time-input.module.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | import {CommonModule} from '@angular/common'; 11 | import {NgModule} from '@angular/core'; 12 | import {DlDateTimeInputDirective} from './dl-date-time-input.directive'; 13 | 14 | /** 15 | * Import this module to allow date/time input. 16 | * @internal 17 | **/ 18 | @NgModule({ 19 | declarations: [DlDateTimeInputDirective], 20 | imports: [CommonModule], 21 | exports: [DlDateTimeInputDirective], 22 | }) 23 | export class DlDateTimeInputModule { 24 | } 25 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | **What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)** 2 | 3 | 4 | 5 | **What is the current behavior? (You can also link to an open issue here)** 6 | 7 | 8 | 9 | **What is the new behavior (if this is a feature change)?** 10 | 11 | 12 | 13 | **Does this PR introduce a breaking change?** 14 | 15 | 16 | 17 | **Please check if the PR fulfills these requirements** 18 | - [ ] This PR is against the `develop` branch. (Please do not submit PR's to the master branch) 19 | - [ ] The commit message follows our guidelines: https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#commit-message-format 20 | - [ ] Tests for the changes have been added (for bug fixes / features) 21 | - [ ] Docs have been added / updated (for bug fixes / features) 22 | 23 | **Other information**: 24 | -------------------------------------------------------------------------------- /src/lib/core/dl-date-adapter-number.ts: -------------------------------------------------------------------------------- 1 | import {DlDateAdapter} from './dl-date-adapter'; 2 | 3 | /** 4 | * Adapts `number` to be usable as a date by date/time components that work with dates. 5 | * No op adapter. 6 | **/ 7 | export class DlDateAdapterNumber extends DlDateAdapter { 8 | /** 9 | * Returns the specified number. 10 | * @param milliseconds 11 | * a moment time time. 12 | * @returns 13 | * the specified moment in time. 14 | */ 15 | fromMilliseconds(milliseconds: number): number { 16 | return milliseconds; 17 | } 18 | 19 | /** 20 | * Returns the specified number. 21 | * @param value 22 | * a moment time time or `null` 23 | * @returns 24 | * the specified moment in time or `null` 25 | */ 26 | toMilliseconds(value: number | null): number | null { 27 | return value; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import {FormsModule} from '@angular/forms'; 3 | import {DlDateTimeDateModule} from '../lib'; 4 | import {DlDateTimePickerModule} from '../lib'; 5 | import { AppComponent } from './app.component'; 6 | 7 | describe('AppComponent', () => { 8 | beforeEach(async () => { 9 | await TestBed.configureTestingModule({ 10 | imports: [ 11 | FormsModule, 12 | DlDateTimeDateModule, 13 | DlDateTimePickerModule, 14 | ], 15 | declarations: [ 16 | AppComponent 17 | ], 18 | }).compileComponents(); 19 | }); 20 | 21 | it('should create the app', () => { 22 | const fixture = TestBed.createComponent(AppComponent); 23 | const app = fixture.debugElement.componentInstance; 24 | expect(app).toBeTruthy(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-picker/public-api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | export * from './dl-date-time-picker-change'; 11 | export * from './dl-date-time-picker-date-button'; 12 | export * from './dl-date-time-picker-model'; 13 | export * from './dl-date-time-picker.component'; 14 | export * from './dl-date-time-picker.module'; 15 | export * from './dl-model-provider'; 16 | export * from './dl-model-provider-day'; 17 | export * from './dl-model-provider-hour'; 18 | export * from './dl-model-provider-minute'; 19 | export * from './dl-model-provider-month'; 20 | export * from './dl-model-provider-year'; 21 | -------------------------------------------------------------------------------- /src/lib/core/dl-date-adapter-native.ts: -------------------------------------------------------------------------------- 1 | import {DlDateAdapter} from './dl-date-adapter'; 2 | 3 | /** 4 | * Adapts `Date` to be usable as a date by date/time components that work with dates. 5 | **/ 6 | export class DlDateAdapterNative extends DlDateAdapter { 7 | /** 8 | * Create a new instance of a `moment` type from milliseconds. 9 | * @param milliseconds 10 | * a time value as milliseconds (local time zone) 11 | * @returns 12 | * an instance of `moment` for the specified moment in time. 13 | */ 14 | fromMilliseconds(milliseconds: number): Date { 15 | return new Date(milliseconds); 16 | } 17 | 18 | 19 | /** 20 | * Returns a moment in time value as milliseconds (local time zone). 21 | * @param value 22 | * a Date or null. 23 | * @returns 24 | * a `value.getTime()` result for the specified `Date` or `null`. 25 | */ 26 | toMilliseconds(value: Date | null): number | null { 27 | return (value) ? value.getTime() : undefined; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-input/dl-date-time-input-change.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | /** 11 | * Emitted when the value of a date/time input changes. 12 | */ 13 | export class DlDateTimeInputChange { 14 | 15 | /** 16 | * The new value of the picker. 17 | */ 18 | private readonly _value: D; 19 | 20 | /** 21 | * Constructs a new instance. 22 | * @param newValue 23 | * the new value of the date/time picker. 24 | */ 25 | constructor(newValue: D) { 26 | this._value = newValue; 27 | } 28 | 29 | /** 30 | * Get the new value of the date/time picker. 31 | * @returns the new value or null. 32 | */ 33 | get value(): D { 34 | return this._value; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-picker/dl-date-time-picker-change.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | /** 11 | * Emitted when the value of a date/time picker changes. 12 | */ 13 | export class DlDateTimePickerChange { 14 | 15 | /** 16 | * The new value of the picker. 17 | */ 18 | private readonly _value: D; 19 | 20 | /** 21 | * Constructs a new instance. 22 | * @param newValue 23 | * the new value of the date/time picker. 24 | */ 25 | constructor(newValue: D) { 26 | this._value = newValue; 27 | } 28 | 29 | /** 30 | * Get the new value of the date/time picker. 31 | * @returns the new value or null. 32 | */ 33 | get value(): D { 34 | return this._value; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/lib/core/dl-date-adapter-moment.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | import {Moment} from 'moment'; 3 | import {DlDateAdapter} from './dl-date-adapter'; 4 | 5 | /** 6 | * Adapts `moment` to be usable as a date by date/time components that work with dates. 7 | **/ 8 | export class DlDateAdapterMoment extends DlDateAdapter { 9 | 10 | /** 11 | * Create a new instance of a `moment` type from milliseconds. 12 | * @param milliseconds 13 | * a time value as milliseconds (local time zone) 14 | * @returns 15 | * an instance of `moment` for the specified moment in time. 16 | */ 17 | fromMilliseconds(milliseconds: number): Moment { 18 | return moment(milliseconds); 19 | } 20 | 21 | /** 22 | * Returns a moment in time value as milliseconds (local time zone). 23 | * @param value 24 | * a moment or `null`. 25 | * @returns 26 | * a `moment.valueOf()` result for the specified `moment` or `null` 27 | */ 28 | toMilliseconds(value: Moment | null): number | null { 29 | return (value) ? value.valueOf() : undefined; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-present Dale Lotts 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | jasmineHtmlReporter: { 19 | suppressAll: true // removes the duplicated traces 20 | }, 21 | coverageReporter: { 22 | dir: require('path').join(__dirname, '../coverage'), 23 | subdir: '.', 24 | reporters: [ 25 | { type: 'html' }, 26 | { type: 'text-summary' }, 27 | { type: 'lcov' } 28 | ] 29 | }, 30 | reporters: ['progress', 'kjhtml'], 31 | port: 9876, 32 | colors: true, 33 | logLevel: config.LOG_INFO, 34 | autoWatch: true, 35 | browsers: ['Chrome'], 36 | singleRun: false 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-picker/dl-date-time-picker.module.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | import {CommonModule} from '@angular/common'; 11 | import {NgModule} from '@angular/core'; 12 | import {DlDateTimePickerComponent} from './dl-date-time-picker.component'; 13 | import {DlDayModelProvider} from './dl-model-provider-day'; 14 | import {DlHourModelProvider} from './dl-model-provider-hour'; 15 | import {DlMinuteModelProvider} from './dl-model-provider-minute'; 16 | import {DlMonthModelProvider} from './dl-model-provider-month'; 17 | import {DlYearModelProvider} from './dl-model-provider-year'; 18 | 19 | /** 20 | * Import this module to supply your own `DateAdapter` provider. 21 | * @internal 22 | **/ 23 | @NgModule({ 24 | declarations: [DlDateTimePickerComponent], 25 | imports: [CommonModule], 26 | exports: [DlDateTimePickerComponent], 27 | providers: [ 28 | DlYearModelProvider, 29 | DlMonthModelProvider, 30 | DlDayModelProvider, 31 | DlHourModelProvider, 32 | DlMinuteModelProvider 33 | ], 34 | }) 35 | export class DlDateTimePickerModule { 36 | } 37 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": [ 4 | "projects/**/*" 5 | ], 6 | "overrides": [ 7 | { 8 | "files": [ 9 | "*.ts" 10 | ], 11 | "parserOptions": { 12 | "project": [ 13 | "tsconfig.json" 14 | ], 15 | "createDefaultProgram": true 16 | }, 17 | "extends": [ 18 | "plugin:@angular-eslint/recommended", 19 | "plugin:@angular-eslint/template/process-inline-templates" 20 | ], 21 | "rules": { 22 | "@angular-eslint/directive-selector": [ 23 | "error", 24 | { 25 | "type": "attribute", 26 | "prefix": "dl", 27 | "style": "camelCase" 28 | } 29 | ], 30 | "@angular-eslint/component-selector": [ 31 | "error", 32 | { 33 | "type": "element", 34 | "prefix": "dl", 35 | "style": "kebab-case" 36 | } 37 | ], 38 | "@typescript-eslint/no-unused-vars": [ 39 | "error", 40 | { 41 | "argsIgnorePattern": "^_" 42 | } 43 | ] 44 | } 45 | }, 46 | { 47 | "files": [ 48 | "*.html" 49 | ], 50 | "extends": [ 51 | "plugin:@angular-eslint/template/recommended" 52 | ], 53 | "rules": {} 54 | } 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /cypress/e2e/date-time-picker.cy.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | import { appPage } from '../po/app.po'; 3 | import { dateTimePickerPage } from '../po/date-time-picker.po'; 4 | 5 | const selectors = { 6 | minViewAttributeName: 'ng-reflect-min-view', 7 | maxViewAttributeName: 'ng-reflect-max-view' 8 | }; 9 | 10 | describe('date-time-picker tests', () => { 11 | 12 | const app = appPage(); 13 | const dateTimePicker = dateTimePickerPage(); 14 | 15 | beforeEach(() => { 16 | app.navigateTo(); 17 | }); 18 | 19 | it('should configure date-time-picker with max view set to year and min view set to minute', () => { 20 | app.getDateTimePicker().should('be.visible').then(el => { 21 | 22 | cy.wrap(el).invoke('attr', selectors.maxViewAttributeName).should('eq', 'year'); 23 | cy.wrap(el).invoke('attr', selectors.minViewAttributeName).should('eq', 'minute'); 24 | }); 25 | }); 26 | 27 | it('should use date-time-picker to select the nearest date to the specified one', () => { 28 | const targetDate = moment('2003-11-07T21:32:17.800Z').valueOf(); 29 | const expectedDate = new Date('2003-11-07T21:30:00.000Z').toString(); 30 | 31 | app.getDateTimePicker().should('be.visible').then(el => { 32 | const maxView = el.attr(selectors.maxViewAttributeName); 33 | const minView = el.attr(selectors.minViewAttributeName); 34 | 35 | dateTimePicker.pickTime(maxView, minView, targetDate); 36 | 37 | app.getSelectedDate().contains(`Selected Date: ${expectedDate}`); 38 | }); 39 | }) 40 | }); 41 | -------------------------------------------------------------------------------- /src/lib/core/dl-date-time-string-format.ts: -------------------------------------------------------------------------------- 1 | import {InjectionToken} from '@angular/core'; 2 | 3 | import * as moment from 'moment'; 4 | 5 | /** 6 | * InjectionToken for string dates that can be used to override default model format. 7 | **/ 8 | export const DL_DATE_TIME_DISPLAY_FORMAT = new InjectionToken('DL_DATE_TIME_DISPLAY_FORMAT'); 9 | 10 | /** 11 | * `Moment`'s long date format `lll` used as the default output format 12 | * for string date's 13 | */ 14 | export const DL_DATE_TIME_DISPLAY_FORMAT_DEFAULT = moment.localeData().longDateFormat('lll'); 15 | 16 | /** 17 | * InjectionToken for string dates that can be used to override default input formats. 18 | **/ 19 | export const DL_DATE_TIME_INPUT_FORMATS = new InjectionToken('DL_DATE__TIME_INPUT_FORMATS'); 20 | 21 | /** 22 | * Default input format's used by `DlDateAdapterString` 23 | */ 24 | export const DL_DATE_TIME_INPUT_FORMATS_DEFAULT = [ 25 | 'YYYY-MM-DDTHH:mm', 26 | 'YYYY-MM-DDTHH:mm:ss', 27 | 'YYYY-MM-DDTHH:mm:ss.SSS', 28 | 'YYYY-MM-DD', 29 | 'M/D/YYYY h:m:s A', 30 | 'M/D/YYYY h:m A', 31 | 'M/D/YYYY h:m A', 32 | 'M/D/YYYY hh:mm A', 33 | 'M/D/YYYY', 34 | 'M/D/YY h:m:s A', 35 | 'M/D/YY h:m A', 36 | 'M/D/YY h A', 37 | 'M/D/YY', 38 | DL_DATE_TIME_DISPLAY_FORMAT_DEFAULT, 39 | moment.ISO_8601, 40 | ]; 41 | 42 | /** 43 | * InjectionToken for string dates that can be used to override default model format. 44 | **/ 45 | export const DL_DATE_TIME_MODEL_FORMAT = new InjectionToken('DL_DATE_TIME_MODEL_FORMAT'); 46 | 47 | /** 48 | * Default model format (ISO 8601)` 49 | */ 50 | export const DL_DATE_TIME_MODEL_FORMAT_DEFAULT = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; 51 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-picker/dl-date-time-picker.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../scss/variables'; 2 | 3 | :host { 4 | user-select: none; 5 | } 6 | 7 | .dl-abdtp-col-label, 8 | .dl-abdtp-view-label { 9 | font-weight: bold; 10 | } 11 | 12 | .dl-abdtp-view-label, 13 | .dl-abdtp-left-button, 14 | .dl-abdtp-right-button, 15 | .dl-abdtp-date-button { 16 | padding: 5px; 17 | border-radius: $dl-abdtp-date-button-border-radius; 18 | cursor: pointer; 19 | color: $dl-abdtp-date-button-color; 20 | outline: 0; 21 | } 22 | 23 | .dl-abdtp-left-button, 24 | .dl-abdtp-up-button, 25 | .dl-abdtp-right-button, 26 | .dl-abdtp-date-button { 27 | border-width: 0; 28 | } 29 | 30 | .dl-abdtp-active:focus, 31 | .dl-abdtp-date-button:focus, 32 | .dl-abdtp-date-button:hover, 33 | .dl-abdtp-left-button:focus, 34 | .dl-abdtp-left-button:hover, 35 | .dl-abdtp-right-button:focus, 36 | .dl-abdtp-right-button:hover, 37 | .dl-abdtp-up-button:focus, 38 | .dl-abdtp-up-button:hover, 39 | .dl-abdtp-view-label:focus { 40 | background: $dl-abdtp-hover-background; 41 | } 42 | 43 | .dl-abdtp-past, 44 | .dl-abdtp-future { 45 | color: $dl-abdtp-past-future-color; 46 | } 47 | 48 | .dl-abdtp-now, 49 | .dl-abdtp-now:hover, 50 | .dl-abdtp-now.disabled, 51 | .dl-abdtp-now.disabled:hover { 52 | border-width: 1px; 53 | border-style: solid; 54 | border-radius: $dl-abdtp-date-button-border-radius; 55 | border-color: $dl-abdtp-now-border-color; 56 | } 57 | 58 | .dl-abdtp-selected { 59 | color: $dl-abdtp-selected-color; 60 | background: $dl-abdtp-selected-background; 61 | } 62 | 63 | .dl-abdtp-selected:focus, 64 | .dl-abdtp-selected:hover { 65 | background: $dl-abdtp-selected-hover-background; 66 | } 67 | 68 | .dl-abdtp-disabled { 69 | cursor: default; 70 | color: $dl-abdtp-disabled; 71 | } 72 | -------------------------------------------------------------------------------- /src/lib/core/dl-date-adapter-string.spec.ts: -------------------------------------------------------------------------------- 1 | import moment, {Moment} from 'moment'; 2 | import {DlDateAdapterString} from './dl-date-adapter-string'; 3 | 4 | 5 | /** 6 | * @license 7 | * Copyright 2013-present Dale Lotts All Rights Reserved. 8 | * http://www.dalelotts.com 9 | * 10 | * Use of this source code is governed by an MIT-style license that can be 11 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 12 | */ 13 | 14 | 15 | 16 | describe('DlDateAdapterString', () => { 17 | 18 | describe('default configuration', () => { 19 | let dateAdapter: DlDateAdapterString; 20 | let testMoment: Moment; 21 | beforeEach(() => { 22 | testMoment = moment(1523077200000); 23 | dateAdapter = new DlDateAdapterString([ 24 | moment.localeData().longDateFormat('lll'), 25 | 'YYYY-MM-DDTHH:mm', 26 | 'YYYY-MM-DDTHH:mm:ss', 27 | 'YYYY-MM-DDTHH:mm:ss.SSS', 28 | 'YYYY-MM-DD', 29 | 'YYYY-MM-DDTHH:mm:ss.SSS[Z]' // ISO_8601 30 | ], 31 | moment.localeData().longDateFormat('lll') 32 | ); 33 | }); 34 | 35 | describe('fromMilliseconds', () => { 36 | it('should return "lll" moment format', () => { 37 | expect(dateAdapter.fromMilliseconds(1523077200000)).toEqual(testMoment.format('lll')); 38 | }); 39 | }); 40 | 41 | describe('toMilliseconds', () => { 42 | it('should accept "lll" moment format', () => { 43 | expect(dateAdapter.toMilliseconds(testMoment.format('lll'))).toEqual(1523077200000); 44 | }); 45 | it('should return undefined for invalid date value', () => { 46 | expect(dateAdapter.toMilliseconds('Aor 7, 2018 12:00 AM')).toBeUndefined(); 47 | }); 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /src/lib/core/dl-date-adapter-string.ts: -------------------------------------------------------------------------------- 1 | import {Inject, Injectable} from '@angular/core'; 2 | import moment from 'moment'; 3 | import {DlDateAdapter} from './dl-date-adapter'; 4 | import {DL_DATE_TIME_INPUT_FORMATS, DL_DATE_TIME_MODEL_FORMAT} from './dl-date-time-string-format'; 5 | 6 | /** 7 | * Adapts `string` to be usable as a date by date/time components that work with dates. 8 | **/ 9 | @Injectable({ 10 | providedIn: 'root' 11 | }) 12 | export class DlDateAdapterString extends DlDateAdapter { 13 | 14 | private readonly inputFormats: string[]; 15 | private readonly modelFormat: string; 16 | 17 | /** 18 | * Constructs a new instance of this class. 19 | * 20 | * @param inputFormats 21 | * see {@link DL_DATE_TIME_INPUT_FORMATS} 22 | * @param modelFormat 23 | * see {@link DL_DATE_TIME_MODEL_FORMAT} 24 | */ 25 | constructor(@Inject(DL_DATE_TIME_INPUT_FORMATS) inputFormats: string[], 26 | @Inject(DL_DATE_TIME_MODEL_FORMAT) modelFormat: string) { 27 | super(); 28 | this.inputFormats = inputFormats; 29 | this.modelFormat = modelFormat; 30 | } 31 | 32 | /** 33 | * Returns the specified number. 34 | * @param milliseconds 35 | * a moment time time. 36 | * @returns 37 | * the specified moment in time. 38 | */ 39 | fromMilliseconds(milliseconds: number): string { 40 | return moment(milliseconds).format(this.modelFormat); 41 | } 42 | 43 | /** 44 | * Returns the specified number. 45 | * @param value 46 | * a moment time time or `null` 47 | * @returns 48 | * the milliseconds for the specified value or `null` 49 | * `null` is returned when value is not a valid input date string 50 | */ 51 | toMilliseconds(value: string | null): number | null { 52 | if (value !== undefined && value !== null) { 53 | const newMoment = moment(value, this.inputFormats, true); 54 | return newMoment.isValid() ? newMoment.valueOf() : undefined; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-picker/specs/dispatch-events.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | export const ENTER = 'Enter'; 11 | export const SPACE = ' '; 12 | export const PAGE_UP = 'PageUp'; 13 | export const PAGE_DOWN = 'PageDown'; 14 | export const END = 'End'; 15 | export const HOME = 'Home'; 16 | export const UP_ARROW = 'ArrowUp'; 17 | export const DOWN_ARROW = 'ArrowDown'; 18 | export const RIGHT_ARROW = 'ArrowRight'; 19 | export const LEFT_ARROW = 'ArrowLeft'; 20 | 21 | /** Utility to dispatch any event on a Node. */ 22 | export function dispatchEvent(node: Node | Window, event: Event): Event { 23 | node.dispatchEvent(event); 24 | return event; 25 | } 26 | 27 | /** Shorthand to dispatch a keyboard event with a specified key code. */ 28 | export function dispatchKeyboardEvent(node: Node, type: string, key: string): KeyboardEvent { 29 | return dispatchEvent(node, createKeyboardEvent(type, key)) as KeyboardEvent; 30 | } 31 | 32 | /** Dispatches a keydown event from an element. */ 33 | export function createKeyboardEvent(type: string, key: string) { 34 | const event = new KeyboardEvent(type, { 35 | bubbles: true, 36 | key: key, 37 | code: key, 38 | cancelable: true, 39 | }); 40 | 41 | // // Firefox does not support `initKeyboardEvent`, but supports `initKeyEvent`. 42 | // const initEventFn = (event.initKeyEvent || event.initKeyboardEvent).bind(event); 43 | const originalPreventDefault = event.preventDefault; 44 | 45 | // IE won't set `defaultPrevented` on synthetic events so we need to do it manually. 46 | event.preventDefault = function () { 47 | Object.defineProperty(event, 'defaultPrevented', {get: () => true}); 48 | return originalPreventDefault.apply(this, arguments); 49 | }; 50 | 51 | return event; 52 | } 53 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-picker/specs/model-type/model-type-string.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | import {Component, ViewChild} from '@angular/core'; 11 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 12 | import {By} from '@angular/platform-browser'; 13 | import {DlDateTimePickerComponent, DlDateTimePickerModule, DlDateTimeStringModule} from '../../../public-api'; 14 | 15 | 16 | @Component({ 17 | template: '' 18 | }) 19 | class ModelTypeComponent { 20 | @ViewChild(DlDateTimePickerComponent, {static: false}) picker: DlDateTimePickerComponent; 21 | } 22 | 23 | describe('DlDateTimePickerComponent modelType', () => { 24 | 25 | beforeEach(async () => { 26 | await TestBed.configureTestingModule({ 27 | imports: [ 28 | DlDateTimeStringModule, 29 | DlDateTimePickerModule, 30 | ], 31 | declarations: [ 32 | ModelTypeComponent, 33 | ] 34 | }).compileComponents(); 35 | }); 36 | 37 | describe('string formatted Date', () => { 38 | let component: ModelTypeComponent; 39 | let fixture: ComponentFixture; 40 | 41 | beforeEach(async () => { 42 | fixture = TestBed.createComponent(ModelTypeComponent); 43 | fixture.detectChanges(); 44 | await fixture.whenStable().then(() => { 45 | fixture.detectChanges(); 46 | component = fixture.componentInstance; 47 | }); 48 | }); 49 | 50 | it('should be string type', () => { 51 | const nowElement = fixture.debugElement.query(By.css('.dl-abdtp-now')); 52 | nowElement.nativeElement.click(); 53 | 54 | expect(component.picker.value).toEqual(jasmine.any(String)); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-picker/specs/model-type/model-type-moment.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | import {Component, ViewChild} from '@angular/core'; 11 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 12 | import {By} from '@angular/platform-browser'; 13 | 14 | import moment, {Moment} from 'moment'; 15 | import {DlDateTimeMomentModule, DlDateTimePickerComponent, DlDateTimePickerModule} from '../../../public-api'; 16 | 17 | @Component({ 18 | template: '' 19 | }) 20 | class ModelTypeComponent { 21 | @ViewChild(DlDateTimePickerComponent, {static: false}) picker: DlDateTimePickerComponent; 22 | } 23 | 24 | describe('DlDateTimePickerComponent modelType', () => { 25 | 26 | beforeEach(async () => { 27 | await TestBed.configureTestingModule({ 28 | imports: [ 29 | DlDateTimeMomentModule, 30 | DlDateTimePickerModule, 31 | ], 32 | declarations: [ 33 | ModelTypeComponent, 34 | ] 35 | }).compileComponents(); 36 | }); 37 | 38 | describe('moment Date', () => { 39 | let component: ModelTypeComponent; 40 | let fixture: ComponentFixture; 41 | 42 | beforeEach(async () => { 43 | fixture = TestBed.createComponent(ModelTypeComponent); 44 | fixture.detectChanges(); 45 | await fixture.whenStable().then(() => { 46 | fixture.detectChanges(); 47 | component = fixture.componentInstance; 48 | }); 49 | }); 50 | 51 | it('should be Date type', () => { 52 | const nowElement = fixture.debugElement.query(By.css('.dl-abdtp-now')); 53 | nowElement.nativeElement.click(); 54 | 55 | expect(component.picker.value).toEqual(jasmine.any(moment)); 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-picker/specs/model-type/model-type-date.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | import {Component, ViewChild} from '@angular/core'; 11 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 12 | import {FormsModule} from '@angular/forms'; 13 | import {By} from '@angular/platform-browser'; 14 | import {DlDateTimeDateModule, DlDateTimePickerComponent, DlDateTimePickerModule} from '../../../public-api'; 15 | 16 | @Component({ 17 | template: '' 18 | }) 19 | class ModelTypeComponent { 20 | @ViewChild(DlDateTimePickerComponent, {static: false}) picker: DlDateTimePickerComponent; 21 | } 22 | 23 | describe('DlDateTimePickerComponent modelType', () => { 24 | 25 | beforeEach(async () => { 26 | await TestBed.configureTestingModule({ 27 | imports: [ 28 | FormsModule, 29 | DlDateTimeDateModule, 30 | DlDateTimePickerModule, 31 | ], 32 | declarations: [ 33 | ModelTypeComponent, 34 | ] 35 | }).compileComponents(); 36 | }); 37 | 38 | describe('native Date', () => { 39 | let component: ModelTypeComponent; 40 | let fixture: ComponentFixture; 41 | 42 | beforeEach(async () => { 43 | fixture = TestBed.createComponent(ModelTypeComponent); 44 | fixture.detectChanges(); 45 | await fixture.whenStable().then(() => { 46 | fixture.detectChanges(); 47 | component = fixture.componentInstance; 48 | }); 49 | }); 50 | 51 | it('should be Date type', () => { 52 | const nowElement = fixture.debugElement.query(By.css('.dl-abdtp-now')); 53 | nowElement.nativeElement.click(); 54 | 55 | expect(component.picker.value).toEqual(jasmine.any(Date)); 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-picker/specs/model-type/model-type-number.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | import {Component, ViewChild} from '@angular/core'; 11 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 12 | import {FormsModule} from '@angular/forms'; 13 | import {By} from '@angular/platform-browser'; 14 | import {DlDateTimeNumberModule, DlDateTimePickerComponent, DlDateTimePickerModule} from '../../../public-api'; 15 | 16 | @Component({ 17 | template: '' 18 | }) 19 | class ModelTypeComponent { 20 | @ViewChild(DlDateTimePickerComponent, {static: false}) picker: DlDateTimePickerComponent; 21 | } 22 | 23 | describe('DlDateTimePickerComponent modelType', () => { 24 | 25 | beforeEach(async () => { 26 | await TestBed.configureTestingModule({ 27 | imports: [ 28 | FormsModule, 29 | DlDateTimeNumberModule, 30 | DlDateTimePickerModule, 31 | ], 32 | declarations: [ 33 | ModelTypeComponent, 34 | ] 35 | }).compileComponents(); 36 | }); 37 | 38 | describe('number', () => { 39 | let component: ModelTypeComponent; 40 | let fixture: ComponentFixture; 41 | 42 | beforeEach(async () => { 43 | fixture = TestBed.createComponent(ModelTypeComponent); 44 | fixture.detectChanges(); 45 | await fixture.whenStable().then(() => { 46 | fixture.detectChanges(); 47 | component = fixture.componentInstance; 48 | }); 49 | }); 50 | 51 | it('should be Number type', () => { 52 | const nowElement = fixture.debugElement.query(By.css('.dl-abdtp-now')); 53 | nowElement.nativeElement.click(); 54 | 55 | expect(component.picker.value).toEqual(jasmine.any(Number)); 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular Bootstrap Date/Time Picker 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 37 |
38 |
39 | Loading... 40 |
41 | 42 | 45 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-input/specs/model-type/model-type-date.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | import {Component, DebugElement, ViewChild} from '@angular/core'; 11 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 12 | import {FormsModule} from '@angular/forms'; 13 | import {By} from '@angular/platform-browser'; 14 | import {DlDateTimeDateModule, DlDateTimeInputDirective, DlDateTimeInputModule} from '../../../public-api'; 15 | 16 | @Component({ 17 | template: `` 23 | }) 24 | class ModelTypeComponent { 25 | @ViewChild(DlDateTimeInputDirective, {static: false}) input: DlDateTimeInputDirective; 26 | } 27 | 28 | describe('DlDateTimeInputDirective modelType', () => { 29 | 30 | beforeEach(async() => { 31 | await TestBed.configureTestingModule({ 32 | imports: [ 33 | FormsModule, 34 | DlDateTimeDateModule, 35 | DlDateTimeInputModule, 36 | ], 37 | declarations: [ 38 | ModelTypeComponent, 39 | ] 40 | }).compileComponents(); 41 | }); 42 | 43 | describe('Date', () => { 44 | let component: ModelTypeComponent; 45 | let fixture: ComponentFixture; 46 | let debugElement: DebugElement; 47 | 48 | beforeEach(async () => { 49 | fixture = TestBed.createComponent(ModelTypeComponent); 50 | fixture.detectChanges(); 51 | await fixture.whenStable().then(() => { 52 | fixture.detectChanges(); 53 | component = fixture.componentInstance; 54 | debugElement = fixture.debugElement; 55 | }); 56 | }); 57 | 58 | it('should be Date type', () => { 59 | const inputElement = debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement; 60 | inputElement.value = '2004-10-01'; 61 | inputElement.dispatchEvent(new Event('input')); 62 | fixture.detectChanges(); 63 | expect(component.input.value).toEqual(jasmine.any(Date)); 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-input/specs/model-type/model-type-number.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | import {Component, DebugElement, ViewChild} from '@angular/core'; 11 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 12 | import {FormsModule} from '@angular/forms'; 13 | import {By} from '@angular/platform-browser'; 14 | import {DlDateTimeInputDirective, DlDateTimeInputModule, DlDateTimeNumberModule} from '../../../public-api'; 15 | 16 | @Component({ 17 | template: `` 23 | }) 24 | class ModelTypeComponent { 25 | @ViewChild(DlDateTimeInputDirective, {static: false}) input: DlDateTimeInputDirective; 26 | } 27 | 28 | describe('DlDateTimeInputDirective modelType', () => { 29 | 30 | beforeEach(async () => { 31 | await TestBed.configureTestingModule({ 32 | imports: [ 33 | FormsModule, 34 | DlDateTimeNumberModule, 35 | DlDateTimeInputModule, 36 | ], 37 | declarations: [ 38 | ModelTypeComponent, 39 | ] 40 | }).compileComponents(); 41 | }); 42 | 43 | describe('number', () => { 44 | let component: ModelTypeComponent; 45 | let fixture: ComponentFixture; 46 | let debugElement: DebugElement; 47 | 48 | beforeEach(async () => { 49 | fixture = TestBed.createComponent(ModelTypeComponent); 50 | fixture.detectChanges(); 51 | await fixture.whenStable().then(() => { 52 | fixture.detectChanges(); 53 | component = fixture.componentInstance; 54 | debugElement = fixture.debugElement; 55 | }); 56 | }); 57 | 58 | it('should be Number type', () => { 59 | const inputElement = debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement; 60 | inputElement.value = '2002-10-01'; 61 | inputElement.dispatchEvent(new Event('input')); 62 | fixture.detectChanges(); 63 | expect(component.input.value).toEqual(jasmine.any(Number)); 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-input/specs/model-type/model-type-string.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | import {Component, DebugElement, ViewChild} from '@angular/core'; 11 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 12 | import {FormsModule} from '@angular/forms'; 13 | import {By} from '@angular/platform-browser'; 14 | import {DlDateTimeInputDirective, DlDateTimeInputModule, DlDateTimeStringModule} from '../../../public-api'; 15 | 16 | @Component({ 17 | template: `` 23 | }) 24 | class ModelTypeComponent { 25 | @ViewChild(DlDateTimeInputDirective, {static: false}) input: DlDateTimeInputDirective; 26 | } 27 | 28 | describe('DlDateTimeInputDirective modelType', () => { 29 | 30 | beforeEach(async () => { 31 | await TestBed.configureTestingModule({ 32 | imports: [ 33 | FormsModule, 34 | DlDateTimeStringModule, 35 | DlDateTimeInputModule, 36 | ], 37 | declarations: [ 38 | ModelTypeComponent, 39 | ] 40 | }).compileComponents(); 41 | }); 42 | 43 | describe('String', () => { 44 | let component: ModelTypeComponent; 45 | let fixture: ComponentFixture; 46 | let debugElement: DebugElement; 47 | 48 | beforeEach(async () => { 49 | fixture = TestBed.createComponent(ModelTypeComponent); 50 | fixture.detectChanges(); 51 | await fixture.whenStable().then(() => { 52 | fixture.detectChanges(); 53 | component = fixture.componentInstance; 54 | debugElement = fixture.debugElement; 55 | }); 56 | }); 57 | 58 | it('should be String type', () => { 59 | const inputElement = debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement; 60 | inputElement.value = '2001-10-01'; 61 | inputElement.dispatchEvent(new Event('input')); 62 | fixture.detectChanges(); 63 | expect(component.input.value).toEqual(jasmine.any(String)); 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-input/specs/model-type/model-type-moment.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | import {Component, DebugElement, ViewChild} from '@angular/core'; 11 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 12 | import {FormsModule} from '@angular/forms'; 13 | import {By} from '@angular/platform-browser'; 14 | import moment from 'moment'; 15 | import {DlDateTimeInputDirective, DlDateTimeInputModule, DlDateTimeMomentModule} from '../../../public-api'; 16 | 17 | @Component({ 18 | template: ` ` 24 | }) 25 | class ModelTypeComponent { 26 | @ViewChild(DlDateTimeInputDirective, {static: false}) input: DlDateTimeInputDirective; 27 | } 28 | 29 | describe('DlDateTimeInputDirective modelType', () => { 30 | 31 | beforeEach(async () => { 32 | await TestBed.configureTestingModule({ 33 | imports: [ 34 | FormsModule, 35 | DlDateTimeMomentModule, 36 | DlDateTimeInputModule, 37 | ], 38 | declarations: [ 39 | ModelTypeComponent, 40 | ] 41 | }).compileComponents(); 42 | }); 43 | 44 | describe('moment', () => { 45 | let component: ModelTypeComponent; 46 | let fixture: ComponentFixture; 47 | let debugElement: DebugElement; 48 | 49 | beforeEach(async () => { 50 | fixture = TestBed.createComponent(ModelTypeComponent); 51 | fixture.detectChanges(); 52 | await fixture.whenStable().then(() => { 53 | fixture.detectChanges(); 54 | component = fixture.componentInstance; 55 | debugElement = fixture.debugElement; 56 | }); 57 | }); 58 | 59 | it('should be moment type', () => { 60 | const inputElement = debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement; 61 | inputElement.value = '2003-10-01'; 62 | inputElement.dispatchEvent(new Event('input')); 63 | fixture.detectChanges(); 64 | expect(component.input.value).toEqual(jasmine.any(moment)); 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-input/dl-date-time-input.directive.md: -------------------------------------------------------------------------------- 1 | This directive provides all of the user facing functionality to input a date and/or time using the keyboard. | 2 | 3 | ## Model date types 4 | 5 | Import the module corresponding to the desired data type of the date in the model. 6 | * Native JavaScript Date: import `DlDateTimePickerDateModule` 7 | * Moment Date: import `DlDateTimePickerMomentModule` 8 | * Milliseconds (local): import `DlDateTimePickerNumberModule` 9 | * String (local): import `DlDateTimePickerStringModule` 10 | 11 | A `DateAdapter` is used to adapt the data type in the model to the `number` data type 12 | used internally by the date/time picker. 13 | 14 | If you need a different data type than what is currently supported, you can extend 15 | `DlDateAdapter` to provide the data type you need then override the `DlDateAdapter` 16 | provider in `app.module.ts` to use your new class. 17 | 18 | `providers: [{provide: DlDateAdapter, useClass: MyCustomDateAdapter}],` 19 | 20 | ## Display format 21 | 22 | The display format is defined in the `DL_DATE_TIME_DISPLAY_FORMAT` token and is injected into this component 23 | to control the display format. 24 | 25 | `DL_DATE_TIME_DISPLAY_FORMAT` defaults to `moment`'s `lll` long date format. 26 | Override `DL_DATE_TIME_DISPLAY_FORMAT` if you do not like the default format. 27 | 28 | `{provide: DL_DATE_TIME_DISPLAY_FORMAT, useValue: ''}` 29 | 30 | ## Input formats 31 | 32 | This directive supports multiple input formats, as defined in the `DL_DATE_TIME_INPUT_FORMATS` token. 33 | When the user inputs a string value into a text box, this directive attempts to parse the input 34 | using one of the specified formats, in the order the format occur in the array. 35 | 36 | Once one of the formats is able to parse the user input, the date is set in the model. 37 | 38 | **Nota bene** For convenience `DL_DATE_TIME_INPUT_FORMATS` defaults to support multiple formats, 39 | which can dramatically slow down parsing performance. It can also result in successfully parsing 40 | a date using a format that is not appropriate for your use case. 41 | 42 | Consider overriding the `DL_DATE_TIME_INPUT_FORMATS` token to only include the specific formats required by your project. 43 | 44 | `{provide: DL_DATE_TIME_INPUT_FORMATS, useValue: ['', ..., '']}` 45 | 46 | See moment's [parsing multiple formats](https://momentjs.com/guides/#/parsing/multiple-formats/) 47 | page for more information on how these date formats are used. 48 | -------------------------------------------------------------------------------- /src/lib/core/dl-date-time-core.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {DlDateAdapter} from './dl-date-adapter'; 3 | import {DlDateAdapterMoment} from './dl-date-adapter-moment'; 4 | import {DlDateAdapterNative} from './dl-date-adapter-native'; 5 | import {DlDateAdapterNumber} from './dl-date-adapter-number'; 6 | import {DlDateAdapterString} from './dl-date-adapter-string'; 7 | import { 8 | DL_DATE_TIME_DISPLAY_FORMAT, 9 | DL_DATE_TIME_DISPLAY_FORMAT_DEFAULT, 10 | DL_DATE_TIME_INPUT_FORMATS, 11 | DL_DATE_TIME_INPUT_FORMATS_DEFAULT, 12 | DL_DATE_TIME_MODEL_FORMAT, 13 | DL_DATE_TIME_MODEL_FORMAT_DEFAULT 14 | } from './dl-date-time-string-format'; 15 | 16 | /** 17 | * Import this module to supply your own `DateAdapter` provider. 18 | * @internal 19 | **/ 20 | @NgModule({ 21 | providers: [ 22 | {provide: DL_DATE_TIME_DISPLAY_FORMAT, useValue: DL_DATE_TIME_DISPLAY_FORMAT_DEFAULT}, 23 | {provide: DL_DATE_TIME_INPUT_FORMATS, useValue: DL_DATE_TIME_INPUT_FORMATS_DEFAULT}, 24 | {provide: DL_DATE_TIME_MODEL_FORMAT, useValue: DL_DATE_TIME_MODEL_FORMAT_DEFAULT} 25 | ] 26 | }) 27 | export class DlDateTimeCoreModule { 28 | } 29 | 30 | /** 31 | * Import this module to store `milliseconds` in the model. 32 | * @internal 33 | */ 34 | @NgModule({ 35 | imports: [DlDateTimeCoreModule], 36 | providers: [ 37 | {provide: DlDateAdapter, useClass: DlDateAdapterNumber} 38 | ], 39 | exports: [DlDateTimeCoreModule] 40 | }) 41 | export class DlDateTimeNumberModule { 42 | } 43 | 44 | /** 45 | * Import this module to store a native JavaScript `Date` in the model. 46 | * @internal 47 | */ 48 | @NgModule({ 49 | imports: [DlDateTimeCoreModule], 50 | providers: [ 51 | {provide: DlDateAdapter, useClass: DlDateAdapterNative} 52 | ], 53 | }) 54 | export class DlDateTimeDateModule { 55 | } 56 | 57 | /** 58 | * Import this module to store a `moment` in the model. 59 | * @internal 60 | */ 61 | @NgModule({ 62 | imports: [DlDateTimeCoreModule], 63 | providers: [ 64 | {provide: DlDateAdapter, useClass: DlDateAdapterMoment} 65 | ], 66 | }) 67 | export class DlDateTimeMomentModule { 68 | } 69 | 70 | /** 71 | * Import this module to store a `string` in the model. 72 | * @internal 73 | */ 74 | @NgModule({ 75 | imports: [DlDateTimeCoreModule], 76 | providers: [ 77 | {provide: DL_DATE_TIME_INPUT_FORMATS, useValue: DL_DATE_TIME_INPUT_FORMATS_DEFAULT}, 78 | {provide: DL_DATE_TIME_MODEL_FORMAT, useValue: DL_DATE_TIME_DISPLAY_FORMAT_DEFAULT}, 79 | {provide: DlDateAdapter, useClass: DlDateAdapterString} 80 | ], 81 | }) 82 | export class DlDateTimeStringModule { 83 | } 84 | 85 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | import {Component} from '@angular/core'; 11 | import {DlDateTimePickerChange} from '../lib'; 12 | 13 | @Component({ 14 | selector: 'dl-root', 15 | templateUrl: './app.component.html', 16 | styleUrls: ['./app.component.scss'] 17 | }) 18 | export class AppComponent { 19 | 20 | maxView = 'year'; 21 | minView = 'minute'; 22 | minuteStep = 5; 23 | selectedDate: Date; 24 | showCalendar = true; 25 | startView = 'day'; 26 | views = ['minute', 'hour', 'day', 'month', 'year']; 27 | 28 | /** 29 | * Sample implementation of a `change` event handler. 30 | * @param event 31 | * The change event. 32 | */ 33 | 34 | onCustomDateChange(event: DlDateTimePickerChange) { 35 | console.log(event.value); 36 | } 37 | 38 | /** 39 | * Determines whether or not the specified `minView` option is disabled (valid) 40 | * considering the current `startView` and `maxView` settings. 41 | * @param view 42 | * the target view value. 43 | */ 44 | 45 | isMinViewDisabled(view) { 46 | return this.views.indexOf(view) > this.views.indexOf(this.startView) 47 | || this.views.indexOf(view) > this.views.indexOf(this.maxView); 48 | } 49 | 50 | /** 51 | * Determines whether or not the specified `maxView` option is disabled (valid) 52 | * considering the current `startView` and `minView` settings. 53 | * @param view 54 | * the target view value. 55 | */ 56 | 57 | isMaxViewDisabled(view) { 58 | return this.views.indexOf(view) < this.views.indexOf(this.startView) 59 | || this.views.indexOf(view) < this.views.indexOf(this.minView); 60 | } 61 | 62 | /** 63 | * Determines whether or not the specified `startView` option is disabled (valid) 64 | * considering the current `minView` and `maxView` settings. 65 | * @param view 66 | * the target view value. 67 | */ 68 | 69 | isStartViewDisabled(view) { 70 | return this.views.indexOf(this.minView) > this.views.indexOf(view) 71 | || this.views.indexOf(this.maxView) < this.views.indexOf(view); 72 | } 73 | 74 | /** 75 | * Removes/Adds the picker from the DOM forcing a re-render when 76 | * the `starView` value changes. 77 | */ 78 | 79 | refresh() { 80 | this.showCalendar = false; 81 | setTimeout(() => this.showCalendar = true, 100); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-picker/dl-date-time-picker-model.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | import {DateButton} from './dl-date-time-picker-date-button'; 11 | 12 | /** 13 | * Interface that represents the model for every view in a date/time picker. 14 | */ 15 | export interface DlDateTimePickerModel { 16 | /** 17 | * The date value of the currently active cell in the model. 18 | */ 19 | activeDate: number; 20 | /** 21 | * Represent the configuration for the left button. 22 | */ 23 | leftButton: { 24 | /** 25 | * The value for the model to the left of this model. 26 | */ 27 | value: number; 28 | 29 | /** 30 | * The accessible label for the left button. 31 | * Used by screen readers. 32 | */ 33 | ariaLabel: string; 34 | 35 | /** 36 | * The classes to add to the left button 37 | */ 38 | classes: {}; 39 | }; 40 | /** 41 | * Represents the configuration for the right button. 42 | */ 43 | rightButton: { 44 | 45 | /** 46 | * The value for the model to the right this model. 47 | */ 48 | value: number; 49 | 50 | /** 51 | * The accessible label for the right button. 52 | * Used by screen readers. 53 | */ 54 | ariaLabel: string; 55 | 56 | /** 57 | * The classes to add to the up button 58 | */ 59 | classes: {}; 60 | }; 61 | /** 62 | * Optional row labels. 63 | * Used to include the weekday labels in the `day` view 64 | */ 65 | rowLabels?: string[]; 66 | /** 67 | * The rows in the current view. 68 | */ 69 | rows: Array<{ 70 | /** 71 | * The cells in the current row. 72 | */ 73 | cells: Array 74 | }>; 75 | /** 76 | * Represent the configuration for the up button. 77 | */ 78 | upButton?: { 79 | 80 | /** 81 | * The value for the model above this model. 82 | */ 83 | value: number; 84 | 85 | /** 86 | * The accessible label for the up button. 87 | * Used by screen readers. 88 | */ 89 | ariaLabel: string; 90 | 91 | /** 92 | * The classes to add to the up button 93 | */ 94 | classes: {}; 95 | }; 96 | /** 97 | * The label displayed in the top-center of the date/time picker 98 | */ 99 | viewLabel: string; 100 | /** 101 | * The name of the view represented by this model. 102 | */ 103 | viewName: string; 104 | } 105 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-picker/dl-date-time-picker.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 11 | 12 |
13 | 14 | 22 |
23 |
24 |
25 |
{{label}}
27 |
28 |
29 |
{{cell.display}}
41 |
42 |
43 |
44 | 45 | 46 |
{{_model.viewLabel}}
47 |
48 | 49 | 58 | 59 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * IE11 requires the following for NgClass support on SVG elements 23 | */ 24 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 25 | 26 | /** 27 | * Web Animations `@angular/platform-browser/animations` 28 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 29 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 30 | */ 31 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 32 | 33 | /** 34 | * By default, zone.js will patch all possible macroTask and DomEvents 35 | * user can disable parts of macroTask/DomEvents patch by setting following flags 36 | * because those flags need to be set before `zone.js` being loaded, and webpack 37 | * will put import in the top of bundle, so user need to create a separate file 38 | * in this directory (for example: zone-flags.ts), and put the following flags 39 | * into that file, and then add the following code before importing zone.js. 40 | * import './zone-flags'; 41 | * 42 | * The flags allowed in zone-flags.ts are listed here. 43 | * 44 | * The following flags will work for all browsers. 45 | * 46 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 47 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 48 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 49 | * 50 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 51 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 52 | * 53 | * (window as any).__Zone_enable_cross_context_check = true; 54 | * 55 | */ 56 | 57 | /*************************************************************************************************** 58 | * Zone JS is required by default for Angular itself. 59 | */ 60 | import 'zone.js'; // Included with Angular CLI. 61 | 62 | 63 | /*************************************************************************************************** 64 | * APPLICATION IMPORTS 65 | */ 66 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 8 | maxView must be >= minView and startView 9 |
10 |
11 | 12 | 15 | minView must be <= maxView and startView 16 | minView determines when selection is triggered. 17 |
18 |
19 | 20 | 23 | startView must be between (or equal to) maxView and minView 24 |
25 |
26 | 27 | 55 | minuteStep has not effect if minView is not set to 'minute' 56 |
57 |
58 | 59 |
60 |
61 |       
62 |   <dl-date-time-picker
63 |     maxView="{{maxView}}"
64 |     minView="{{minView}}"
65 |     startView="{{startView}}"
66 |     minuteStep="{{minuteStep}}"
67 |     [(ngModel)]="selectedDate"
68 |   >
69 |   </dl-date-time-picker>
70 |     
71 |
72 |
73 |
74 | 81 |
82 |

Selected Date: {{selectedDate}}

83 |
84 |
85 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at dlotts@knightrider.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /cypress/po/date-time-picker.po.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is an example of how you can implement automated end-to-end tests for the 3 | * date/time picker component. 4 | * 5 | * The overall strategy here is to use the `dl-abdtp-value` attributes, which contain numeric date values, 6 | * to determine which buttons to click on the picker in order to select a target dates. 7 | */ 8 | 9 | const selectors = { 10 | currentViewAttributeName: 'data-dl-abdtp-view', 11 | changeViewButton: '.dl-abdtp-up-button', 12 | dateTimeButton: '.dl-abdtp-date-button', 13 | dateValueAttributeName: 'dl-abdtp-value', 14 | navigateLeftButton: '.dl-abdtp-left-button', 15 | navigateRightButton: '.dl-abdtp-right-button' 16 | } 17 | 18 | export const dateTimePickerPage = () => { 19 | 20 | /** 21 | * Navigates to the given date-picker target date selection view. 22 | * 23 | * @param targetView the date-picker view to navigate to 24 | */ 25 | const navigateToView = (targetView: string): void => { 26 | cy.get(`[${selectors.currentViewAttributeName}]`).then(el => { 27 | if (el.attr(selectors.currentViewAttributeName) !== targetView) { 28 | cy.get(selectors.changeViewButton).click(); 29 | navigateToView(targetView); 30 | } 31 | }); 32 | } 33 | 34 | /** 35 | * Navigates to the past dates until the first date button in the view contains the date-time value bigger than the given time. 36 | * 37 | * @param targetDateTime - the date-time to navigate to 38 | */ 39 | const navigateLeft = (targetDateTime: number): void => { 40 | cy.get(selectors.dateTimeButton).first().then(el => { 41 | if (Number(el.attr(selectors.dateValueAttributeName)) > targetDateTime) { 42 | cy.get(selectors.navigateLeftButton).click(); 43 | navigateLeft(targetDateTime); 44 | } 45 | }); 46 | } 47 | 48 | /** 49 | * Navigates to the future dates until the late date button in the view contains the date-time value smaller or equal to the given time. 50 | * 51 | * @param targetDateTime - the date-time to navigate to 52 | */ 53 | const navigateRight = (time: number): void => { 54 | cy.get(selectors.dateTimeButton).last().then(el => { 55 | if (Number(el.attr(selectors.dateValueAttributeName)) <= time) { 56 | cy.get(selectors.navigateRightButton).click(); 57 | navigateRight(time); 58 | } 59 | }); 60 | } 61 | 62 | /** 63 | * Picks the nearest date to the specified target date0time. Continues to select dates until the min target view is reached. 64 | * 65 | * @param minView - target min view 66 | * @param targetDateTime - the target date time to select 67 | */ 68 | const pickNearestDate = (minView: string, targetDateTime: number): void => { 69 | cy.get(`[${selectors.currentViewAttributeName}]`).then(el => { 70 | if (el.attr(selectors.currentViewAttributeName) !== minView) { 71 | clickNearestDateButtonInTheView(targetDateTime); 72 | pickNearestDate(minView, targetDateTime); 73 | } else { 74 | clickNearestDateButtonInTheView(targetDateTime); 75 | } 76 | }); 77 | } 78 | 79 | const clickNearestDateButtonInTheView = (targetDateTime: number) => { 80 | cy.get(selectors.dateTimeButton).then(els => { 81 | const buttons = els.toArray().filter(el => Number(el.getAttribute(selectors.dateValueAttributeName)) <= targetDateTime); 82 | buttons[buttons.length - 1].click(); 83 | }); 84 | } 85 | 86 | /** 87 | * Have the dateTimePicker select the best possible value that is less than or equal to the specified time based on the current configuration of the dateTimePicker. 88 | * 89 | * @param maxView - the max view to start the selection with 90 | * @param minView - the min view to end the selection with 91 | * @param targetDateTime - target date-time to select 92 | */ 93 | const pickTime = (maxView: string, minView: string, targetDateTime: number): void => { 94 | navigateToView(maxView); 95 | navigateLeft(targetDateTime); 96 | navigateRight(targetDateTime); 97 | pickNearestDate(minView, targetDateTime); 98 | } 99 | 100 | return { 101 | pickTime 102 | } 103 | }; 104 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-picker/specs/select-filter/select-filter.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | import {Component, DebugElement, ViewChild} from '@angular/core'; 11 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 12 | import {FormsModule} from '@angular/forms'; 13 | import {By} from '@angular/platform-browser'; 14 | import {DateButton, DlDateTimeNumberModule, DlDateTimePickerComponent, DlDateTimePickerModule} from '../../../public-api'; 15 | 16 | @Component({ 17 | template: '' 18 | }) 19 | class SelectFilterComponent { 20 | now = Date.now(); 21 | @ViewChild(DlDateTimePickerComponent, {static: false}) picker: DlDateTimePickerComponent; 22 | selectedDate: number; 23 | selectFilter = (dateButton: DateButton) => dateButton.value > this.now; 24 | } 25 | 26 | @Component({ 27 | template: '' 28 | }) 29 | class UndefinedSelectFilterComponent { 30 | @ViewChild(DlDateTimePickerComponent, {static: false}) picker: DlDateTimePickerComponent; 31 | selectFilter: (dateButton: DateButton, viewName: string) => boolean; // intentionally did not assign value 32 | selectedDate: number; // intentionally did not assign value 33 | } 34 | 35 | describe('DlDateTimePickerComponent startDate', () => { 36 | 37 | beforeEach(async () => { 38 | await TestBed.configureTestingModule({ 39 | imports: [ 40 | FormsModule, 41 | DlDateTimeNumberModule, 42 | DlDateTimePickerModule, 43 | ], 44 | declarations: [ 45 | SelectFilterComponent, 46 | UndefinedSelectFilterComponent, 47 | ] 48 | }).compileComponents(); 49 | }); 50 | 51 | describe('year', () => { 52 | let component: SelectFilterComponent; 53 | let fixture: ComponentFixture; 54 | let debugElement: DebugElement; 55 | 56 | beforeEach(async () => { 57 | fixture = TestBed.createComponent(SelectFilterComponent); 58 | fixture.detectChanges(); 59 | await fixture.whenStable().then(() => { 60 | fixture.detectChanges(); 61 | component = fixture.componentInstance; 62 | debugElement = fixture.debugElement; 63 | }); 64 | }); 65 | 66 | it('should disable .dl-abdtp-now element', () => { 67 | const nowElement = debugElement.query(By.css('.dl-abdtp-now')); 68 | expect(nowElement.nativeElement.classList).toContain('dl-abdtp-disabled'); 69 | expect(nowElement.attributes['aria-disabled']).toBe('true'); 70 | }); 71 | 72 | it('should ignore click on disabled element', () => { 73 | const changeSpy = jasmine.createSpy('change listener'); 74 | component.picker.change.subscribe(changeSpy); 75 | const disabledElement = debugElement.query(By.css('.dl-abdtp-disabled')); 76 | disabledElement.nativeElement.click(); 77 | expect(component.picker.value).toBeUndefined(); 78 | expect(changeSpy).not.toHaveBeenCalled(); 79 | }); 80 | }); 81 | 82 | describe('undefined', () => { 83 | let fixture: ComponentFixture; 84 | let debugElement: DebugElement; 85 | 86 | beforeEach(async () => { 87 | fixture = TestBed.createComponent(UndefinedSelectFilterComponent); 88 | fixture.detectChanges(); 89 | await fixture.whenStable().then(() => { 90 | fixture.detectChanges(); 91 | debugElement = fixture.debugElement; 92 | }); 93 | }); 94 | 95 | it('should not disable any elements', () => { 96 | const dateButtons = debugElement.queryAll(By.css('.dl-abdtp-date-button')); 97 | dateButtons.forEach((dateButton) => { 98 | expect(dateButton.nativeElement.classList).not.toContain('disabled'); 99 | }); 100 | }); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-picker/specs/start-date/start-date.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | import {Component, DebugElement, ViewChild} from '@angular/core'; 11 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 12 | import {FormsModule} from '@angular/forms'; 13 | import {By} from '@angular/platform-browser'; 14 | import {DlDateTimeNumberModule, DlDateTimePickerComponent, DlDateTimePickerModule} from '../../../public-api'; 15 | import {APR, MAY, OCT} from '../month-constants'; 16 | import moment from 'moment'; 17 | 18 | @Component({ 19 | template: '' 20 | }) 21 | class StartDateComponent { 22 | @ViewChild(DlDateTimePickerComponent, { static: false }) picker: DlDateTimePickerComponent; 23 | selectedDate: number; 24 | startDate = new Date(1985, OCT, 18).getTime(); 25 | } 26 | 27 | @Component({ 28 | template: '' 29 | }) 30 | class UndefinedStartDateComponent { 31 | @ViewChild(DlDateTimePickerComponent, { static: false }) picker: DlDateTimePickerComponent; 32 | selectedDate: number; // intentionally did not assign value 33 | startDate: number; // intentionally did not assign value 34 | } 35 | 36 | describe('DlDateTimePickerComponent startDate', () => { 37 | 38 | beforeEach(async () => { 39 | await TestBed.configureTestingModule({ 40 | imports: [ 41 | FormsModule, 42 | DlDateTimeNumberModule, 43 | DlDateTimePickerModule, 44 | ], 45 | declarations: [ 46 | StartDateComponent, 47 | UndefinedStartDateComponent, 48 | ] 49 | }).compileComponents(); 50 | }); 51 | 52 | describe('year', () => { 53 | let component: StartDateComponent; 54 | let fixture: ComponentFixture; 55 | let debugElement: DebugElement; 56 | 57 | beforeEach(async () => { 58 | fixture = TestBed.createComponent(StartDateComponent); 59 | fixture.detectChanges(); 60 | await fixture.whenStable().then(() => { 61 | fixture.detectChanges(); 62 | component = fixture.componentInstance; 63 | debugElement = fixture.debugElement; 64 | }); 65 | }); 66 | 67 | it('should start on the startDate', () => { 68 | expect(debugElement.query(By.css('.dl-abdtp-view-label')).nativeElement.textContent.trim()).toBe('Oct 1985'); 69 | }); 70 | 71 | it('should change to the selected date after value is set', () => { 72 | component.picker.value = new Date(1995, APR, 18).getTime(); 73 | fixture.detectChanges(); 74 | expect(debugElement.query(By.css('.dl-abdtp-view-label')).nativeElement.textContent.trim()).toBe('Apr 1995'); 75 | }); 76 | }); 77 | 78 | describe('undefined', () => { 79 | let component: UndefinedStartDateComponent; 80 | let fixture: ComponentFixture; 81 | let debugElement: DebugElement; 82 | 83 | beforeEach(async () => { 84 | fixture = TestBed.createComponent(UndefinedStartDateComponent); 85 | fixture.detectChanges(); 86 | await fixture.whenStable().then(() => { 87 | fixture.detectChanges(); 88 | component = fixture.componentInstance; 89 | debugElement = fixture.debugElement; 90 | }); 91 | }); 92 | 93 | it('should start the current date', () => { 94 | expect(debugElement.query(By.css('.dl-abdtp-view-label')).nativeElement.textContent.trim()).toBe(moment().format('MMM YYYY')); 95 | }); 96 | 97 | it('should change to the selected date after value is set', () => { 98 | component.picker.value = new Date(1966, MAY, 6).getTime(); 99 | fixture.detectChanges(); 100 | expect(debugElement.query(By.css('.dl-abdtp-view-label')).nativeElement.textContent.trim()).toBe('May 1966'); 101 | }); 102 | 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-bootstrap-datetimepicker", 3 | "version": "0.0.0-semantic-release", 4 | "engines": { 5 | "node": ">=14" 6 | }, 7 | "license": "MIT", 8 | "author": "https://github.com/dalelotts/angular-bootstrap-datetimepicker/graphs/contributors", 9 | "bugs": { 10 | "url": "https://github.com/dalelotts/angular-bootstrap-datetimepicker/issues" 11 | }, 12 | "homepage": "https://dalelotts.github.io/angular-bootstrap-datetimepicker", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/dalelotts/angular-bootstrap-datetimepicker.git" 16 | }, 17 | "keywords": [ 18 | "angular", 19 | "calendar", 20 | "component", 21 | "date picker", 22 | "date time picker", 23 | "date", 24 | "directive", 25 | "moment", 26 | "time picker", 27 | "time" 28 | ], 29 | "scripts": { 30 | "build": "ng build", 31 | "build:lib": "ng-packagr -p ng-package.json -c src/lib/tsconfig.lib.json && npm run build:styles", 32 | "build:styles": "scss-bundle --config scss-bundle.config.json", 33 | "coverage:upload": "cat build/coverage/lcov.info | coveralls", 34 | "document": "compodoc --disableInternal --disablePrivate --disableLifeCycleHooks --assetsFolder screenshots -p src/tsconfig.doc.json --gaID UA-325325-19 -n \"Angular Bootstrap Date/Time Picker\"", 35 | "lint": "ng lint", 36 | "ng": "ng", 37 | "start": "ng serve", 38 | "test": "ng lint && ng test --watch=false --code-coverage && npm run e2e && ng build && npm run build:lib", 39 | "test:tdd": "ng test", 40 | "e2e": "ng run angular-bootstrap-datetimepicker:cypress-run", 41 | "e2e:debug": "ng run angular-bootstrap-datetimepicker:cypress-open", 42 | "semantic-release": "semantic-release", 43 | "travis-deploy-once": "travis-deploy-once", 44 | "cypress:open": "cypress open" 45 | }, 46 | "peerDependencies": { 47 | "@angular/animations": ">=13.0.0", 48 | "@angular/common": ">=13.0.0", 49 | "@angular/compiler": ">=13.0.0", 50 | "@angular/core": ">=13.0.0", 51 | "@angular/forms": ">=13.0.0", 52 | "@angular/platform-browser": ">=13.0.0", 53 | "@angular/platform-browser-dynamic": ">=13.0.0", 54 | "@angular/router": ">=13.0.0", 55 | "moment": ">=2.0.0", 56 | "rxjs": ">=7.0.0", 57 | "zone.js": ">=0.11.4" 58 | }, 59 | "devDependencies": { 60 | "@angular-devkit/build-angular": "^13.2.5", 61 | "@angular-eslint/builder": "13.1.0", 62 | "@angular-eslint/eslint-plugin": "13.1.0", 63 | "@angular-eslint/eslint-plugin-template": "13.1.0", 64 | "@angular-eslint/schematics": "13.1.0", 65 | "@angular-eslint/template-parser": "13.1.0", 66 | "@angular/animations": "^13.2.0", 67 | "@angular/cli": "^13.2.5", 68 | "@angular/common": "^13.2.0", 69 | "@angular/compiler": "^13.2.0", 70 | "@angular/compiler-cli": "^13.2.0", 71 | "@angular/core": "^13.2.0", 72 | "@angular/forms": "^13.2.0", 73 | "@angular/language-service": "^13.2.0", 74 | "@angular/platform-browser": "^13.2.0", 75 | "@angular/platform-browser-dynamic": "^13.2.0", 76 | "@angular/router": "^13.2.0", 77 | "@compodoc/compodoc": "^1.1.11", 78 | "@cypress/schematic": "^2.4.0", 79 | "@types/jasmine": "~3.8.0", 80 | "@types/node": "^14.18.63", 81 | "@types/sass": "1.43.1", 82 | "@typescript-eslint/eslint-plugin": "5.11.0", 83 | "@typescript-eslint/parser": "5.11.0", 84 | "bootstrap": "^4.4.1", 85 | "core-js": "^2.6.12", 86 | "cypress": "^13.6.3", 87 | "eslint": "^8.2.0", 88 | "jasmine-core": "~3.8.0", 89 | "jasmine-spec-reporter": "^4.2.1", 90 | "jquery": "^3.4.1", 91 | "karma": "^6.3.12", 92 | "karma-chrome-launcher": "^3.1.0", 93 | "karma-coverage": "~2.1.0", 94 | "karma-jasmine": "^2.0.1", 95 | "karma-jasmine-html-reporter": "^1.5.2", 96 | "moment": "^2.24.0", 97 | "ng-packagr": "^13.3.1", 98 | "open-iconic": "^1.1.1", 99 | "popper.js": "^1.16.1", 100 | "rxjs": "^7.5.0", 101 | "scss-bundle": "^3.0.2", 102 | "semantic-release": "^23.0.8", 103 | "travis-deploy-once": "^5.0.11", 104 | "typescript": "~4.5.2", 105 | "zone.js": "~0.11.3" 106 | }, 107 | "release": { 108 | "pkgRoot": "dist" 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "angular-bootstrap-datetimepicker": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": { 12 | "@schematics/angular:component": { 13 | "styleext": "scss" 14 | } 15 | }, 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "outputPath": "dist/angular-bootstrap-datetimepicker", 21 | "index": "src/index.html", 22 | "main": "src/main.ts", 23 | "polyfills": "src/polyfills.ts", 24 | "tsConfig": "src/tsconfig.app.json", 25 | "aot": true, 26 | "assets": [ 27 | "src/favicon.ico", 28 | "src/assets" 29 | ], 30 | "styles": [ 31 | "src/styles.scss" 32 | ], 33 | "scripts": [] 34 | }, 35 | "configurations": { 36 | "production": { 37 | "fileReplacements": [ 38 | { 39 | "replace": "src/environments/environment.ts", 40 | "with": "src/environments/environment.prod.ts" 41 | } 42 | ], 43 | "budgets": [ 44 | { 45 | "type": "initial", 46 | "maximumWarning": "2mb", 47 | "maximumError": "5mb" 48 | } 49 | ], 50 | "outputHashing": "all" 51 | }, 52 | "development": { 53 | "buildOptimizer": false, 54 | "optimization": false, 55 | "vendorChunk": true, 56 | "extractLicenses": false, 57 | "sourceMap": true, 58 | "namedChunks": true 59 | } 60 | }, 61 | "defaultConfiguration": "production" 62 | }, 63 | "serve": { 64 | "builder": "@angular-devkit/build-angular:dev-server", 65 | "configurations": { 66 | "production": { 67 | "browserTarget": "angular-bootstrap-datetimepicker:build:production" 68 | }, 69 | "development": { 70 | "browserTarget": "angular-bootstrap-datetimepicker:build:development" 71 | } 72 | }, 73 | "defaultConfiguration": "development" 74 | }, 75 | "extract-i18n": { 76 | "builder": "@angular-devkit/build-angular:extract-i18n", 77 | "options": { 78 | "browserTarget": "angular-bootstrap-datetimepicker:build" 79 | } 80 | }, 81 | "test": { 82 | "builder": "@angular-devkit/build-angular:karma", 83 | "options": { 84 | "main": "src/test.ts", 85 | "polyfills": "src/polyfills.ts", 86 | "tsConfig": "src/tsconfig.spec.json", 87 | "karmaConfig": "src/karma.conf.js", 88 | "styles": [ 89 | "src/styles.scss" 90 | ], 91 | "scripts": [], 92 | "assets": [ 93 | "src/favicon.ico", 94 | "src/assets" 95 | ] 96 | } 97 | }, 98 | "lint": { 99 | "builder": "@angular-eslint/builder:lint", 100 | "options": { 101 | "lintFilePatterns": [ 102 | "src/**/*.ts", 103 | "src/**/*.html" 104 | ] 105 | } 106 | }, 107 | "cypress-run": { 108 | "builder": "@cypress/schematic:cypress", 109 | "options": { 110 | "devServerTarget": "angular-bootstrap-datetimepicker:serve" 111 | }, 112 | "configurations": { 113 | "production": { 114 | "devServerTarget": "angular-bootstrap-datetimepicker:serve:production" 115 | } 116 | } 117 | }, 118 | "cypress-open": { 119 | "builder": "@cypress/schematic:cypress", 120 | "options": { 121 | "devServerTarget": "angular-bootstrap-datetimepicker:serve", 122 | "watch": true, 123 | "headless": false 124 | } 125 | }, 126 | "e2e": { 127 | "builder": "@cypress/schematic:cypress", 128 | "options": { 129 | "devServerTarget": "angular-bootstrap-datetimepicker:serve", 130 | "watch": true, 131 | "headless": false 132 | }, 133 | "configurations": { 134 | "production": { 135 | "devServerTarget": "angular-bootstrap-datetimepicker:serve:production" 136 | } 137 | } 138 | } 139 | } 140 | } 141 | }, 142 | "defaultProject": "angular-bootstrap-datetimepicker" 143 | } 144 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-picker/specs/minute-step/minute-step.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | import {Component, ViewChild} from '@angular/core'; 11 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 12 | import {FormsModule} from '@angular/forms'; 13 | import {By} from '@angular/platform-browser'; 14 | import {DlDateTimeNumberModule, DlDateTimePickerComponent, DlDateTimePickerModule} from '../../../public-api'; 15 | 16 | @Component({ 17 | template: '' 18 | }) 19 | class MinuteStepComponent { 20 | minuteStep = 1; 21 | @ViewChild(DlDateTimePickerComponent, {static: false}) picker: DlDateTimePickerComponent; 22 | } 23 | 24 | @Component({ 25 | template: '' 26 | }) 27 | class UndefinedMinuteStepComponent { 28 | minuteStep: number; // intentionally did not assign value 29 | @ViewChild(DlDateTimePickerComponent, {static: false}) picker: DlDateTimePickerComponent; 30 | } 31 | 32 | describe('DlDateTimePickerComponent minuteStep', () => { 33 | 34 | beforeEach(async () => { 35 | await TestBed.configureTestingModule({ 36 | imports: [ 37 | FormsModule, 38 | DlDateTimeNumberModule, 39 | DlDateTimePickerModule, 40 | ], 41 | declarations: [ 42 | MinuteStepComponent, 43 | UndefinedMinuteStepComponent, 44 | ] 45 | } 46 | ).compileComponents(); 47 | }); 48 | 49 | describe('defined', () => { 50 | let component: MinuteStepComponent; 51 | let fixture: ComponentFixture; 52 | 53 | beforeEach(async () => { 54 | fixture = TestBed.createComponent(MinuteStepComponent); 55 | fixture.detectChanges(); 56 | await fixture.whenStable().then(() => { 57 | fixture.detectChanges(); 58 | component = fixture.componentInstance; 59 | }); 60 | }); 61 | 62 | it('should step one minute as configured', () => { 63 | const minuteElements = fixture.debugElement.queryAll(By.css('.dl-abdtp-minute')); 64 | expect(minuteElements.length).toBe(60); 65 | expect(minuteElements[0].nativeElement.textContent).toContain(':00'); 66 | expect(minuteElements[59].nativeElement.textContent).toContain(':59'); 67 | }); 68 | 69 | it('should step 5-minutes if set to undefined', () => { 70 | component.minuteStep = undefined; 71 | fixture.detectChanges(); 72 | 73 | const minuteElements = fixture.debugElement.queryAll(By.css('.dl-abdtp-minute')); 74 | expect(minuteElements.length).toBe(12); 75 | expect(minuteElements[0].nativeElement.textContent).toContain(':00'); 76 | expect(minuteElements[11].nativeElement.textContent).toContain(':55'); 77 | }); 78 | 79 | it('should step 5-minutes if set to null', () => { 80 | component.minuteStep = null; 81 | fixture.detectChanges(); 82 | 83 | const minuteElements = fixture.debugElement.queryAll(By.css('.dl-abdtp-minute')); 84 | expect(minuteElements.length).toBe(12); 85 | expect(minuteElements[0].nativeElement.textContent).toContain(':00'); 86 | expect(minuteElements[11].nativeElement.textContent).toContain(':55'); 87 | }); 88 | 89 | it('should change to 15-minute step after config change', () => { 90 | component.minuteStep = 15; 91 | fixture.detectChanges(); 92 | 93 | const minuteElements = fixture.debugElement.queryAll(By.css('.dl-abdtp-minute')); 94 | expect(minuteElements.length).toBe(4); 95 | expect(minuteElements[0].nativeElement.textContent).toContain(':00'); 96 | expect(minuteElements[3].nativeElement.textContent).toContain(':45'); 97 | }); 98 | 99 | it('should render all minute step values between 1 and 59', () => { 100 | const stepValues = new Array(59) 101 | .fill(1) 102 | .map((one, index) => index + one); 103 | 104 | stepValues.forEach((minuteStep) => { 105 | component.minuteStep = minuteStep; 106 | fixture.detectChanges(); 107 | 108 | const expectedMinuteElements = Math.ceil(60 / minuteStep); 109 | const minuteElements = fixture.debugElement.queryAll(By.css('.dl-abdtp-minute')); 110 | expect(minuteElements.length).toBe(expectedMinuteElements); 111 | expect(minuteElements[0].nativeElement.textContent).toContain(':00'); 112 | }); 113 | }); 114 | }); 115 | 116 | describe('undefined', () => { 117 | let fixture: ComponentFixture; 118 | 119 | beforeEach(async () => { 120 | fixture = TestBed.createComponent(UndefinedMinuteStepComponent); 121 | fixture.detectChanges(); 122 | await fixture.whenStable().then(() => { 123 | fixture.detectChanges(); 124 | }); 125 | }); 126 | 127 | it('should default to 5-minute step', () => { 128 | const minuteElements = fixture.debugElement.queryAll(By.css('.dl-abdtp-minute')); 129 | expect(minuteElements.length).toBe(12); 130 | expect(minuteElements[0].nativeElement.textContent).toContain(':00'); 131 | expect(minuteElements[11].nativeElement.textContent).toContain(':55'); 132 | }); 133 | }); 134 | }); 135 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-picker/specs/button-classes/button-classes.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | import {Component, ViewChild} from '@angular/core'; 11 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 12 | import {FormsModule} from '@angular/forms'; 13 | import {By} from '@angular/platform-browser'; 14 | import {DlDateTimeNumberModule} from '../../../public-api'; 15 | import {DlDateTimePickerComponent} from '../../dl-date-time-picker.component'; 16 | import {DlDateTimePickerModule} from '../../dl-date-time-picker.module'; 17 | 18 | @Component({ 19 | template: '' 20 | }) 21 | class DefaultButtonClassComponent { 22 | @ViewChild(DlDateTimePickerComponent, {static: false}) picker: DlDateTimePickerComponent; 23 | } 24 | 25 | @Component({ 26 | template: ` 27 | ` 32 | }) 33 | class ConfigurableButtonClassComponent { 34 | leftIconClass: string | string[] | Set | {}; 35 | @ViewChild(DlDateTimePickerComponent, {static: false}) picker: DlDateTimePickerComponent; 36 | rightIconClass: string | string[] | Set | {}; 37 | upIconClass: string | string[] | Set | {}; 38 | } 39 | 40 | describe('DlDateTimePickerComponent button-classes', () => { 41 | 42 | beforeEach(async () => { 43 | await TestBed.configureTestingModule({ 44 | imports: [ 45 | FormsModule, 46 | DlDateTimeNumberModule, 47 | DlDateTimePickerModule 48 | ], 49 | declarations: [ 50 | DefaultButtonClassComponent, 51 | ConfigurableButtonClassComponent, 52 | ], 53 | }).compileComponents(); 54 | }); 55 | 56 | describe('default', () => { 57 | let fixture: ComponentFixture; 58 | 59 | beforeEach(async () => { 60 | fixture = TestBed.createComponent(DefaultButtonClassComponent); 61 | fixture.detectChanges(); 62 | await fixture.whenStable().then(() => { 63 | fixture.detectChanges(); 64 | }); 65 | }); 66 | 67 | it('should have left button with open iconic chevron left', () => { 68 | const leftButton = fixture.debugElement.query(By.css('.left-icon')); 69 | expect(leftButton.nativeElement.classList).toContain('oi'); 70 | expect(leftButton.nativeElement.classList).toContain('oi-chevron-left'); 71 | }); 72 | 73 | it('should have right button with open iconic chevron right', () => { 74 | const leftButton = fixture.debugElement.query(By.css('.right-icon')); 75 | expect(leftButton.nativeElement.classList).toContain('oi'); 76 | expect(leftButton.nativeElement.classList).toContain('oi-chevron-right'); 77 | }); 78 | 79 | it('should have up button with open iconic chevron top', () => { 80 | const leftButton = fixture.debugElement.query(By.css('.up-icon')); 81 | expect(leftButton.nativeElement.classList).toContain('oi'); 82 | expect(leftButton.nativeElement.classList).toContain('oi-chevron-top'); 83 | }); 84 | }); 85 | 86 | describe('custom icons', () => { 87 | let component: ConfigurableButtonClassComponent; 88 | let fixture: ComponentFixture; 89 | 90 | beforeEach(async () => { 91 | fixture = TestBed.createComponent(ConfigurableButtonClassComponent); 92 | fixture.detectChanges(); 93 | await fixture.whenStable().then(() => { 94 | fixture.detectChanges(); 95 | component = fixture.componentInstance; 96 | }); 97 | }); 98 | 99 | it('should omit left icon classes if undefined', () => { 100 | const leftButton = fixture.debugElement.query(By.css('.left-icon')); 101 | expect(leftButton.nativeElement.classList).not.toContain('octicon'); 102 | expect(leftButton.nativeElement.classList).not.toContain('octicon-chevron-left'); 103 | }); 104 | 105 | it('should omit right icon classes if undefined', () => { 106 | const leftButton = fixture.debugElement.query(By.css('.right-icon')); 107 | expect(leftButton.nativeElement.classList).not.toContain('octicon'); 108 | expect(leftButton.nativeElement.classList).not.toContain('octicon-chevron-right'); 109 | }); 110 | 111 | it('should omit up icon classes if undefined', () => { 112 | const leftButton = fixture.debugElement.query(By.css('.up-icon')); 113 | expect(leftButton.nativeElement.classList).not.toContain('octicon'); 114 | expect(leftButton.nativeElement.classList).not.toContain('octicon-chevron-top'); 115 | }); 116 | 117 | it('should override left button icon class', () => { 118 | component.leftIconClass = ['octicon', 'octicon-chevron-left']; 119 | fixture.detectChanges(); 120 | 121 | const leftButton = fixture.debugElement.query(By.css('.left-icon')); 122 | expect(leftButton.nativeElement.classList).toContain('octicon'); 123 | expect(leftButton.nativeElement.classList).toContain('octicon-chevron-left'); 124 | }); 125 | 126 | it('should override right button class', () => { 127 | component.rightIconClass = ['octicon', 'octicon-chevron-right']; 128 | fixture.detectChanges(); 129 | 130 | const leftButton = fixture.debugElement.query(By.css('.right-icon')); 131 | expect(leftButton.nativeElement.classList).toContain('octicon'); 132 | expect(leftButton.nativeElement.classList).toContain('octicon-chevron-right'); 133 | }); 134 | 135 | it('should override up button class', () => { 136 | component.upIconClass = ['octicon', 'octicon-chevron-top']; 137 | fixture.detectChanges(); 138 | 139 | const leftButton = fixture.debugElement.query(By.css('.up-icon')); 140 | expect(leftButton.nativeElement.classList).toContain('octicon'); 141 | expect(leftButton.nativeElement.classList).toContain('octicon-chevron-top'); 142 | }); 143 | }); 144 | }); 145 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-input/dl-date-time-input.directive.ts: -------------------------------------------------------------------------------- 1 | import {Directive, ElementRef, EventEmitter, HostListener, Inject, Input, Output, Renderer2} from '@angular/core'; 2 | import { 3 | AbstractControl, 4 | ControlValueAccessor, 5 | NG_VALIDATORS, 6 | NG_VALUE_ACCESSOR, 7 | ValidationErrors, 8 | Validator, 9 | ValidatorFn, 10 | Validators, 11 | } from '@angular/forms'; 12 | import moment from 'moment'; 13 | import {DL_DATE_TIME_DISPLAY_FORMAT, DL_DATE_TIME_INPUT_FORMATS, DlDateAdapter} from '../core/public-api'; 14 | import {DlDateTimeInputChange} from './dl-date-time-input-change'; 15 | 16 | /** 17 | * This directive allows the user to enter dates, using the keyboard, into an input box and 18 | * angular will then store a date value in the model. 19 | * 20 | * The input format(s), display format, and model format are independent and fully customizable. 21 | */ 22 | @Directive({ 23 | selector: 'input[dlDateTimeInput]', 24 | providers: [ 25 | {provide: NG_VALUE_ACCESSOR, useExisting: DlDateTimeInputDirective, multi: true}, 26 | {provide: NG_VALIDATORS, useExisting: DlDateTimeInputDirective, multi: true} 27 | ] 28 | }) 29 | export class DlDateTimeInputDirective implements ControlValueAccessor, Validator { 30 | 31 | /* tslint:disable:member-ordering */ 32 | private _filterValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => { 33 | return (this._inputFilter || (() => true))(this._value) ? 34 | null : {'dlDateTimeInputFilter': {'value': control.value}}; 35 | } 36 | private _inputFilter: (value: (D | null)) => boolean = () => true; 37 | private _isValid = true; 38 | private _parseValidator: ValidatorFn = (): ValidationErrors | null => { 39 | return this._isValid ? 40 | null : {'dlDateTimeInputParse': {'text': this._elementRef.nativeElement.value}}; 41 | } 42 | private _changed: ((value: D) => void)[] = []; 43 | private _touched: (() => void)[] = []; 44 | private _validator = Validators.compose([this._parseValidator, this._filterValidator]); 45 | private _onValidatorChange: () => void = () => {}; 46 | private _value: D | undefined = undefined; 47 | 48 | /** 49 | * Emits when a `change` event when date/time is selected or 50 | * the value of the date/time picker changes. 51 | **/ 52 | @Output() 53 | readonly dateChange = new EventEmitter>(); 54 | 55 | /** 56 | * Constructs a new instance of this directive. 57 | * @param _renderer 58 | * reference to the renderer. 59 | * @param _elementRef 60 | * reference to this element. 61 | * @param _dateAdapter 62 | * date adapter for the date type in the model. 63 | * @param _displayFormat 64 | * from `DL_DATE_TIME_DISPLAY_FORMAT`, which defines the format to use for a valid date/time value. 65 | * @param _inputFormats 66 | * from `DL_DATE_TIME_INPUT_FORMATS`, which defines the input formats that allowed as valid date/time values. 67 | * NB: moment is always in strict parse mode for this directive. 68 | */ 69 | constructor( 70 | private _renderer: Renderer2, 71 | private _elementRef: ElementRef, 72 | private _dateAdapter: DlDateAdapter, 73 | @Inject(DL_DATE_TIME_DISPLAY_FORMAT) private readonly _displayFormat: string, 74 | @Inject(DL_DATE_TIME_INPUT_FORMATS) private readonly _inputFormats: string[] 75 | ) {} 76 | 77 | /** 78 | * Set a function used to determine whether or not the `value` entered by the user is allowed. 79 | * @param inputFilterFunction 80 | * a function that returns `true` if the `value` entered by the user is allowed, otherwise `false`. 81 | */ 82 | @Input() 83 | set dlDateTimeInputFilter(inputFilterFunction: (value: D | null) => boolean) { 84 | this._inputFilter = inputFilterFunction || (() => true); 85 | this._onValidatorChange(); 86 | } 87 | 88 | /* tslint:enable:member-ordering */ 89 | 90 | /** 91 | * Returns `D` value of the date/time input or `undefined` | `null` if no value is set. 92 | **/ 93 | get value(): D { 94 | return this._value; 95 | } 96 | 97 | /** 98 | * Set the value of the date/time input to a value of `D` | `undefined` | `null`; 99 | * @param newValue 100 | * the new value of the date/time input 101 | */ 102 | 103 | set value(newValue: D | null | undefined) { 104 | if (newValue !== this._value) { 105 | this._value = newValue; 106 | this._changed.forEach(onChanged => onChanged(this._value)); 107 | } 108 | } 109 | 110 | /** 111 | * Emit a `change` event when the value of the input changes. 112 | */ 113 | @HostListener('change') _onChange() { 114 | this.dateChange.emit(new DlDateTimeInputChange(this._value)); 115 | } 116 | 117 | /** 118 | * Format the input text using {@link DL_DATE_TIME_DISPLAY_FORMAT} and mark the control as touched. 119 | */ 120 | @HostListener('blur') _onBlur() { 121 | if (this._value) { 122 | this._setElementValue(this._value); 123 | } 124 | this._touched.forEach(onTouched => onTouched()); 125 | } 126 | 127 | /** 128 | * Parse the user input into a possibly valid date. 129 | * The model value is not set if the input is NOT one of the {@link DL_DATE_TIME_INPUT_FORMATS}. 130 | * @param value 131 | * Value of the input control. 132 | */ 133 | @HostListener('input', ['$event.target.value']) _onInput(value: string | null | undefined): void { 134 | const testDate = value === null || value === undefined || value === '' 135 | ? undefined 136 | : moment(value, this._inputFormats, true); 137 | 138 | this._isValid = testDate && testDate.isValid(); 139 | this.value = this._isValid ? this._dateAdapter.fromMilliseconds(testDate.valueOf()) : undefined; 140 | } 141 | 142 | /** 143 | * @internal 144 | */ 145 | private _setElementValue(value: D) { 146 | if (value !== null && value !== undefined) { 147 | this._renderer.setProperty(this._elementRef.nativeElement, 'value', moment(value).format(this._displayFormat)); 148 | } 149 | } 150 | 151 | /** 152 | * @internal 153 | */ 154 | registerOnChange(onChange: (value: any) => void): void { 155 | this._changed.push(onChange); 156 | } 157 | 158 | /** 159 | * @internal 160 | */ 161 | registerOnTouched(onTouched: () => void): void { 162 | this._touched.push(onTouched); 163 | } 164 | 165 | /** 166 | * @internal 167 | */ 168 | registerOnValidatorChange(validatorOnChange: () => void): void { 169 | this._onValidatorChange = validatorOnChange; 170 | } 171 | 172 | /** 173 | * @internal 174 | */ 175 | setDisabledState(isDisabled: boolean): void { 176 | this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled); 177 | } 178 | 179 | /** 180 | * @internal 181 | */ 182 | validate(control: AbstractControl): ValidationErrors | null { 183 | return this._validator(control); 184 | } 185 | 186 | /** 187 | * @internal 188 | */ 189 | writeValue(value: D): void { 190 | this._isValid = true; 191 | this.value = value; 192 | this._setElementValue(value); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-picker/specs/ng-model/ng-model.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | import {Component, ViewChild} from '@angular/core'; 11 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 12 | import {FormsModule} from '@angular/forms'; 13 | import {By} from '@angular/platform-browser'; 14 | import {DlDateTimeNumberModule, DlDateTimePickerComponent, DlDateTimePickerModule} from '../../../public-api'; 15 | import {dispatchKeyboardEvent, ENTER, SPACE} from '../dispatch-events'; 16 | import {JAN} from '../month-constants'; 17 | import moment from 'moment'; 18 | 19 | @Component({ 20 | template: '' 21 | }) 22 | class YearSelectorComponent { 23 | @ViewChild(DlDateTimePickerComponent, {static: false}) picker: DlDateTimePickerComponent; 24 | selectedDate: number; 25 | } 26 | 27 | describe('DlDateTimePickerComponent', () => { 28 | 29 | beforeEach(async () => { 30 | await TestBed.configureTestingModule({ 31 | imports: [ 32 | FormsModule, 33 | DlDateTimeNumberModule, 34 | DlDateTimePickerModule, 35 | ], 36 | declarations: [ 37 | YearSelectorComponent 38 | ] 39 | }).compileComponents(); 40 | }); 41 | 42 | describe('startView=year', () => { 43 | let component: YearSelectorComponent; 44 | let fixture: ComponentFixture; 45 | 46 | beforeEach(() => { 47 | fixture = TestBed.createComponent(YearSelectorComponent); 48 | component = fixture.componentInstance; 49 | fixture.detectChanges(); 50 | }); 51 | 52 | it('should be touched when clicking .dl-abdtp-left-button', () => { 53 | // ng-untouched/ng-touched requires ngModel 54 | const pickerElement = fixture.debugElement.query(By.css('dl-date-time-picker')).nativeElement; 55 | expect(pickerElement.classList).toContain('ng-untouched'); 56 | 57 | const leftButton = fixture.debugElement.query(By.css('.dl-abdtp-left-button')); 58 | leftButton.nativeElement.click(); 59 | fixture.detectChanges(); 60 | 61 | expect(pickerElement.classList).toContain('ng-touched'); 62 | }); 63 | 64 | it('should be touched when clicking .dl-abdtp-right-button', () => { 65 | // ng-untouched/ng-touched requires ngModel 66 | const pickerElement = fixture.debugElement.query(By.css('dl-date-time-picker')).nativeElement; 67 | expect(pickerElement.classList).toContain('ng-untouched'); 68 | 69 | const leftButton = fixture.debugElement.query(By.css('.dl-abdtp-right-button')); 70 | leftButton.nativeElement.click(); 71 | fixture.detectChanges(); 72 | 73 | expect(pickerElement.classList).toContain('ng-touched'); 74 | }); 75 | 76 | it('should be touched when clicking .dl-abdtp-year', () => { 77 | // ng-untouched/ng-touched requires ngModel 78 | const pickerElement = fixture.debugElement.query(By.css('dl-date-time-picker')).nativeElement; 79 | expect(pickerElement.classList).toContain('ng-untouched'); 80 | 81 | const yearElement = fixture.debugElement.query(By.css('.dl-abdtp-year')); 82 | yearElement.nativeElement.click(); 83 | fixture.detectChanges(); 84 | 85 | expect(pickerElement.classList).toContain('ng-touched'); 86 | }); 87 | 88 | it('should be dirty when clicking .dl-abdtp-year', () => { 89 | const pickerElement = fixture.debugElement.query(By.css('dl-date-time-picker')).nativeElement; 90 | expect(pickerElement.classList).toContain('ng-untouched'); 91 | expect(pickerElement.classList).toContain('ng-pristine'); 92 | 93 | const yearElement = fixture.debugElement.query(By.css('.dl-abdtp-year')); 94 | yearElement.nativeElement.click(); 95 | fixture.detectChanges(); 96 | 97 | expect(pickerElement.classList).toContain('ng-touched'); 98 | expect(pickerElement.classList).toContain('ng-dirty'); 99 | }); 100 | 101 | it('should store the value in ngModel when clicking a .dl-abdtp-year', () => { 102 | const startYear = (Math.trunc(moment().year() / 10) * 10); 103 | const yearElements = fixture.debugElement.queryAll(By.css('.dl-abdtp-year')); 104 | yearElements[9].nativeElement.click(); // 2019-01-01 105 | fixture.detectChanges(); 106 | 107 | expect(component.selectedDate).toBe(new Date(startYear + 9, JAN, 1).getTime()); 108 | }); 109 | 110 | it('should store the value internally when clicking a .dl-abdtp-year', function () { 111 | const startYear = (Math.trunc(moment().year() / 10) * 10); 112 | const changeSpy = jasmine.createSpy('change listener'); 113 | component.picker.change.subscribe(changeSpy); 114 | 115 | const yearElements = fixture.debugElement.queryAll(By.css('.dl-abdtp-year')); 116 | yearElements[8].nativeElement.click(); // 2018-01-01 117 | fixture.detectChanges(); 118 | 119 | const expected = new Date(startYear + 8, JAN, 1).getTime(); 120 | expect(component.picker.value).toBe(expected); 121 | expect(changeSpy).toHaveBeenCalled(); 122 | expect(changeSpy.calls.first().args[0].value).toBe(expected); 123 | }); 124 | 125 | it('should store the value in ngModel when hitting ENTER', () => { 126 | const changeSpy = jasmine.createSpy('change listener'); 127 | component.picker.change.subscribe(changeSpy); 128 | 129 | expect(component.picker.value).toBeNull(); 130 | 131 | const activeElement = fixture.debugElement.query(By.css('.dl-abdtp-active')); 132 | 133 | dispatchKeyboardEvent(activeElement.nativeElement, 'keydown', ENTER); 134 | fixture.detectChanges(); 135 | 136 | expect(component.picker.value).not.toBeNull(); 137 | expect(component.picker.value).not.toBeUndefined(); 138 | expect(changeSpy).toHaveBeenCalled(); 139 | expect(changeSpy.calls.first().args[0].value).toBe(component.picker.value); 140 | expect(component.selectedDate).toBe(component.picker.value); 141 | }); 142 | 143 | it('should store the value in ngModel when hitting SPACE', () => { 144 | const changeSpy = jasmine.createSpy('change listener'); 145 | component.picker.change.subscribe(changeSpy); 146 | 147 | expect(component.picker.value).toBeNull(); 148 | 149 | const activeElement = fixture.debugElement.query(By.css('.dl-abdtp-active')); 150 | 151 | dispatchKeyboardEvent(activeElement.nativeElement, 'keydown', SPACE); 152 | fixture.detectChanges(); 153 | 154 | expect(component.picker.value).not.toBeNull(); 155 | expect(component.picker.value).not.toBeUndefined(); 156 | expect(changeSpy).toHaveBeenCalled(); 157 | expect(changeSpy.calls.first().args[0].value).toBe(component.picker.value); 158 | expect(component.selectedDate).toBe(component.picker.value); 159 | }); 160 | 161 | it('should not emit change event if value does not change', () => { 162 | const changeSpy = jasmine.createSpy('change listener'); 163 | component.picker.change.subscribe(changeSpy); 164 | 165 | component.picker.value = 1293840000000; 166 | fixture.detectChanges(); 167 | 168 | component.picker.value = 1293840000000; 169 | fixture.detectChanges(); 170 | 171 | expect(component.picker.value).toBe(1293840000000); 172 | expect(changeSpy).toHaveBeenCalledTimes(1); 173 | expect(changeSpy.calls.first().args[0].value).toBe(1293840000000); 174 | expect(component.selectedDate).toBe(1293840000000); 175 | }); 176 | }); 177 | // ng-pristine, ng-touched - when should these change? 178 | }); 179 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-picker/specs/max-view/max-view.spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | import {Component, ViewChild} from '@angular/core'; 11 | import {ComponentFixture, TestBed} from '@angular/core/testing'; 12 | import {FormsModule} from '@angular/forms'; 13 | import {By} from '@angular/platform-browser'; 14 | import {DlDateTimeNumberModule, DlDateTimePickerComponent, DlDateTimePickerModule} from '../../../public-api'; 15 | 16 | @Component({ 17 | template: '' 18 | }) 19 | class YearMaxViewComponent { 20 | @ViewChild(DlDateTimePickerComponent, {static: false}) picker: DlDateTimePickerComponent; 21 | selectedDate: number; 22 | } 23 | 24 | @Component({ 25 | template: '' 26 | }) 27 | class MonthMaxViewComponent { 28 | @ViewChild(DlDateTimePickerComponent, {static: false}) picker: DlDateTimePickerComponent; 29 | selectedDate: number; 30 | } 31 | 32 | @Component({ 33 | template: '' 34 | }) 35 | class DayMaxViewComponent { 36 | @ViewChild(DlDateTimePickerComponent, {static: false}) picker: DlDateTimePickerComponent; 37 | selectedDate: number; 38 | } 39 | 40 | @Component({ 41 | template: '' 42 | }) 43 | class HourMaxViewComponent { 44 | @ViewChild(DlDateTimePickerComponent, {static: false}) picker: DlDateTimePickerComponent; 45 | selectedDate: number; 46 | } 47 | 48 | @Component({ 49 | template: '' 50 | }) 51 | class MinuteMaxViewComponent { 52 | @ViewChild(DlDateTimePickerComponent, {static: false}) picker: DlDateTimePickerComponent; 53 | selectedDate: number; 54 | } 55 | 56 | @Component({ 57 | template: '' 58 | }) 59 | class UndefinedMaxViewComponent { 60 | maxView: string; // intentionally did not assign value 61 | @ViewChild(DlDateTimePickerComponent, {static: false}) picker: DlDateTimePickerComponent; 62 | selectedDate: number; 63 | } 64 | 65 | describe('DlDateTimePickerComponent maxView', () => { 66 | 67 | beforeEach(async () => { 68 | await TestBed.configureTestingModule({ 69 | imports: [ 70 | FormsModule, 71 | DlDateTimeNumberModule, 72 | DlDateTimePickerModule 73 | ], 74 | declarations: [ 75 | YearMaxViewComponent, 76 | MonthMaxViewComponent, 77 | DayMaxViewComponent, 78 | HourMaxViewComponent, 79 | MinuteMaxViewComponent, 80 | UndefinedMaxViewComponent, 81 | ] 82 | }).compileComponents(); 83 | }); 84 | 85 | describe('year', () => { 86 | let fixture: ComponentFixture; 87 | 88 | beforeEach(async () => { 89 | fixture = TestBed.createComponent(YearMaxViewComponent); 90 | fixture.detectChanges(); 91 | await fixture.whenStable().then(() => { 92 | fixture.detectChanges(); 93 | }); 94 | }); 95 | 96 | it('should start with day-view', () => { 97 | const dayView = fixture.debugElement.query(By.css('.dl-abdtp-day-view')); 98 | expect(dayView).toBeTruthy(); 99 | }); 100 | 101 | it('should not have an .dl-abdtp-up-button', () => { 102 | fixture.debugElement.query(By.css('.dl-abdtp-up-button')).nativeElement.click(); // to go month-view 103 | fixture.detectChanges(); 104 | 105 | fixture.debugElement.query(By.css('.dl-abdtp-up-button')).nativeElement.click(); // go to year-view 106 | fixture.detectChanges(); 107 | 108 | const upButton = fixture.debugElement.query(By.css('.dl-abdtp-up-button')); 109 | expect(upButton).toBeFalsy(); 110 | }); 111 | }); 112 | 113 | describe('month', () => { 114 | let fixture: ComponentFixture; 115 | 116 | beforeEach(async () => { 117 | fixture = TestBed.createComponent(MonthMaxViewComponent); 118 | fixture.detectChanges(); 119 | await fixture.whenStable().then(() => { 120 | fixture.detectChanges(); 121 | }); 122 | }); 123 | 124 | it('should start with day-view', () => { 125 | const dayView = fixture.debugElement.query(By.css('.dl-abdtp-day-view')); 126 | expect(dayView).toBeTruthy(); 127 | }); 128 | 129 | it('should not have an .dl-abdtp-up-button', () => { 130 | fixture.debugElement.query(By.css('.dl-abdtp-up-button')).nativeElement.click(); // go to month-view 131 | fixture.detectChanges(); 132 | const upButton = fixture.debugElement.query(By.css('.dl-abdtp-up-button')); 133 | expect(upButton).toBeFalsy(); 134 | }); 135 | }); 136 | 137 | describe('day', () => { 138 | let fixture: ComponentFixture; 139 | 140 | beforeEach(async () => { 141 | fixture = TestBed.createComponent(DayMaxViewComponent); 142 | fixture.detectChanges(); 143 | await fixture.whenStable().then(() => { 144 | fixture.detectChanges(); 145 | }); 146 | }); 147 | 148 | it('should start with day-view', () => { 149 | const dayView = fixture.debugElement.query(By.css('.dl-abdtp-day-view')); 150 | expect(dayView).toBeTruthy(); 151 | }); 152 | 153 | it('should not have an .dl-abdtp-up-button', () => { 154 | const upButton = fixture.debugElement.query(By.css('.dl-abdtp-up-button')); 155 | expect(upButton).toBeFalsy(); 156 | }); 157 | }); 158 | 159 | describe('hour', () => { 160 | let fixture: ComponentFixture; 161 | 162 | beforeEach(async () => { 163 | fixture = TestBed.createComponent(HourMaxViewComponent); 164 | fixture.detectChanges(); 165 | await fixture.whenStable().then(() => { 166 | fixture.detectChanges(); 167 | }); 168 | }); 169 | 170 | it('should start with hour-view', () => { 171 | const hourView = fixture.debugElement.query(By.css('.dl-abdtp-hour-view')); 172 | expect(hourView).toBeTruthy(); 173 | }); 174 | 175 | it('should not have an .dl-abdtp-up-button', () => { 176 | const upButton = fixture.debugElement.query(By.css('.dl-abdtp-up-button')); 177 | expect(upButton).toBeFalsy(); 178 | }); 179 | }); 180 | 181 | describe('minute', () => { 182 | let fixture: ComponentFixture; 183 | 184 | beforeEach(async () => { 185 | fixture = TestBed.createComponent(MinuteMaxViewComponent); 186 | fixture.detectChanges(); 187 | await fixture.whenStable().then(() => { 188 | fixture.detectChanges(); 189 | }); 190 | }); 191 | 192 | it('should start with minute-view', () => { 193 | const minuteView = fixture.debugElement.query(By.css('.dl-abdtp-minute-view')); 194 | expect(minuteView).toBeTruthy(); 195 | }); 196 | 197 | it('should not have an .dl-abdtp-up-button', () => { 198 | const upButton = fixture.debugElement.query(By.css('.dl-abdtp-up-button')); 199 | expect(upButton).toBeFalsy(); 200 | }); 201 | }); 202 | 203 | describe('undefined', () => { 204 | let fixture: ComponentFixture; 205 | 206 | beforeEach(async () => { 207 | fixture = TestBed.createComponent(UndefinedMaxViewComponent); 208 | fixture.detectChanges(); 209 | await fixture.whenStable().then(() => { 210 | fixture.detectChanges(); 211 | }); 212 | }); 213 | 214 | it('should start with day-view', () => { 215 | const dayView = fixture.debugElement.query(By.css('.dl-abdtp-day-view')); 216 | expect(dayView).toBeTruthy(); 217 | }); 218 | 219 | it('should not have an .dl-abdtp-up-button', () => { 220 | fixture.debugElement.query(By.css('.dl-abdtp-up-button')).nativeElement.click(); // to go month-view 221 | fixture.detectChanges(); 222 | 223 | fixture.debugElement.query(By.css('.dl-abdtp-up-button')).nativeElement.click(); // go to year-view 224 | fixture.detectChanges(); 225 | 226 | const upButton = fixture.debugElement.query(By.css('.dl-abdtp-up-button')); 227 | expect(upButton).toBeFalsy(); 228 | }); 229 | }); 230 | }); 231 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-picker/dl-model-provider.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | import {SimpleChanges} from '@angular/core'; 11 | import {DlDateTimePickerModel} from './dl-date-time-picker-model'; 12 | 13 | /** 14 | * Implemented by classes that provide models for the date/time picker. 15 | * 16 | * The terms `left`, `right`, `up`, and `down` are used in place of 17 | * `previous`, `next`, etc so that the model provider is UI centric 18 | * rather than time direction centric. 19 | * 20 | * For example, another calendar implementation may render `future` dates to the `left`. 21 | * 22 | * Having the api method `goLeft` may move the `active` cell to a 23 | * future or past value depending on the calendar implementation, 24 | * but both operations are performed with the `left-arrow` key. 25 | */ 26 | export interface DlModelProvider { 27 | 28 | /** 29 | * Receives configuration changes detected by Angular. (i.e. minuteStep) 30 | * 31 | * @param changes 32 | * the changes detected by Angular. 33 | */ 34 | onChanges(changes: SimpleChanges): void; 35 | 36 | /** 37 | * Returns the model for the specified moment in time. 38 | * @param milliseconds 39 | * the moment in time the model should represent. 40 | * @param selectedMilliseconds 41 | * the current value of the date/time picker. 42 | * @returns 43 | * the model representing the specified moment in time. 44 | */ 45 | getModel(milliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel; 46 | 47 | /** 48 | * Move the `active` cell one cell down from the specified moment in time. 49 | * 50 | * The next cell `down` might be in a different time range than the currently 51 | * displayed view. In this case, the model time range will include the new active cell. 52 | * 53 | * What happens is determined entirely by the implementation and it 54 | * varies from view-to-view. 55 | * 56 | * This is used for keyboard navigation. 57 | * 58 | * @param fromMilliseconds 59 | * the moment in time from which the next model `down` will be constructed. 60 | * @param selectedMilliseconds 61 | * the current value of the date/time picker 62 | * @returns 63 | * the model representing the next model `down` from the specified moment in time. 64 | */ 65 | goDown(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel; 66 | 67 | /** 68 | * Move the `active` cell to the `last` cell from the specified moment in time. 69 | * 70 | * Typically the view or time range will not change. However, changes to the view 71 | * or time rage are not specifically prohibited. 72 | * 73 | * What happens is determined entirely by the implementation and it 74 | * varies from view-to-view. 75 | * 76 | * This is used for keyboard navigation. 77 | * 78 | * @param fromMilliseconds 79 | * the moment in time from which the `last` active cell will be calculated. 80 | * @param selectedMilliseconds 81 | * the current value of the date/time picker 82 | * @returns 83 | * a model with the `last` cell in the view as the active cell. 84 | */ 85 | goEnd(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel; 86 | 87 | /** 88 | * Move the `active` cell to the `first` cell from the specified moment in time. 89 | * 90 | * Typically the view or time range will not change. However, changes to the view 91 | * or time rage are not specifically prohibited. 92 | * 93 | * What happens is determined entirely by the implementation and it 94 | * varies from view-to-view. 95 | * 96 | * This is used for keyboard navigation. 97 | * 98 | * @param fromMilliseconds 99 | * the moment in time from which the `first` active cell will be calculated. 100 | * @param selectedMilliseconds 101 | * the current value of the date/time picker 102 | * @returns 103 | * a model with the `first` cell in the view as the active cell. 104 | */ 105 | goHome(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel; 106 | 107 | /** 108 | * Move the `active` cell one cell to the `left` from the specified moment in time. 109 | * 110 | * The next cell `left` might be in a different time range than the currently 111 | * displayed view. In this case, the model time range will include the new active cell. 112 | * 113 | * What happens is determined entirely by the implementation and it 114 | * varies from view-to-view. 115 | * 116 | * This is used for keyboard navigation. 117 | * 118 | * @param fromMilliseconds 119 | * the moment in time from which the next model `left` will be constructed. 120 | * @param selectedMilliseconds 121 | * the current value of the date/time picker 122 | * @returns 123 | * the model representing the next model `left` from the specified moment in time. 124 | */ 125 | goLeft(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel; 126 | 127 | /** 128 | * Move the `active` cell one cell to the `right` from the specified moment in time. 129 | * 130 | * The next cell `right` might be in a different time range than the currently 131 | * displayed view. In this case, the model time range will include the new active cell. 132 | * 133 | * What happens is determined entirely by the implementation and it 134 | * varies from view-to-view. 135 | * 136 | * This is used for keyboard navigation. 137 | * 138 | * @param fromMilliseconds 139 | * the moment in time from which the next model `right` will be constructed. 140 | * @param selectedMilliseconds 141 | * the current value of the date/time picker 142 | * @returns 143 | * the model representing the next model `right` from the specified moment in time. 144 | */ 145 | goRight(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel; 146 | 147 | /** 148 | * Move the `active` cell one cell `up` from the specified moment in time. 149 | * 150 | * The next cell `up` might be in a different time range than the currently 151 | * displayed view. In this case, the model time range will include the new active cell. 152 | * 153 | * What happens is determined entirely by the implementation and it 154 | * varies from view-to-view. 155 | * 156 | * This is used for keyboard navigation. 157 | * 158 | * @param fromMilliseconds 159 | * the moment in time from which the next model `up` will be constructed. 160 | * @param selectedMilliseconds 161 | * the current value of the date/time picker 162 | * @returns 163 | * the model representing the next model `up` from the specified moment in time. 164 | */ 165 | goUp(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel; 166 | 167 | /** 168 | * Move the `active` cell one `page-down` from the specified moment in time. 169 | * 170 | * The next cell `page-down` will be in a different time range than the currently 171 | * displayed view and the model time range will include the new active cell. 172 | * 173 | * What happens is determined entirely by the implementation and it 174 | * varies from view-to-view. 175 | * 176 | * This is used for keyboard navigation. 177 | * 178 | * @param fromMilliseconds 179 | * the moment in time from which the next model `page-down` will be constructed. 180 | * @param selectedMilliseconds 181 | * the current value of the date/time picker 182 | * @returns 183 | * the model representing the next model `page-down` from the specified moment in time. 184 | */ 185 | pageDown(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel; 186 | 187 | /** 188 | * Move the `active` cell one `page-up` from the specified moment in time. 189 | * 190 | * The next cell `page-up` will be in a different time range than the currently 191 | * displayed view and the model time range will include the new active cell. 192 | * 193 | * What happens is determined entirely by the implementation and it 194 | * varies from view-to-view. 195 | * 196 | * This is used for keyboard navigation. 197 | * 198 | * @param fromMilliseconds 199 | * the moment in time from which the next model `page-up` will be constructed. 200 | * @param selectedMilliseconds 201 | * the current value of the date/time picker 202 | * @returns 203 | * the model representing the next model `page-up` from the specified moment in time. 204 | */ 205 | pageUp(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel; 206 | } 207 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-picker/dl-model-provider-hour.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | import {SimpleChanges} from '@angular/core'; 11 | import moment from 'moment'; 12 | import {DlDateTimePickerModel} from './dl-date-time-picker-model'; 13 | import {DlModelProvider} from './dl-model-provider'; 14 | 15 | /** 16 | * Default implementation for the `hour` view. 17 | */ 18 | export class DlHourModelProvider implements DlModelProvider { 19 | 20 | /** 21 | * Receives input changes detected by Angular. 22 | * 23 | * @param changes 24 | * the input changes detected by Angular. 25 | */ 26 | onChanges( 27 | _changes: SimpleChanges 28 | ): void {} 29 | 30 | 31 | /** 32 | * Returns the `hour` model for the specified moment in `local` time with the 33 | * `active` hour set to the beginning of the day. 34 | * 35 | * The `hour` model represents a day (24 hours) as six rows with four columns 36 | * and each cell representing one-hour increments. 37 | * 38 | * The hour always starts at the beginning of the hour. 39 | * 40 | * Each cell represents a one-hour increment starting at midnight. 41 | * 42 | * @param milliseconds 43 | * the moment in time from which the minute model will be created. 44 | * @param selectedMilliseconds 45 | * the current value of the date/time picker. 46 | * @returns 47 | * the model representing the specified moment in time. 48 | */ 49 | getModel(milliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 50 | const startDate = moment(milliseconds).startOf('day'); 51 | 52 | const rowNumbers = [0, 1, 2, 3, 4, 5]; 53 | const columnNumbers = [0, 1, 2, 3]; 54 | 55 | const previousDay = moment(startDate).subtract(1, 'day'); 56 | const nextDay = moment(startDate).add(1, 'day'); 57 | const activeValue = moment(milliseconds).startOf('hour').valueOf(); 58 | const selectedValue = selectedMilliseconds === null || selectedMilliseconds === undefined 59 | ? selectedMilliseconds 60 | : moment(selectedMilliseconds).startOf('hour').valueOf(); 61 | 62 | return { 63 | viewName: 'hour', 64 | viewLabel: startDate.format('ll'), 65 | activeDate: activeValue, 66 | leftButton: { 67 | value: previousDay.valueOf(), 68 | ariaLabel: `Go to ${previousDay.format('ll')}`, 69 | classes: {}, 70 | }, 71 | upButton: { 72 | value: startDate.valueOf(), 73 | ariaLabel: `Go to ${startDate.format('MMM YYYY')}`, 74 | classes: {}, 75 | }, 76 | rightButton: { 77 | value: nextDay.valueOf(), 78 | ariaLabel: `Go to ${nextDay.format('ll')}`, 79 | classes: {}, 80 | }, 81 | rows: rowNumbers.map(rowOfHours) 82 | }; 83 | 84 | function rowOfHours(rowNumber) { 85 | 86 | const currentMoment = moment(); 87 | const cells = columnNumbers.map((columnNumber) => { 88 | const hourMoment = moment(startDate).add((rowNumber * columnNumbers.length) + columnNumber, 'hours'); 89 | return { 90 | display: hourMoment.format('LT'), 91 | ariaLabel: hourMoment.format('LLL'), 92 | value: hourMoment.valueOf(), 93 | classes: { 94 | 'dl-abdtp-active': activeValue === hourMoment.valueOf(), 95 | 'dl-abdtp-selected': selectedValue === hourMoment.valueOf(), 96 | 'dl-abdtp-now': hourMoment.isSame(currentMoment, 'hour'), 97 | } 98 | }; 99 | }); 100 | return {cells}; 101 | } 102 | } 103 | 104 | /** 105 | * Move the active `hour` one row `down` from the specified moment in time. 106 | * 107 | * Moving `down` can result in the `active` hour being part of a different day than 108 | * the specified `fromMilliseconds`, in this case the day represented by the model 109 | * will change to show the correct hour. 110 | * 111 | * @param fromMilliseconds 112 | * the moment in time from which the next `hour` model `down` will be constructed. 113 | * @param selectedMilliseconds 114 | * the current value of the date/time picker. 115 | * @returns 116 | * model containing an `active` `hour` one row `down` from the specified moment in time. 117 | */ 118 | goDown(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 119 | return this.getModel(moment(fromMilliseconds).add(4, 'hour').valueOf(), selectedMilliseconds); 120 | } 121 | 122 | /** 123 | * Move the active `hour` one row `up` from the specified moment in time. 124 | * 125 | * Moving `up` can result in the `active` hour being part of a different day than 126 | * the specified `fromMilliseconds`, in this case the day represented by the model 127 | * will change to show the correct hour. 128 | * 129 | * @param fromMilliseconds 130 | * the moment in time from which the next `hour` model `up` will be constructed. 131 | * @param selectedMilliseconds 132 | * the current value of the date/time picker. 133 | * @returns 134 | * model containing an `active` `hour` one row `up` from the specified moment in time. 135 | */ 136 | goUp(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 137 | return this.getModel(moment(fromMilliseconds).subtract(4, 'hour').valueOf(), selectedMilliseconds); 138 | } 139 | 140 | /** 141 | * Move the `active` hour one cell `left` in the current `hour` view. 142 | * 143 | * Moving `left` can result in the `active` hour being part of a different day than 144 | * the specified `fromMilliseconds`, in this case the day represented by the model 145 | * will change to show the correct year. 146 | * 147 | * @param fromMilliseconds 148 | * the moment in time from which the `hour` model to the `left` will be constructed. 149 | * @param selectedMilliseconds 150 | * the current value of the date/time picker. 151 | * @returns 152 | * model containing an `active` `hour` one cell to the `left` of the specified moment in time. 153 | */ 154 | goLeft(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 155 | return this.getModel(moment(fromMilliseconds).subtract(1, 'hour').valueOf(), selectedMilliseconds); 156 | } 157 | 158 | /** 159 | * Move the `active` hour one cell `right` in the current `hour` view. 160 | * 161 | * Moving `right` can result in the `active` hour being part of a different day than 162 | * the specified `fromMilliseconds`, in this case the day represented by the model 163 | * will change to show the correct year. 164 | * 165 | * @param fromMilliseconds 166 | * the moment in time from which the `hour` model to the `right` will be constructed. 167 | * @param selectedMilliseconds 168 | * the current value of the date/time picker. 169 | * @returns 170 | * model containing an `active` `hour` one cell to the `right` of the specified moment in time. 171 | */ 172 | goRight(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 173 | return this.getModel(moment(fromMilliseconds).add(1, 'hour').valueOf(), selectedMilliseconds); 174 | } 175 | 176 | /** 177 | * Move the active `hour` one day `down` from the specified moment in time. 178 | * 179 | * Paging `down` will result in the `active` hour being part of a different day than 180 | * the specified `fromMilliseconds`. As a result, the day represented by the model 181 | * will change to show the correct year. 182 | * 183 | * @param fromMilliseconds 184 | * the moment in time from which the next `hour` model page `down` will be constructed. 185 | * @param selectedMilliseconds 186 | * the current value of the date/time picker. 187 | * @returns 188 | * model containing an `active` `hour` one day `down` from the specified moment in time. 189 | */ 190 | pageDown(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 191 | return this.getModel(moment(fromMilliseconds).add(1, 'day').valueOf(), selectedMilliseconds); 192 | } 193 | 194 | /** 195 | * Move the active `hour` one day `up` from the specified moment in time. 196 | * 197 | * Paging `up` will result in the `active` hour being part of a different day than 198 | * the specified `fromMilliseconds`. As a result, the day represented by the model 199 | * will change to show the correct year. 200 | * 201 | * @param fromMilliseconds 202 | * the moment in time from which the next `hour` model page `up` will be constructed. 203 | * @param selectedMilliseconds 204 | * the current value of the date/time picker. 205 | * @returns 206 | * model containing an `active` `hour` one day `up` from the specified moment in time. 207 | */ 208 | pageUp(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 209 | return this.getModel(moment(fromMilliseconds).subtract(1, 'day').valueOf(), selectedMilliseconds); 210 | } 211 | 212 | /** 213 | * Move the `active` `hour` to `11:00 pm` of the current day. 214 | * 215 | * The view or time range will not change unless the `fromMilliseconds` value 216 | * is in a different day than the displayed decade. 217 | * 218 | * @param fromMilliseconds 219 | * the moment in time from which `11:00 pm` will be calculated. 220 | * @param selectedMilliseconds 221 | * the current value of the date/time picker. 222 | * @returns 223 | * a model with the `11:00 pm` cell in the view as the active `hour`. 224 | */ 225 | goEnd(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 226 | return this.getModel(moment 227 | (fromMilliseconds) 228 | .endOf('day') 229 | .startOf('hour') 230 | .valueOf(), selectedMilliseconds); 231 | } 232 | 233 | /** 234 | * Move the `active` `hour` to `midnight` of the current day. 235 | * 236 | * The view or time range will not change unless the `fromMilliseconds` value 237 | * is in a different day than the displayed decade. 238 | * 239 | * @param fromMilliseconds 240 | * the moment in time from which `midnight` will be calculated. 241 | * @param selectedMilliseconds 242 | * the current value of the date/time picker. 243 | * @returns 244 | * a model with the `midnight` cell in the view as the active `hour`. 245 | */ 246 | goHome(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 247 | return this.getModel(moment(fromMilliseconds).startOf('day').valueOf(), selectedMilliseconds); 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-picker/dl-model-provider-month.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | import {SimpleChanges} from '@angular/core'; 11 | import moment from 'moment'; 12 | import {DlDateTimePickerModel} from './dl-date-time-picker-model'; 13 | import {DlModelProvider} from './dl-model-provider'; 14 | 15 | /** 16 | * Default implementation for the `month` view. 17 | */ 18 | export class DlMonthModelProvider implements DlModelProvider { 19 | 20 | /** 21 | * Receives input changes detected by Angular. 22 | * 23 | * @param changes 24 | * the input changes detected by Angular. 25 | */ 26 | onChanges( 27 | _changes: SimpleChanges 28 | ): void {} 29 | 30 | /** 31 | * Returns the `month` model for the specified moment in `local` time with the 32 | * `active` month set to the first day of the specified month. 33 | * 34 | * The `month` model represents a year (12 months) as three rows with four columns. 35 | * 36 | * The year always starts in January. 37 | * 38 | * Each cell represents midnight on the 1st day of the month. 39 | * 40 | * The `active` month will be the January of year of the specified milliseconds. 41 | * 42 | * @param milliseconds 43 | * the moment in time from which the month model will be created. 44 | * @param selectedMilliseconds 45 | * the current value of the date/time picker. 46 | * @returns 47 | * the model representing the specified moment in time. 48 | */ 49 | getModel(milliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 50 | const startDate = moment(milliseconds).startOf('year'); 51 | 52 | const rowNumbers = [0, 1, 2]; 53 | const columnNumbers = [0, 1, 2, 3]; 54 | 55 | const previousYear = moment(startDate).subtract(1, 'year'); 56 | const nextYear = moment(startDate).add(1, 'year'); 57 | const activeValue = moment(milliseconds).startOf('month').valueOf(); 58 | const selectedValue = selectedMilliseconds === null || selectedMilliseconds === undefined 59 | ? selectedMilliseconds 60 | : moment(selectedMilliseconds).startOf('month').valueOf(); 61 | 62 | return { 63 | viewName: 'month', 64 | viewLabel: startDate.format('YYYY'), 65 | activeDate: activeValue, 66 | leftButton: { 67 | value: previousYear.valueOf(), 68 | ariaLabel: `Go to ${previousYear.format('YYYY')}`, 69 | classes: {}, 70 | }, 71 | upButton: { 72 | value: startDate.valueOf(), 73 | ariaLabel: `Go to ${startDate.format('YYYY')}`, 74 | classes: {}, 75 | }, 76 | rightButton: { 77 | value: nextYear.valueOf(), 78 | ariaLabel: `Go to ${nextYear.format('YYYY')}`, 79 | classes: {}, 80 | }, 81 | rows: rowNumbers.map(rowOfMonths) 82 | }; 83 | 84 | function rowOfMonths(rowNumber) { 85 | 86 | const currentMoment = moment(); 87 | const cells = columnNumbers.map((columnNumber) => { 88 | const monthMoment = moment(startDate).add((rowNumber * columnNumbers.length) + columnNumber, 'months'); 89 | return { 90 | display: monthMoment.format('MMM'), 91 | ariaLabel: monthMoment.format('MMM YYYY'), 92 | value: monthMoment.valueOf(), 93 | classes: { 94 | 'dl-abdtp-active': activeValue === monthMoment.valueOf(), 95 | 'dl-abdtp-selected': selectedValue === monthMoment.valueOf(), 96 | 'dl-abdtp-now': monthMoment.isSame(currentMoment, 'month'), 97 | } 98 | }; 99 | }); 100 | return {cells}; 101 | } 102 | } 103 | 104 | /** 105 | * Move the active `month` one row `down` from the specified moment in time. 106 | * 107 | * Moving `down` can result in the `active` month being part of a different year than 108 | * the specified `fromMilliseconds`, in this case the year represented by the model 109 | * will change to show the correct year. 110 | * 111 | * @param fromMilliseconds 112 | * the moment in time from which the next `month` model `down` will be constructed. 113 | * @param selectedMilliseconds 114 | * the current value of the date/time picker. 115 | * @returns 116 | * model containing an `active` `month` one row `down` from the specified moment in time. 117 | */ 118 | goDown(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 119 | return this.getModel(moment(fromMilliseconds).add(4, 'month').valueOf(), selectedMilliseconds); 120 | } 121 | 122 | /** 123 | * Move the active `month` one row `up` from the specified moment in time. 124 | * 125 | * Moving `up` can result in the `active` month being part of a different year than 126 | * the specified `fromMilliseconds`, in this case the year represented by the model 127 | * will change to show the correct year. 128 | * 129 | * @param fromMilliseconds 130 | * the moment in time from which the previous `month` model `up` will be constructed. 131 | * @param selectedMilliseconds 132 | * the current value of the date/time picker. 133 | * @returns 134 | * model containing an `active` `month` one row `up` from the specified moment in time. 135 | */ 136 | goUp(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 137 | return this.getModel(moment(fromMilliseconds).subtract(4, 'month').valueOf(), selectedMilliseconds); 138 | } 139 | 140 | /** 141 | * Move the `active` `month` one (1) month to the `left` of the specified moment in time. 142 | * 143 | * Moving `left` can result in the `active` month being part of a different year than 144 | * the specified `fromMilliseconds`, in this case the year represented by the model 145 | * will change to show the correct year. 146 | * 147 | * @param fromMilliseconds 148 | * the moment in time from which the `month` model to the `left` will be constructed. 149 | * @param selectedMilliseconds 150 | * the current value of the date/time picker. 151 | * @returns 152 | * model containing an `active` `month` one month to the `left` of the specified moment in time. 153 | */ 154 | goLeft(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 155 | return this.getModel(moment(fromMilliseconds).subtract(1, 'month').valueOf(), selectedMilliseconds); 156 | } 157 | 158 | /** 159 | * Move the `active` `month` one (1) month to the `right` of the specified moment in time. 160 | * 161 | * The `active` month will be `one (1) month after` the specified milliseconds. 162 | * This moves the `active` date one month `right` in the current `month` view. 163 | * 164 | * Moving `right` can result in the `active` month being part of a different year than 165 | * the specified `fromMilliseconds`, in this case the year represented by the model 166 | * will change to show the correct year. 167 | * 168 | * @param fromMilliseconds 169 | * the moment in time from which the `month` model to the `right` will be constructed. 170 | * @param selectedMilliseconds 171 | * the current value of the date/time picker. 172 | * @returns 173 | * model containing an `active` `month` one year to the `right` of the specified moment in time. 174 | */ 175 | goRight(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 176 | return this.getModel(moment(fromMilliseconds).add(1, 'month').valueOf(), selectedMilliseconds); 177 | } 178 | 179 | /** 180 | * Move the active `month` one year `down` from the specified moment in time. 181 | * 182 | * Paging `down` will result in the `active` month being part of a different year than 183 | * the specified `fromMilliseconds`. As a result, the year represented by the model 184 | * will change to show the correct year. 185 | * 186 | * @param fromMilliseconds 187 | * the moment in time from which the next `month` model page `down` will be constructed. 188 | * @param selectedMilliseconds 189 | * the current value of the date/time picker. 190 | * @returns 191 | * model containing an `active` `month` one year `down` from the specified moment in time. 192 | */ 193 | pageDown(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 194 | return this.getModel(moment(fromMilliseconds).add(12, 'months').valueOf(), selectedMilliseconds); 195 | } 196 | 197 | /** 198 | * Move the active `month` one year `down` from the specified moment in time. 199 | * 200 | * Paging `up` will result in the `active` month being part of a different year than 201 | * the specified `fromMilliseconds`. As a result, the year represented by the model 202 | * will change to show the correct year. 203 | * 204 | * @param fromMilliseconds 205 | * the moment in time from which the next `month` model page `up` will be constructed. 206 | * @param selectedMilliseconds 207 | * the current value of the date/time picker. 208 | * @returns 209 | * model containing an `active` `month` one year `up` from the specified moment in time. 210 | */ 211 | pageUp(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 212 | return this.getModel(moment(fromMilliseconds).subtract(12, 'months').valueOf(), selectedMilliseconds); 213 | } 214 | 215 | /** 216 | * Move the `active` `month` to `December` of the current year. 217 | * 218 | * The view or time range will not change unless the `fromMilliseconds` value 219 | * is in a different year than the displayed decade. 220 | * 221 | * @param fromMilliseconds 222 | * the moment in time from which `December 1` will be calculated. 223 | * @param selectedMilliseconds 224 | * the current value of the date/time picker. 225 | * @returns 226 | * a model with the `December` cell in the view as the active `month`. 227 | */ 228 | goEnd(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 229 | return this.getModel(moment(fromMilliseconds).endOf('year').valueOf(), selectedMilliseconds); 230 | } 231 | 232 | /** 233 | * Move the `active` `month` to `January` of the current year. 234 | * 235 | * The view or time range will not change unless the `fromMilliseconds` value 236 | * is in a different year than the displayed decade. 237 | * 238 | * @param fromMilliseconds 239 | * the moment in time from which `January 1` will be calculated. 240 | * @param selectedMilliseconds 241 | * the current value of the date/time picker. 242 | * @returns 243 | * a model with the `January` cell in the view as the active `month`. 244 | */ 245 | goHome(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 246 | return this.getModel(moment(fromMilliseconds).startOf('year').valueOf(), selectedMilliseconds); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-picker/dl-model-provider-day.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2013-present Dale Lotts All Rights Reserved. 4 | * http://www.dalelotts.com 5 | * 6 | * Use of this source code is governed by an MIT-style license that can be 7 | * found in the LICENSE file at https://github.com/dalelotts/angular-bootstrap-datetimepicker/blob/master/LICENSE 8 | */ 9 | 10 | import {SimpleChanges} from '@angular/core'; 11 | import moment from 'moment'; 12 | import {DlDateTimePickerModel} from './dl-date-time-picker-model'; 13 | import {DlModelProvider} from './dl-model-provider'; 14 | 15 | /** 16 | * Default implementation for the `day` view. 17 | */ 18 | export class DlDayModelProvider implements DlModelProvider { 19 | 20 | /** 21 | * Receives input changes detected by Angular. 22 | * 23 | * @param changes 24 | * the input changes detected by Angular. 25 | */ 26 | onChanges( 27 | _changes: SimpleChanges 28 | ): void {} 29 | 30 | /** 31 | * Returns the `day` model for the specified moment in `local` time with the 32 | * `active` day set to the first day of the month. 33 | * 34 | * The `day` model represents a month (42 days) as six rows with seven columns 35 | * and each cell representing one-day increments. 36 | * 37 | * The `day` always starts at midnight. 38 | * 39 | * Each cell represents a one-day increment at midnight. 40 | * 41 | * @param milliseconds 42 | * the moment in time from which the minute model will be created. 43 | * @param selectedMilliseconds 44 | * the current value of the date/time picker. 45 | * @returns 46 | * the model representing the specified moment in time. 47 | */ 48 | getModel(milliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 49 | 50 | const startOfMonth = moment(milliseconds).startOf('month'); 51 | const endOfMonth = moment(milliseconds).endOf('month'); 52 | const startOfView = moment(startOfMonth).subtract(Math.abs(startOfMonth.weekday()), 'days'); 53 | 54 | const rowNumbers = [0, 1, 2, 3, 4, 5]; 55 | const columnNumbers = [0, 1, 2, 3, 4, 5, 6]; 56 | 57 | const previousMonth = moment(startOfMonth).subtract(1, 'month'); 58 | const nextMonth = moment(startOfMonth).add(1, 'month'); 59 | const activeValue = moment(milliseconds).startOf('day').valueOf(); 60 | const selectedValue = selectedMilliseconds === null || selectedMilliseconds === undefined 61 | ? selectedMilliseconds 62 | : moment(selectedMilliseconds).startOf('day').valueOf(); 63 | 64 | return { 65 | viewName: 'day', 66 | viewLabel: startOfMonth.format('MMM YYYY'), 67 | activeDate: activeValue, 68 | leftButton: { 69 | value: previousMonth.valueOf(), 70 | ariaLabel: `Go to ${previousMonth.format('MMM YYYY')}`, 71 | classes: {}, 72 | }, 73 | upButton: { 74 | value: startOfMonth.valueOf(), 75 | ariaLabel: `Go to month view`, 76 | classes: {}, 77 | }, 78 | rightButton: { 79 | value: nextMonth.valueOf(), 80 | ariaLabel: `Go to ${nextMonth.format('MMM YYYY')}`, 81 | classes: {}, 82 | }, 83 | rowLabels: columnNumbers.map((column) => moment().weekday(column).format('dd')), 84 | rows: rowNumbers.map(rowOfDays) 85 | }; 86 | 87 | function rowOfDays(rowNumber) { 88 | const currentMoment = moment(); 89 | const cells = columnNumbers.map((columnNumber) => { 90 | const dayMoment = moment(startOfView).add((rowNumber * columnNumbers.length) + columnNumber, 'days'); 91 | return { 92 | display: dayMoment.format('D'), 93 | ariaLabel: dayMoment.format('ll'), 94 | value: dayMoment.valueOf(), 95 | classes: { 96 | 'dl-abdtp-active': activeValue === dayMoment.valueOf(), 97 | 'dl-abdtp-future': dayMoment.isAfter(endOfMonth), 98 | 'dl-abdtp-past': dayMoment.isBefore(startOfMonth), 99 | 'dl-abdtp-selected': selectedValue === dayMoment.valueOf(), 100 | 'dl-abdtp-now': dayMoment.isSame(currentMoment, 'day'), 101 | } 102 | }; 103 | }); 104 | return {cells}; 105 | } 106 | } 107 | 108 | /** 109 | * Move the active `day` one row `down` from the specified moment in time. 110 | * 111 | * Moving `down` can result in the `active` day being part of a different month than 112 | * the specified `fromMilliseconds`, in this case the month represented by the model 113 | * will change to show the correct hour. 114 | * 115 | * @param fromMilliseconds 116 | * the moment in time from which the next `day` model `down` will be constructed. 117 | * @param selectedMilliseconds 118 | * the current value of the date/time picker. 119 | * @returns 120 | * model containing an `active` `day` one row `down` from the specified moment in time. 121 | */ 122 | goDown(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 123 | return this.getModel(moment(fromMilliseconds).add(7, 'days').valueOf(), selectedMilliseconds); 124 | } 125 | 126 | /** 127 | * Move the active `day` one row `up` from the specified moment in time. 128 | * 129 | * Moving `up` can result in the `active` day being part of a different month than 130 | * the specified `fromMilliseconds`, in this case the month represented by the model 131 | * will change to show the correct hour. 132 | * 133 | * @param fromMilliseconds 134 | * the moment in time from which the next `day` model `up` will be constructed. 135 | * @param selectedMilliseconds 136 | * the current value of the date/time picker. 137 | * @returns 138 | * model containing an `active` `day` one row `up` from the specified moment in time. 139 | */ 140 | goUp(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 141 | return this.getModel(moment(fromMilliseconds).subtract(7, 'days').valueOf(), selectedMilliseconds); 142 | } 143 | 144 | /** 145 | * Move the `active` day one cell `left` in the current `day` view. 146 | * 147 | * Moving `left` can result in the `active` day being part of a different month than 148 | * the specified `fromMilliseconds`, in this case the month represented by the model 149 | * will change to show the correct year. 150 | * 151 | * @param fromMilliseconds 152 | * the moment in time from which the `day` model to the `left` will be constructed. 153 | * @param selectedMilliseconds 154 | * the current value of the date/time picker. 155 | * @returns 156 | * model containing an `active` `day` one cell to the `left` of the specified moment in time. 157 | */ 158 | goLeft(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 159 | return this.getModel(moment(fromMilliseconds).subtract(1, 'day').valueOf(), selectedMilliseconds); 160 | } 161 | 162 | /** 163 | * Move the `active` day one cell `right` in the current `day` view. 164 | * 165 | * Moving `right` can result in the `active` day being part of a different month than 166 | * the specified `fromMilliseconds`, in this case the month represented by the model 167 | * will change to show the correct year. 168 | * 169 | * @param fromMilliseconds 170 | * the moment in time from which the `day` model to the `right` will be constructed. 171 | * @param selectedMilliseconds 172 | * the current value of the date/time picker. 173 | * @returns 174 | * model containing an `active` `day` one cell to the `right` of the specified moment in time. 175 | */ 176 | goRight(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 177 | return this.getModel(moment(fromMilliseconds).add(1, 'day').valueOf(), selectedMilliseconds); 178 | } 179 | 180 | /** 181 | * Move the active `day` one month `down` from the specified moment in time. 182 | * 183 | * Paging `down` will result in the `active` day being part of a different month than 184 | * the specified `fromMilliseconds`. As a result, the month represented by the model 185 | * will change to show the correct year. 186 | * 187 | * @param fromMilliseconds 188 | * the moment in time from which the next `day` model page `down` will be constructed. 189 | * @param selectedMilliseconds 190 | * the current value of the date/time picker. 191 | * @returns 192 | * model containing an `active` `day` one month `down` from the specified moment in time. 193 | */ 194 | pageDown(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 195 | return this.getModel(moment(fromMilliseconds).add(1, 'month').valueOf(), selectedMilliseconds); 196 | } 197 | 198 | /** 199 | * Move the active `day` one month `up` from the specified moment in time. 200 | * 201 | * Paging `up` will result in the `active` day being part of a different month than 202 | * the specified `fromMilliseconds`. As a result, the month represented by the model 203 | * will change to show the correct year. 204 | * 205 | * @param fromMilliseconds 206 | * the moment in time from which the next `day` model page `up` will be constructed. 207 | * @param selectedMilliseconds 208 | * the current value of the date/time picker. 209 | * @returns 210 | * model containing an `active` `day` one month `up` from the specified moment in time. 211 | */ 212 | pageUp(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 213 | return this.getModel(moment(fromMilliseconds).subtract(1, 'month').valueOf(), selectedMilliseconds); 214 | } 215 | 216 | 217 | /** 218 | * Move the `active` `day` to the last day of the month. 219 | * 220 | * The view or time range will not change unless the `fromMilliseconds` value 221 | * is in a different day than the displayed decade. 222 | * 223 | * @param fromMilliseconds 224 | * the moment in time from which the last day of the month will be calculated. 225 | * @param selectedMilliseconds 226 | * the current value of the date/time picker. 227 | * @returns 228 | * a model with the last cell in the view as the active `day`. 229 | */ 230 | goEnd(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 231 | return this.getModel(moment(fromMilliseconds) 232 | .endOf('month').startOf('day').valueOf(), selectedMilliseconds); 233 | } 234 | 235 | /** 236 | * Move the `active` `day` to the first day of the month. 237 | * 238 | * The view or time range will not change unless the `fromMilliseconds` value 239 | * is in a different day than the displayed decade. 240 | * 241 | * @param fromMilliseconds 242 | * the moment in time from which the first day of the month will be calculated. 243 | * @param selectedMilliseconds 244 | * the current value of the date/time picker. 245 | * @returns 246 | * a model with the first cell in the view as the active `day`. 247 | */ 248 | goHome(fromMilliseconds: number, selectedMilliseconds: number): DlDateTimePickerModel { 249 | return this.getModel(moment(fromMilliseconds).startOf('month').valueOf(), selectedMilliseconds); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/lib/dl-date-time-input/specs/dl-date-time-input.directive.spec.ts: -------------------------------------------------------------------------------- 1 | import {Component, DebugElement, ViewChild} from '@angular/core'; 2 | import {ComponentFixture, fakeAsync, flush, TestBed, tick} from '@angular/core/testing'; 3 | import {FormsModule, NgForm} from '@angular/forms'; 4 | import {By} from '@angular/platform-browser'; 5 | import moment from 'moment'; 6 | import { 7 | DL_DATE_TIME_DISPLAY_FORMAT_DEFAULT, 8 | DlDateTimeInputDirective, 9 | DlDateTimeInputModule, 10 | DlDateTimeNumberModule 11 | } from '../../public-api'; 12 | import {OCT} from '../../dl-date-time-picker/specs/month-constants'; 13 | 14 | @Component({ 15 | template: ` 16 |
17 | 19 |
` 20 | }) 21 | class DateModelComponent { 22 | dateValue: number; 23 | @ViewChild(DlDateTimeInputDirective, {static: false}) input: DlDateTimeInputDirective; 24 | dateTimeFilter: (value: (number | null)) => boolean = () => true; 25 | } 26 | 27 | describe('DlDateTimeInputDirective', () => { 28 | 29 | beforeEach(async () => { 30 | await TestBed.configureTestingModule({ 31 | imports: [ 32 | FormsModule, 33 | DlDateTimeNumberModule, 34 | DlDateTimeInputModule 35 | ], 36 | declarations: [ 37 | DateModelComponent, 38 | ] 39 | }).compileComponents(); 40 | }); 41 | 42 | describe('numeric model', () => { 43 | let component: DateModelComponent; 44 | let fixture: ComponentFixture; 45 | let debugElement: DebugElement; 46 | 47 | beforeEach(async () => { 48 | fixture = TestBed.createComponent(DateModelComponent); 49 | fixture.detectChanges(); 50 | await fixture.whenStable().then(() => { 51 | fixture.detectChanges(); 52 | component = fixture.componentInstance; 53 | debugElement = fixture.debugElement; 54 | }); 55 | }); 56 | 57 | it('should be input as text and stored as a number', () => { 58 | const inputElement = debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement; 59 | inputElement.value = '2018-10-01'; 60 | inputElement.dispatchEvent(new Event('input')); 61 | fixture.detectChanges(); 62 | expect(component.dateValue).toEqual(moment('2018-10-01').valueOf()); 63 | }); 64 | 65 | it('should be displayed using default format', fakeAsync(() => { 66 | const octoberFirst = moment('2018-10-01'); 67 | const expectedValue = octoberFirst.format(DL_DATE_TIME_DISPLAY_FORMAT_DEFAULT); 68 | component.dateValue = octoberFirst.valueOf(); 69 | fixture.detectChanges(); 70 | flush(); 71 | const inputElement = debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement; 72 | expect(inputElement.value).toEqual(expectedValue); 73 | })); 74 | 75 | it('should remove model value when text value is empty string', fakeAsync(() => { 76 | const inputElement = debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement; 77 | inputElement.value = '2018-10-01'; 78 | inputElement.dispatchEvent(new Event('input')); 79 | fixture.detectChanges(); 80 | 81 | 82 | inputElement.value = ''; 83 | inputElement.dispatchEvent(new Event('input')); 84 | fixture.detectChanges(); 85 | expect(component.dateValue).toBeUndefined(); 86 | })); 87 | 88 | it('should mark input touched on blur', () => { 89 | const inputElement = fixture.debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement; 90 | 91 | expect(inputElement.classList).toContain('ng-untouched'); 92 | 93 | inputElement.dispatchEvent(new Event('focus')); 94 | fixture.detectChanges(); 95 | 96 | expect(inputElement.classList).toContain('ng-untouched'); 97 | 98 | inputElement.dispatchEvent(new Event('blur')); 99 | fixture.detectChanges(); 100 | 101 | expect(inputElement.classList).toContain('ng-touched'); 102 | }); 103 | 104 | it('should reformat the input value on blur', fakeAsync(() => { 105 | const inputElement = debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement; 106 | 107 | inputElement.value = '1/1/2001'; 108 | inputElement.dispatchEvent(new Event('input')); 109 | fixture.detectChanges(); 110 | 111 | expect(inputElement.value).toBe('1/1/2001'); 112 | 113 | inputElement.dispatchEvent(new Event('blur')); 114 | fixture.detectChanges(); 115 | 116 | expect(inputElement.value).toBe(moment('2001-01-01').format(DL_DATE_TIME_DISPLAY_FORMAT_DEFAULT)); 117 | })); 118 | 119 | it('should not reformat invalid dates on blur', () => { 120 | const inputElement = debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement; 121 | 122 | inputElement.value = 'very-invalid-date'; 123 | inputElement.dispatchEvent(new Event('input')); 124 | fixture.detectChanges(); 125 | 126 | expect(inputElement.value).toBe('very-invalid-date'); 127 | 128 | inputElement.dispatchEvent(new Event('blur')); 129 | fixture.detectChanges(); 130 | 131 | expect(inputElement.value).toBe('very-invalid-date'); 132 | }); 133 | 134 | it('should consider empty input to be valid (for non-required inputs)', () => { 135 | const inputElement = debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement; 136 | 137 | expect(inputElement.classList).toContain('ng-valid'); 138 | }); 139 | 140 | it('should add ng-invalid on invalid input', fakeAsync(() => { 141 | const novemberFirst = moment('2018-11-01'); 142 | component.dateValue = novemberFirst.valueOf(); 143 | fixture.detectChanges(); 144 | flush(); 145 | 146 | const inputElement = debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement; 147 | 148 | expect(inputElement.classList).toContain('ng-valid'); 149 | 150 | inputElement.value = 'very-valid-date'; 151 | inputElement.dispatchEvent(new Event('input')); 152 | fixture.detectChanges(); 153 | 154 | expect(inputElement.classList).toContain('ng-invalid'); 155 | 156 | const control = debugElement.children[0].injector.get(NgForm).control.get('dateValue'); 157 | expect(control.hasError('dlDateTimeInputParse')).toBe(true); 158 | expect(control.errors.dlDateTimeInputParse.text).toBe('very-valid-date'); 159 | })); 160 | 161 | it('should not error if dateTimeFilter is undefined', () => { 162 | component.dateTimeFilter = undefined; 163 | fixture.detectChanges(); 164 | 165 | const inputElement = debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement; 166 | inputElement.value = '10/29/2018 05:00 PM'; 167 | inputElement.dispatchEvent(new Event('input')); 168 | fixture.detectChanges(); 169 | 170 | expect(inputElement.classList).toContain('ng-valid'); 171 | }); 172 | 173 | it('should not error if dateTimeFilter is null', () => { 174 | component.dateTimeFilter = null; 175 | fixture.detectChanges(); 176 | 177 | const inputElement = debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement; 178 | inputElement.value = '10/29/2018 05:00 PM'; 179 | inputElement.dispatchEvent(new Event('input')); 180 | fixture.detectChanges(); 181 | 182 | expect(inputElement.classList).toContain('ng-valid'); 183 | }); 184 | 185 | it('should add ng-invalid for input of filtered out date', () => { 186 | const expectedErrorValue = moment('2018-10-29T17:00').valueOf(); 187 | 188 | const allowedValue = moment('2019-10-29T17:00').valueOf(); 189 | 190 | spyOn(component, 'dateTimeFilter').and.callFake((date: number) => { 191 | return date === allowedValue; 192 | }); 193 | 194 | const inputElement = debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement; 195 | inputElement.value = '10/29/2018 05:00 PM'; 196 | inputElement.dispatchEvent(new Event('input')); 197 | fixture.detectChanges(); 198 | 199 | expect(inputElement.classList).toContain('ng-invalid'); 200 | 201 | const control = debugElement.children[0].injector.get(NgForm).control.get('dateValue'); 202 | expect(control.hasError('dlDateTimeInputFilter')).toBe(true); 203 | const value = control.errors.dlDateTimeInputFilter.value; 204 | expect(value).toBe(expectedErrorValue); 205 | }); 206 | 207 | it('should remove ng-invalid when model is updated with valid date', fakeAsync(() => { 208 | // This is to fix #448, inputting a value that is a disallowed date (but a valid date) 209 | // should change to ng-valid when the model is updated to an allowed date. 210 | 211 | const allowedValue = moment('2019-10-29T17:00').valueOf(); 212 | spyOn(component, 'dateTimeFilter').and.callFake((date: number) => { 213 | return date === allowedValue; 214 | }); 215 | 216 | const inputElement = debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement; 217 | inputElement.value = '10/29/2018 05:00 PM'; 218 | inputElement.dispatchEvent(new Event('blur')); 219 | 220 | fixture.detectChanges(); 221 | 222 | expect(inputElement.classList).toContain('ng-invalid'); 223 | 224 | component.dateValue = allowedValue; 225 | 226 | fixture.detectChanges(); 227 | tick(); 228 | fixture.detectChanges(); 229 | 230 | expect(inputElement.classList).toContain('ng-valid'); 231 | })); 232 | 233 | it('should add ng-invalid for non-date input and remove ng-invalid after when model is updated with valid date', fakeAsync(() => { 234 | // This is to fix #448, inputting a completely invalid date value (i.e not a date at all) 235 | // should change to ng-valid when the model is updated to an allowed date. 236 | 237 | const allowedValue = moment('2019-10-29T17:00').valueOf(); 238 | spyOn(component, 'dateTimeFilter').and.callFake((date: number) => { 239 | return date === allowedValue; 240 | }); 241 | 242 | const inputElement = debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement; 243 | inputElement.value = 'very-invalid-date'; 244 | inputElement.dispatchEvent(new Event('input')); 245 | 246 | fixture.detectChanges(); 247 | 248 | expect(inputElement.classList).toContain('ng-invalid'); 249 | 250 | inputElement.dispatchEvent(new Event('blur')); 251 | fixture.detectChanges(); 252 | 253 | component.dateValue = allowedValue; 254 | 255 | fixture.detectChanges(); 256 | tick(); 257 | fixture.detectChanges(); 258 | 259 | expect(inputElement.classList).toContain('ng-valid'); 260 | })); 261 | 262 | it('should disable input when setDisabled is called', () => { 263 | const inputElement = debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement; 264 | expect(inputElement.disabled).toBe(false); 265 | component.input.setDisabledState(true); 266 | expect(inputElement.disabled).toBe(true); 267 | }); 268 | 269 | it('should emit a change event when a valid value is entered.', function () { 270 | const changeSpy = jasmine.createSpy('change listener'); 271 | component.input.dateChange.subscribe(changeSpy); 272 | 273 | const inputElement = debugElement.query(By.directive(DlDateTimeInputDirective)).nativeElement; 274 | inputElement.value = '2018-10-01'; 275 | inputElement.dispatchEvent(new Event('input')); 276 | fixture.detectChanges(); 277 | 278 | inputElement.dispatchEvent(new Event('blur')); 279 | fixture.detectChanges(); 280 | 281 | inputElement.dispatchEvent(new Event('change')); 282 | fixture.detectChanges(); 283 | 284 | const expected = new Date(2018, OCT, 1).getTime(); 285 | 286 | expect(changeSpy).toHaveBeenCalled(); 287 | expect(changeSpy.calls.first().args[0].value).toBe(expected); 288 | }); 289 | }); 290 | }); 291 | -------------------------------------------------------------------------------- /.github/contributing.md: -------------------------------------------------------------------------------- 1 | Submitting Issues 2 | ================= 3 | 4 | If you are submitting a bug, please create a [jsfiddle](http://jsfiddle.net/) demonstrating the issue. 5 | 6 | Contributing code 7 | ================= 8 | 9 | To contribute, fork the library and install gulp and dependencies. You need [node](http://nodejs.org/); use [nvm](https://github.com/creationix/nvm) or [nenv](https://github.com/ryuone/nenv) to install it. 10 | 11 | ```shell 12 | git clone https://github.com/dalelotts/angular-bootstrap-datetimepicker.git 13 | cd angular-bootstrap-datetimepicker 14 | npm install 15 | git checkout -b my-fix-branch develop # all patches against develop branch, please! 16 | npm test # this runs lint, complexity checks, and unit tests. 17 | ``` 18 | 19 | Very important notes 20 | ==================== 21 | 22 | * **Pull pull requests to the `master` branch will be closed.** Please submit all pull requests to the `develop` branch. 23 | * **Pull requests will not be merged without unit tests.** 24 | * **Do not include the minified files in your pull request.** 25 | * **Have good tests. If you don't have tests for every line and branch in your changes, I won't accept the PR. 26 | * **If your PR fails the CI build, I won't look at it. 27 | 28 | Gulp tasks 29 | =========== 30 | 31 | We use Gulp for managing the build. Here are some useful Gulp tasks: 32 | 33 | * `npm test` Runs all tests (webpack, commonjs, etc) checks the coding style, lints the code, calculates complexity, runs all tests, and enforces code coverage. You should make sure you do this before submitting a PR. 34 | * `gulp` The default task checks the coding style, lints the code, calculates complexity, runs the tests (not webpack or commonJS tests), and enforces code coverage. 35 | * `gulp scss` Generates the css file from the source scss. 36 | * `gulp css-lint` Lint the css files after generating. 37 | * `gulp templatecache` Generates src/js/datetimepicker.templates.js. You must re-add the IIFE around the generated code after it is generated (PR to fix this would be apprecaited) 38 | 39 | # Contributing to angular-bootstrap-datetimepicker 40 | 41 | We'd love for you to contribute to our source code and to make angular-bootstrap-datetimepicker even better than it is 42 | today! Here are the guidelines we'd like you to follow: 43 | 44 | - [Question or Problem?](#question) 45 | - [Issues and Bugs](#issue) 46 | - [Feature Requests](#feature) 47 | - [Submission Guidelines](#submit) 48 | - [Coding Rules](#rules) 49 | - [Commit Message Guidelines](#commit) 50 | - [Signing the CLA](#cla) 51 | - [Further Info](#info) 52 | 53 | ## Got a Question or Problem? 54 | 55 | If you have questions about how to use angular-bootstrap-datetimepicker, please direct these to the [Gitter Chat Group][gitter]. 56 | 57 | ## Found an Issue? 58 | 59 | If you find a bug in the source code or a mistake in the documentation, you can help us by 60 | submitting an issue to our [GitHub Repository][issues]. Even better you can submit a Pull Request 61 | with a fix. 62 | 63 | **Localization Issues:** This directive uses the [Moment][momentI18N] for all localization. 64 | This means that any localization issues should be reproted to Moment. 65 | 66 | **Please see the [Submission Guidelines](#submit) below.** 67 | 68 | ## Want a Feature? 69 | 70 | You can request a new feature by submitting an issue to our [GitHub Repository][github]. If you 71 | would like to implement a new feature then consider what kind of change it is: 72 | 73 | * **Major Changes** that you wish to contribute to the project should be discussed first on [Gitter Chat][gitter] 74 | so that we can better coordinate our efforts, prevent duplication of work, and help you to craft the 75 | change so that it is successfully accepted into the project. 76 | * **Small Changes** can be crafted and submitted to the [GitHub Repository][github] as a Pull 77 | Request. 78 | 79 | 80 | ## Want a Doc Fix? 81 | 82 | If you want to help improve the docs, submit a PR. 83 | 84 | When naming the commit, it is advised to follow the commit message 85 | guidelines below, by starting the commit message with **docs** and 86 | reference the filename and follows the **[Commit Message Guidelines](#commit)** outlined below. 87 | 88 | ## Submission Guidelines 89 | 90 | ### Submitting an Issue 91 | Before you submit your issue search the archive, maybe your question was already answered. 92 | 93 | If your issue appears to be a bug, and hasn't been reported, open a new issue. Help us to maximize 94 | the effort we can spend fixing issues and adding new features, by not reporting duplicate issues. 95 | Providing the following information will increase the chances of your issue being dealt with 96 | quickly: 97 | 98 | * **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps 99 | * **Motivation for or Use Case** - explain why this is a bug for you 100 | * **Angular Version(s)** - is it a regression? 101 | * **Browsers and Operating System** - is this a problem with all browsers or only specific ones? 102 | * **Reproduce the Error** - provide a live example (using [Plunker][plunker] or 103 | [JSFiddle][jsfiddle]) or an unambiguous set of steps. 104 | * **Related Issues** - has a similar issue been reported before? 105 | * **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be 106 | causing the problem (line of code or commit) 107 | 108 | Here is a great example of a well defined issue: https://github.com/angular/angular.js/issues/5069 109 | 110 | **If you get help, help others. Good karma rulez!** 111 | 112 | ### Submitting a Pull Request 113 | Before you submit your pull request consider the following guidelines: 114 | 115 | * Search [GitHub][pulls] for an open or closed Pull Request 116 | that relates to your submission. You don't want to duplicate effort. 117 | * Make your changes in a new git branch: 118 | 119 | ```shell 120 | git checkout -b my-fix-branch master 121 | ``` 122 | 123 | * Create your patch, **including appropriate test cases**. 124 | * Run the full test suite, `npm test`, and ensure that all tests pass. 125 | * Commit your changes using a descriptive commit message that follows our 126 | [commit message conventions](#commit). Adherence to the [commit message conventions](#commit) is required, 127 | because release notes are automatically generated from these messages. 128 | 129 | ```shell 130 | git commit -a 131 | ``` 132 | Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files. 133 | 134 | * Build your changes locally to ensure all the tests pass: 135 | 136 | ```shell 137 | npm test 138 | ``` 139 | 140 | * Push your branch to your fork on GitHub: 141 | 142 | ```shell 143 | git push origin my-fix-branch 144 | ``` 145 | 146 | In GitHub, send a pull request to `angular-bootstrap-datetimepicker:develop`. 147 | If we suggest changes, then: 148 | 149 | * Make the required updates. 150 | * Re-run the test suite to ensure tests are still passing. 151 | * Commit your changes to your branch (e.g. `my-fix-branch`). 152 | * Push the changes to your GitHub repository (this will update your Pull Request). 153 | 154 | If the PR gets too outdated we may ask you to rebase and force push to update the PR: 155 | 156 | ```shell 157 | git rebase master -i 158 | git push origin my-fix-branch -f 159 | ``` 160 | 161 | _WARNING: Squashing or reverting commits and force-pushing thereafter may remove GitHub comments 162 | on code that were previously made by you or others in your commits. Avoid any form of rebasing 163 | unless necessary._ 164 | 165 | That's it! Thank you for your contribution! 166 | 167 | #### After your pull request is merged 168 | 169 | After your pull request is merged, you can safely delete your branch and pull the changes 170 | from the main (upstream) repository: 171 | 172 | * Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows: 173 | 174 | ```shell 175 | git push origin --delete my-fix-branch 176 | ``` 177 | 178 | * Check out the master branch: 179 | 180 | ```shell 181 | git checkout master -f 182 | ``` 183 | 184 | * Delete the local branch: 185 | 186 | ```shell 187 | git branch -D my-fix-branch 188 | ``` 189 | 190 | * Update your master with the latest upstream version: 191 | 192 | ```shell 193 | git pull --ff upstream master 194 | ``` 195 | 196 | ## Coding Rules 197 | 198 | To ensure consistency throughout the source code, keep these rules in mind as you are working: 199 | 200 | * All features or bug fixes **must be tested** by one or more test. 201 | 202 | ## Git Commit Guidelines 203 | 204 | We have very precise rules over how our git commit messages can be formatted. This leads to **more 205 | readable messages** that are easy to follow when looking through the **project history**. But also, 206 | we use the git commit messages to **generate the angular-bootstrap-datetimepicker change log**. 207 | 208 | The commit message formatting can be added using a typical git workflow or through the use of a CLI wizard ([Commitizen](https://github.com/commitizen/cz-cli)). To use the wizard, run `npm run commit` in your terminal after staging your changes in git. 209 | 210 | ### Commit Message Format 211 | Each commit message consists of a **header**, a **body** and a **footer**. The header has a special 212 | format that includes a **type**, a **scope** and a **subject**: 213 | 214 | ``` 215 | (): 216 | 217 | 218 | 219 |