├── e2e
├── app.e2e-spec.ts
├── tsconfig.e2e.json
├── hide-input-container-e2e.spec.ts
├── test-utils.ts
├── configuration-e2e.spec.ts
├── format-validation-e2e.spec.ts
├── move-to-date-api-e2e.spec.ts
├── locale-e2e.spec.ts
├── directive-e2e.spec.ts
├── daypicker-e2e.spec.ts
├── current-btn-e2e.spec.ts
├── daytimepicker-e2e.spec.ts
├── reactive-directive-e2e.spec.ts
├── selected-unselect-e2e.spec.ts
└── timepicker-e2e.spec.ts
├── src
├── assets
│ └── .gitkeep
├── styles.less
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── favicon.ico
├── app
│ ├── common
│ │ ├── types
│ │ │ ├── calendar-mode.ts
│ │ │ ├── week-days.type.ts
│ │ │ ├── poistions.type.ts
│ │ │ ├── calendar-mode-enum.ts
│ │ │ ├── calendar-value-enum.ts
│ │ │ ├── single-calendar-value.ts
│ │ │ ├── calendar-value.ts
│ │ │ └── validator.type.ts
│ │ ├── models
│ │ │ ├── date.model.ts
│ │ │ ├── navigation-event.model.ts
│ │ │ └── calendar.model.ts
│ │ ├── decorators
│ │ │ └── decorators.ts
│ │ ├── styles
│ │ │ └── variables.less
│ │ └── services
│ │ │ ├── dom-appender
│ │ │ └── dom-appender.service.ts
│ │ │ └── utils
│ │ │ └── utils.service.spec.ts
│ ├── month-calendar
│ │ ├── month.model.ts
│ │ ├── month-calendar-config.ts
│ │ ├── month-calendar.component.html
│ │ ├── month-calendar.component.less
│ │ ├── month-calendar.component.spec.ts
│ │ ├── month-calendar.service.spec.ts
│ │ └── month-calendar.service.ts
│ ├── demo
│ │ ├── demo-root.component.ts
│ │ ├── demo
│ │ │ ├── demo.component.spec.ts
│ │ │ └── demo.component.less
│ │ ├── services
│ │ │ └── ga
│ │ │ │ ├── ga.service.spec.ts
│ │ │ │ └── ga.service.ts
│ │ └── demo.module.ts
│ ├── date-picker
│ │ ├── date-picker.api.ts
│ │ ├── date-picker-directive-config.model.ts
│ │ ├── date-picker.component.less
│ │ ├── date-picker-config.model.ts
│ │ ├── date-picker-directive.service.ts
│ │ ├── date-picker.directive.spec.ts
│ │ ├── date-picker.service.spec.ts
│ │ ├── date-picker.component.html
│ │ ├── date-picker.component.spec.ts
│ │ ├── date-picker-directive.service.spec.ts
│ │ ├── date-picker.service.ts
│ │ └── date-picker.directive.ts
│ ├── day-time-calendar
│ │ ├── day-time-calendar-config.model.ts
│ │ ├── day-time-calendar.component.less
│ │ ├── day-time-calendar.component.html
│ │ ├── day-time-calendar.service.ts
│ │ ├── day-time-calendar.component.spec.ts
│ │ ├── day-time-calendar.service.spec.ts
│ │ └── day-time-calendar.component.ts
│ ├── day-calendar
│ │ ├── day.model.ts
│ │ ├── day-calendar-config.model.ts
│ │ ├── day-calendar.component.html
│ │ ├── day-calendar.component.less
│ │ ├── day-calendar.component.spec.ts
│ │ ├── day-calendar.service.ts
│ │ └── day-calendar.service.spec.ts
│ ├── index.ts
│ ├── time-select
│ │ ├── time-select-config.model.ts
│ │ ├── time-select.component.less
│ │ ├── time-select.component.spec.ts
│ │ ├── time-select.component.html
│ │ ├── time-select.service.ts
│ │ └── time-select.component.ts
│ ├── calendar-nav
│ │ ├── calendar-nav.component.spec.ts
│ │ ├── calendar-nav.component.ts
│ │ ├── calendar-nav.component.html
│ │ └── calendar-nav.component.less
│ └── date-picker.module.ts
├── typings.d.ts
├── main.ts
├── tsconfig.app.json
├── tsconfig.spec.json
├── index.html
├── test.ts
└── polyfills.ts
├── index.d.ts
├── screenshots
├── date_picker.png
├── month_picker.png
└── date_time_picker.png
├── .editorconfig
├── ISSUE_TEMPLATE.md
├── .gitignore
├── tsconfig.json
├── .travis.yml
├── protractor.conf.js
├── LICENSE
├── .angular-cli.json
├── karma.conf.js
├── README.fa.md
├── package.json
├── tslint.json
├── CONTRIBUTING.md
└── CHANGELOG.md
/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | export * from './bin/app/index';
2 |
--------------------------------------------------------------------------------
/src/styles.less:
--------------------------------------------------------------------------------
1 | & {
2 | html, body {
3 | margin: 0;
4 | padding: 0;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fingerpich/jalali-angular-datepicker/HEAD/src/favicon.ico
--------------------------------------------------------------------------------
/src/app/common/types/calendar-mode.ts:
--------------------------------------------------------------------------------
1 | export type CalendarMode = 'day' | 'month' | 'daytime' | 'time';
2 |
--------------------------------------------------------------------------------
/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | /* SystemJS module definition */
2 | declare var module: {
3 | id: string;
4 | };
5 |
--------------------------------------------------------------------------------
/src/app/common/types/week-days.type.ts:
--------------------------------------------------------------------------------
1 | export type WeekDays = 'su' | 'mo' | 'tu' | 'we' | 'th' | 'fr' | 'sa';
2 |
--------------------------------------------------------------------------------
/src/app/common/types/poistions.type.ts:
--------------------------------------------------------------------------------
1 | export type TOpens = 'right' | 'left';
2 | export type TDrops = 'up' | 'down';
--------------------------------------------------------------------------------
/screenshots/date_picker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fingerpich/jalali-angular-datepicker/HEAD/screenshots/date_picker.png
--------------------------------------------------------------------------------
/screenshots/month_picker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fingerpich/jalali-angular-datepicker/HEAD/screenshots/month_picker.png
--------------------------------------------------------------------------------
/src/app/common/types/calendar-mode-enum.ts:
--------------------------------------------------------------------------------
1 | export enum ECalendarMode {
2 | Day,
3 | DayTime,
4 | Month,
5 | Time
6 | }
7 |
--------------------------------------------------------------------------------
/screenshots/date_time_picker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fingerpich/jalali-angular-datepicker/HEAD/screenshots/date_time_picker.png
--------------------------------------------------------------------------------
/src/app/common/types/calendar-value-enum.ts:
--------------------------------------------------------------------------------
1 | export enum ECalendarValue {
2 | Moment = 1,
3 | MomentArr,
4 | String,
5 | StringArr
6 | }
7 |
--------------------------------------------------------------------------------
/src/app/common/types/single-calendar-value.ts:
--------------------------------------------------------------------------------
1 | import {Moment} from 'jalali-moment';
2 |
3 | export type SingleCalendarValue = Moment | string;
4 |
--------------------------------------------------------------------------------
/src/app/common/types/calendar-value.ts:
--------------------------------------------------------------------------------
1 | import {Moment} from 'jalali-moment';
2 |
3 | export type CalendarValue = Moment | Moment[] | string | string[];
4 |
--------------------------------------------------------------------------------
/src/app/common/models/date.model.ts:
--------------------------------------------------------------------------------
1 | import {Moment} from 'jalali-moment';
2 |
3 | export interface IDate {
4 | date: Moment;
5 | selected?: boolean;
6 | }
7 |
--------------------------------------------------------------------------------
/src/app/common/models/navigation-event.model.ts:
--------------------------------------------------------------------------------
1 | import {Moment} from 'jalali-moment';
2 |
3 | export interface INavEvent {
4 | from: Moment;
5 | to: Moment;
6 | }
7 |
--------------------------------------------------------------------------------
/src/app/common/types/validator.type.ts:
--------------------------------------------------------------------------------
1 | import {CalendarValue} from './calendar-value';
2 |
3 | export type DateValidator = (inputVal: CalendarValue) => {[key: string]: any};
4 |
--------------------------------------------------------------------------------
/src/app/month-calendar/month.model.ts:
--------------------------------------------------------------------------------
1 | import {IDate} from '../common/models/date.model';
2 |
3 | export interface IMonth extends IDate {
4 | currentMonth: boolean;
5 | disabled: boolean;
6 | text: string;
7 | }
8 |
--------------------------------------------------------------------------------
/src/app/demo/demo-root.component.ts:
--------------------------------------------------------------------------------
1 | import {Component} from '@angular/core';
2 |
3 | @Component({
4 | selector: 'dp-demo-root',
5 | template: ''
6 | })
7 | export class DemoRootComponent {
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/date-picker/date-picker.api.ts:
--------------------------------------------------------------------------------
1 | import {SingleCalendarValue} from '../common/types/single-calendar-value';
2 |
3 | export interface IDpDayPickerApi {
4 | open: () => void;
5 | close: () => void;
6 | moveCalendarTo: (date: SingleCalendarValue) => void;
7 | }
8 |
--------------------------------------------------------------------------------
/src/app/demo/demo/demo.component.spec.ts:
--------------------------------------------------------------------------------
1 | import {DemoComponent} from './demo.component';
2 |
3 | describe('Component: Demo', () => {
4 | it('should create an instance', () => {
5 | const component = new DemoComponent(null);
6 | expect(component).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/src/app/day-time-calendar/day-time-calendar-config.model.ts:
--------------------------------------------------------------------------------
1 | import {ITimeSelectConfig} from '../time-select/time-select-config.model';
2 | import {IDayCalendarConfig} from '../day-calendar/day-calendar-config.model';
3 |
4 | export interface IDayTimeCalendarConfig extends ITimeSelectConfig, IDayCalendarConfig {
5 | }
6 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/src/app/day-calendar/day.model.ts:
--------------------------------------------------------------------------------
1 | import {IDate} from '../common/models/date.model';
2 |
3 | export interface IDay extends IDate {
4 | currentMonth?: boolean;
5 | prevMonth?: boolean;
6 | nextMonth?: boolean;
7 | currentDay?: boolean;
8 | disabled?: boolean;
9 | }
10 |
11 | export interface IDayEvent {
12 | day: IDay;
13 | event: MouseEvent;
14 | }
15 |
--------------------------------------------------------------------------------
/src/app/common/models/calendar.model.ts:
--------------------------------------------------------------------------------
1 | import {Moment} from 'jalali-moment';
2 | import {SingleCalendarValue} from '../types/single-calendar-value';
3 |
4 | export interface ICalendar {
5 | locale?: string;
6 | min?: SingleCalendarValue;
7 | max?: Moment | string;
8 | }
9 |
10 | export interface ICalendarInternal {
11 | locale?: string;
12 | min?: Moment;
13 | max?: Moment;
14 | }
15 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import './polyfills.ts';
2 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
3 | import {enableProdMode} from '@angular/core';
4 | import {environment} from './environments/environment';
5 | import {DemoModule} from './app/demo/demo.module';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic().bootstrapModule(DemoModule);
12 |
--------------------------------------------------------------------------------
/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // The file contents for the current environment will overwrite these during build.
2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do
3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead.
4 | // The list of which env maps to which file can be found in `.angular-cli.json`.
5 |
6 | export const environment = {
7 | production: false
8 | };
9 |
--------------------------------------------------------------------------------
/src/app/demo/services/ga/ga.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, inject } from '@angular/core/testing';
2 |
3 | import { GaService } from './ga.service';
4 |
5 | describe('GaService', () => {
6 | beforeEach(() => {
7 | TestBed.configureTestingModule({
8 | providers: [GaService]
9 | });
10 | });
11 |
12 | it('should ...', inject([GaService], (service: GaService) => {
13 | expect(service).toBeTruthy();
14 | }));
15 | });
16 |
--------------------------------------------------------------------------------
/e2e/tsconfig.e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "declaration": false,
5 | "moduleResolution": "node",
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "lib": [
9 | "es2016",
10 | "dom"
11 | ],
12 | "outDir": "../dist/out-tsc-e2e",
13 | "module": "commonjs",
14 | "target": "es6",
15 | "types": [
16 | "jasmine",
17 | "node"
18 | ]
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/app/day-time-calendar/day-time-calendar.component.less:
--------------------------------------------------------------------------------
1 | @import '../common/styles/variables';
2 |
3 | & {
4 | dp-day-time-calendar {
5 | display: inline-block;
6 |
7 | dp-time-select {
8 | display: block;
9 | //border: 1px solid @c-black;
10 | border-top: 0;
11 | }
12 |
13 | &.dp-material {
14 | dp-time-select {
15 | //border: 1px solid @c-light-gray;
16 | border-top: 0;
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/e2e/hide-input-container-e2e.spec.ts:
--------------------------------------------------------------------------------
1 | import {DemoPage} from './app.po';
2 |
3 | describe('hideInputContainer', () => {
4 | let page: DemoPage;
5 |
6 | beforeEach(() => {
7 | page = new DemoPage();
8 | page.navigateTo();
9 | });
10 |
11 | it('should hide/show InputContainer datetimepicker', () => {
12 | expect(page.daytimePickerInput.isDisplayed()).toBe(true);
13 | page.hideInputRadio.click();
14 | expect(page.daytimePickerInput.isDisplayed()).toBe(false);
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "declaration": false,
5 | "moduleResolution": "node",
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "lib": [
9 | "es2016",
10 | "dom"
11 | ],
12 | "outDir": "../out-tsc/app",
13 | "target": "es5",
14 | "module": "es2015",
15 | "baseUrl": "",
16 | "types": []
17 | },
18 | "exclude": [
19 | "test.ts",
20 | "**/*.spec.ts"
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/e2e/test-utils.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 | import {browser} from 'protractor';
3 |
4 |
5 | export class TestUtils {
6 | static async takeScreenshot(filename) {
7 | const png = await browser.takeScreenshot();
8 | TestUtils.writeScreenShot(png, 'screens/' + filename + '.png');
9 | }
10 |
11 | private static writeScreenShot(data: string, filename: string) {
12 | const stream = fs.createWriteStream(filename);
13 | stream.write(new Buffer(data, 'base64'));
14 | stream.end();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | Make sure to add **all the information needed to understand the bug** so that someone can help. If the info is missing we'll add the 'Needs more information' label and close the issue until there is enough information.
2 |
3 | - [ ] Provide a **minimal code snippet**, please make sure it is well formatted.
4 | - [ ] Provide a **stackblitz** demo / [stackblitz](https://stackblitz.com/) example that reproduces the bug.
5 | - [ ] Provide **screenshots** where appropriate
6 | - [ ] What's the **version** of Angular you're using?
7 | - [ ] Does this occur on specific browser?
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /tmp
5 | /bin
6 | /prebuild
7 | /aot
8 |
9 | # dependencies
10 | /node_modules
11 | /bower_components
12 |
13 | # IDEs and editors
14 | /.idea
15 | /.vscode
16 | .project
17 | .classpath
18 | *.launch
19 | .settings/
20 |
21 | # misc
22 | /.sass-cache
23 | /connect.lock
24 | /coverage/*
25 | /libpeerconnection.log
26 | npm-debug.log
27 | testem.log
28 | /typings
29 |
30 | # e2e
31 | /e2e/*.js
32 | /e2e/*.map
33 |
34 | #System Files
35 | .DS_Store
36 | Thumbs.db
37 |
38 | dist
39 | src/**/*.js
40 |
--------------------------------------------------------------------------------
/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "sourceMap": true,
4 | "declaration": false,
5 | "moduleResolution": "node",
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "lib": [
9 | "es2016",
10 | "dom"
11 | ],
12 | "outDir": "../out-tsc/spec",
13 | "module": "commonjs",
14 | "target": "es6",
15 | "baseUrl": "",
16 | "types": [
17 | "jasmine",
18 | "node",
19 | "jalali-moment"
20 | ]
21 | },
22 | "files": [
23 | "test.ts"
24 | ],
25 | "include": [
26 | "**/*.spec.ts"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "outDir": "bin",
5 | "sourceMap": true,
6 | "declaration": true,
7 | "moduleResolution": "node",
8 | "experimentalDecorators": true,
9 | "emitDecoratorMetadata": true,
10 | "skipLibCheck": true,
11 | "types": [
12 | "node",
13 | "jasmine"
14 | ],
15 | "lib": [
16 | "es2015",
17 | "dom"
18 | ]
19 | },
20 | "exclude": [
21 | "node_modules",
22 | "**/*.spec.ts"
23 | ],
24 | "angularCompilerOptions": {
25 | "genDir": "aot",
26 | "debug": true,
27 | "skipTemplateCodegen": true,
28 | "strictMetadataEmit": true
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/app/demo/services/ga/ga.service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 | import {environment} from '../../../../environments/environment';
3 | declare const ga: Function;
4 |
5 | @Injectable()
6 | export class GaService {
7 |
8 | public emitEvent(eventCategory: string,
9 | eventAction: string,
10 | eventLabel: string = null,
11 | eventValue: number = null) {
12 | if (environment.production && window['ga']) {
13 | ga('send', 'event', {
14 | eventCategory: eventCategory,
15 | eventLabel: eventLabel,
16 | eventAction: eventAction,
17 | eventValue: eventValue
18 | });
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/app/common/decorators/decorators.ts:
--------------------------------------------------------------------------------
1 | import {UtilsService} from '../services/utils/utils.service';
2 | export const DEFAULT_DEBOUNCE_MS = 500;
3 |
4 | export default function debounce(ms: number = DEFAULT_DEBOUNCE_MS) {
5 | return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
6 | return {
7 | configurable: true,
8 | enumerable: descriptor.enumerable,
9 | get: function () {
10 | Object.defineProperty(this, propertyKey, {
11 | configurable: true,
12 | enumerable: descriptor.enumerable,
13 | value: UtilsService.debounce(descriptor.value, ms)
14 | });
15 |
16 | return this[propertyKey];
17 | }
18 | };
19 | };
20 | }
21 |
--------------------------------------------------------------------------------
/src/app/day-time-calendar/day-time-calendar.component.html:
--------------------------------------------------------------------------------
1 |
10 |
11 |
16 |
17 |
--------------------------------------------------------------------------------
/src/app/date-picker/date-picker-directive-config.model.ts:
--------------------------------------------------------------------------------
1 | import {TDrops, TOpens} from '../common/types/poistions.type';
2 | import {IDayCalendarConfig} from '../day-calendar/day-calendar-config.model';
3 | import {IMonthCalendarConfig} from '../month-calendar/month-calendar-config';
4 | import {ITimeSelectConfig} from '../time-select/time-select-config.model';
5 |
6 | export interface IDatePickerDirectiveConfig extends IDayCalendarConfig, IMonthCalendarConfig, ITimeSelectConfig {
7 | closeOnSelect?: boolean;
8 | closeOnSelectDelay?: number;
9 | onOpenDelay?: number;
10 | disableKeypress?: boolean;
11 | appendTo?: string | HTMLElement;
12 | inputElementContainer?: HTMLElement;
13 | drops?: TDrops;
14 | opens?: TOpens;
15 | hideInputContainer?: boolean;
16 | }
17 |
--------------------------------------------------------------------------------
/e2e/configuration-e2e.spec.ts:
--------------------------------------------------------------------------------
1 | import {DemoPage} from './app.po';
2 |
3 | describe('dpDayPicker configuration', () => {
4 | let page: DemoPage;
5 |
6 | beforeEach(() => {
7 | page = new DemoPage();
8 | page.navigateTo();
9 |
10 | page.daytimePickerMenu.click();
11 | });
12 |
13 | it('openOnClick = false, should not open picker when clicked', () => {
14 | page.openOnClickRadioOff.click();
15 | page.openOnFocusRadioOff.click();
16 | page.daytimePickerInput.click();
17 | expect(page.datePickerPopup.isDisplayed()).toBe(false);
18 | });
19 |
20 | it('openOnClick = true, should open picker when clicked', () => {
21 | page.openOnClickRadioOn.click();
22 | page.openOnFocusRadioOff.click();
23 | page.daytimePickerInput.click();
24 | expect(page.datePickerPopup.isDisplayed()).toBe(true);
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/src/app/index.ts:
--------------------------------------------------------------------------------
1 | export {SingleCalendarValue} from './common/types/single-calendar-value';
2 | export {IDate} from './common/models/date.model';
3 | export {ECalendarMode} from './common/types/calendar-mode-enum';
4 | export {IDay} from './day-calendar/day.model';
5 | export {IMonth} from './month-calendar/month.model';
6 | export {ECalendarValue} from './common/types/calendar-value-enum';
7 | export {CalendarValue} from './common/types/calendar-value';
8 | export {IDayCalendarConfig} from './day-calendar/day-calendar-config.model';
9 | export {IDayEvent} from './day-calendar/day.model';
10 | export {IDatePickerConfig} from './date-picker/date-picker-config.model';
11 | export {IDatePickerDirectiveConfig} from './date-picker/date-picker-directive-config.model';
12 | export {IMonthCalendarConfig} from './month-calendar/month-calendar-config';
13 |
14 | export * from './date-picker.module';
15 |
--------------------------------------------------------------------------------
/src/app/time-select/time-select-config.model.ts:
--------------------------------------------------------------------------------
1 | import {Moment} from 'jalali-moment';
2 | import {ICalendar, ICalendarInternal} from '../common/models/calendar.model';
3 | import {ECalendarValue} from '../common/types/calendar-value-enum';
4 |
5 | export interface IConfig {
6 | hours12Format?: string;
7 | hours24Format?: string;
8 | maxTime?: Moment;
9 | meridiemFormat?: string;
10 | minTime?: Moment;
11 | minutesFormat?: string;
12 | minutesInterval?: number;
13 | secondsFormat?: string;
14 | secondsInterval?: number;
15 | showSeconds?: boolean;
16 | showTwentyFourHours?: boolean;
17 | timeSeparator?: string;
18 | returnedValueType?: ECalendarValue;
19 | }
20 |
21 | export interface ITimeSelectConfig extends IConfig, ICalendar {
22 | }
23 |
24 | export interface ITimeSelectConfigInternal extends IConfig,
25 | ICalendarInternal {
26 | }
27 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Angular Jalali Date Picker
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Loading...
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/app/date-picker/date-picker.component.less:
--------------------------------------------------------------------------------
1 | @import '../common/styles/variables';
2 |
3 | & {
4 | dp-date-picker {
5 | display: inline-block;
6 |
7 | &.dp-material {
8 | .dp-picker-input {
9 | box-sizing: border-box;
10 | height: @basic-height;
11 | width: @basic-width;
12 | font-size: 13px;
13 | outline: none;
14 | }
15 | }
16 |
17 | .dp-input-container {
18 | position: relative;
19 | }
20 |
21 | .dp-selected {
22 | background: @c-primary;
23 | color: @c-white;
24 | }
25 | }
26 |
27 | .dp-popup {
28 | position: relative;
29 | background: @c-white;
30 | box-shadow: 1px 1px 5px 0 fade(@c-black, 10);
31 | border-left: 1px solid fade(@c-black, 10);
32 | border-right: 1px solid fade(@c-black, 10);
33 | border-bottom: 1px solid fade(@c-black, 10);
34 | z-index: 9999;
35 | white-space: nowrap;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/app/common/styles/variables.less:
--------------------------------------------------------------------------------
1 | @c-black: #000000;
2 | @c-white: #FFFFFF;
3 | @demo-primary: #106CC8;
4 | @c-primary: rgba(16, 108, 200, 0.50); // #106CC8;
5 | @c-light-gray: #E0E0E0;
6 |
7 | @basic-height: 30px;
8 | @basic-width: 252px;
9 |
10 | .arrow(@deg) {
11 | &::before {
12 | position: relative;
13 | content: '';
14 | display: inline-block;
15 | height: 8px;
16 | width: 8px;
17 | vertical-align: baseline;
18 | border-style: solid;
19 | border-width: 2px 2px 0 0;
20 | transform: rotate(@deg);
21 | }
22 | }
23 |
24 | .double-arrow(@deg) {
25 | &::before, &::after {
26 | position: relative;
27 | content: '';
28 | display: inline-block;
29 | height: 8px;
30 | width: 8px;
31 | vertical-align: baseline;
32 | border-style: solid;
33 | border-width: 2px 2px 0 0;
34 | transform: rotate(@deg);
35 | }
36 |
37 | &::before {
38 | right: -10px;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: required
2 | dist: trusty
3 | language: node_js
4 | node_js:
5 | - '12.18.3'
6 |
7 | cache:
8 | directories:
9 | - node_modules
10 |
11 | addons:
12 | apt:
13 | sources:
14 | - google-chrome
15 | packages:
16 | - google-chrome-stable
17 |
18 | before_install:
19 | - export CHROME_BIN=/usr/bin/google-chrome
20 | - export CHROME_BIN=chromium-browser
21 | - export DISPLAY=:99.0
22 | - sh -e /etc/init.d/xvfb start
23 | - sudo apt-get update
24 | - sudo apt-get install -y libappindicator1 fonts-liberation
25 | - wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
26 | - sudo dpkg -i google-chrome*.deb
27 | - sleep 3
28 | - "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16"
29 |
30 | before_script:
31 | - npm run lint
32 | - npm test -- -watch=false
33 | #- npm run e2e:headless
34 |
35 | script:
36 | - npm run build:prod
37 |
--------------------------------------------------------------------------------
/src/app/demo/demo.module.ts:
--------------------------------------------------------------------------------
1 | import {DatePickerComponent} from '../date-picker/date-picker.component';
2 | import {RouterModule} from '@angular/router';
3 | import {NgModule} from '@angular/core';
4 | import {DemoComponent} from './demo/demo.component';
5 | import {DemoRootComponent} from './demo-root.component';
6 | import {BrowserModule} from '@angular/platform-browser';
7 | import {FormsModule, ReactiveFormsModule} from '@angular/forms';
8 | import {DpDatePickerModule} from '../date-picker.module';
9 | import {GaService} from './services/ga/ga.service';
10 |
11 | @NgModule({
12 | imports: [
13 | BrowserModule,
14 | FormsModule,
15 | ReactiveFormsModule,
16 | DpDatePickerModule,
17 | RouterModule.forRoot([
18 | {
19 | path: '**',
20 | component: DemoComponent
21 | }
22 | ])
23 | ],
24 | declarations: [
25 | DemoRootComponent,
26 | DemoComponent
27 | ],
28 | entryComponents: [
29 | DatePickerComponent
30 | ],
31 | providers: [GaService],
32 | bootstrap: [DemoRootComponent]
33 | })
34 | export class DemoModule {
35 | }
36 |
--------------------------------------------------------------------------------
/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | const {SpecReporter} = require('jasmine-spec-reporter');
5 | const args = process.argv;
6 | const headless = args.indexOf('headless') !== -1;
7 |
8 | exports.config = {
9 | allScriptsTimeout: 120000,
10 | specs: [
11 | './e2e/**/*spec.ts'
12 | ],
13 | capabilities: {
14 | browserName: 'chrome',
15 | chromeOptions: {
16 | args: headless ? [
17 | '--headless',
18 | '--disable-gpu',
19 | '--window-size=1280,1024'] : []
20 | }
21 | },
22 | directConnect: true,
23 | baseUrl: 'http://localhost:4200/',
24 | framework: 'jasmine',
25 | jasmineNodeOpts: {
26 | showColors: true,
27 | defaultTimeoutInterval: 120000,
28 | print: function () {
29 | }
30 | },
31 | onPrepare() {
32 | jasmine.getEnv().addReporter(
33 | new SpecReporter({spec: {displayStacktrace: true}}));
34 |
35 | require('ts-node').register({ project: 'e2e/tsconfig.e2e.json' });
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Vlad Ioffe
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/app/month-calendar/month-calendar-config.ts:
--------------------------------------------------------------------------------
1 | import {locale, Moment} from 'jalali-moment';
2 | import {ICalendar, ICalendarInternal} from '../common/models/calendar.model';
3 | import {ECalendarValue} from '../common/types/calendar-value-enum';
4 |
5 | export interface IConfig {
6 | isMonthDisabledCallback?: (date: Moment) => boolean;
7 | allowMultiSelect?: boolean;
8 | yearFormat?: string;
9 | yearFormatter?: (month: Moment) => string;
10 | format?: string;
11 | isNavHeaderBtnClickable?: boolean;
12 | monthBtnFormat?: string;
13 | monthBtnFormatter?: (day: Moment) => string;
14 | monthBtnCssClassCallback?: (day: Moment) => string;
15 | multipleYearsNavigateBy?: number;
16 | showMultipleYearsNavigation?: boolean;
17 | locale?: string;
18 | returnedValueType?: ECalendarValue;
19 | showGoToCurrent?: boolean;
20 | unSelectOnClick?: boolean;
21 | }
22 |
23 | export interface IMonthCalendarConfig extends IConfig,
24 | ICalendar {
25 |
26 | }
27 |
28 | export interface IMonthCalendarConfigInternal extends IConfig,
29 | ICalendarInternal {
30 | }
31 |
--------------------------------------------------------------------------------
/e2e/format-validation-e2e.spec.ts:
--------------------------------------------------------------------------------
1 | import {DemoPage} from './app.po';
2 |
3 | describe('format validation', () => {
4 | let page: DemoPage;
5 |
6 | beforeEach(() => {
7 | page = new DemoPage();
8 | page.navigateTo();
9 | });
10 |
11 | it('should check that the format validation is working', () => {
12 | const common = (menu, input) => {
13 | menu.click();
14 | input.sendKeys('lmaldlad');
15 | page.clickOnBody();
16 | expect(page.formatValidationMsg.getText()).toBe('invalid format');
17 | input.clear();
18 | };
19 |
20 | common(page.daytimePickerMenu, page.daytimePickerInput);
21 | common(page.daytimeDirectiveMenu, page.daytimeDirectiveInput);
22 | common(page.dayPickerMenu, page.dayPickerInput);
23 | common(page.dayDirectiveMenu, page.dayDirectiveInput);
24 | common(page.dayDirectiveReactiveMenu, page.dayDirectiveReactiveInput);
25 | common(page.monthPickerMenu, page.monthPickerInput);
26 | common(page.monthDirectiveMenu, page.monthDirectiveInput);
27 | common(page.timePickerMenu, page.timePickerInput);
28 | common(page.timeDirectiveMenu, page.timeSelectDirectiveInput);
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/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/long-stack-trace-zone';
4 | import 'zone.js/dist/proxy.js';
5 | import 'zone.js/dist/sync-test';
6 | import 'zone.js/dist/jasmine-patch';
7 | import 'zone.js/dist/async-test';
8 | import 'zone.js/dist/fake-async-test';
9 | import {getTestBed} from '@angular/core/testing';
10 | import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing';
11 |
12 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
13 | declare var __karma__: any;
14 | declare var require: any;
15 |
16 | // Prevent Karma from running prematurely.
17 | __karma__.loaded = function () {
18 | };
19 |
20 | // First, initialize the Angular testing environment.
21 | getTestBed().initTestEnvironment(
22 | BrowserDynamicTestingModule,
23 | platformBrowserDynamicTesting()
24 | );
25 | // Then we find all the tests.
26 | const context = require.context('./', true, /\.spec\.ts$/);
27 | // And load the modules.
28 | context.keys().map(context);
29 | // Finally, start Karma to run the tests.
30 | __karma__.start();
31 |
--------------------------------------------------------------------------------
/src/app/calendar-nav/calendar-nav.component.spec.ts:
--------------------------------------------------------------------------------
1 | import {async, ComponentFixture, TestBed} from '@angular/core/testing';
2 |
3 | import {CalendarNavComponent} from './calendar-nav.component';
4 |
5 | describe('CalendarNavComponent', () => {
6 | let component: CalendarNavComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [CalendarNavComponent]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(CalendarNavComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 |
26 | it('should emit event when go to current click', () => {
27 | const nativeElement = fixture.nativeElement;
28 | const goToCurrent = nativeElement.querySelector('.dp-current-location-btn');
29 |
30 | spyOn(component.onGoToCurrent, 'emit');
31 | goToCurrent.dispatchEvent(new Event('click'));
32 | expect(component.onGoToCurrent.emit).toHaveBeenCalledWith();
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/.angular-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "project": {
4 | "name": "ng2-date-picker"
5 | },
6 | "apps": [
7 | {
8 | "root": "src",
9 | "outDir": "dist",
10 | "assets": [
11 | "assets",
12 | "favicon.ico"
13 | ],
14 | "index": "index.html",
15 | "main": "main.ts",
16 | "polyfills": "polyfills.ts",
17 | "test": "test.ts",
18 | "tsconfig": "tsconfig.app.json",
19 | "testTsconfig": "tsconfig.spec.json",
20 | "prefix": "dp",
21 | "styles": [
22 | "styles.less"
23 | ],
24 | "scripts": [],
25 | "environmentSource": "environments/environment.ts",
26 | "environments": {
27 | "dev": "environments/environment.ts",
28 | "prod": "environments/environment.prod.ts"
29 | }
30 | }
31 | ],
32 | "e2e": {
33 | "protractor": {
34 | "config": "./protractor.conf.js"
35 | }
36 | },
37 | "lint": [
38 | {
39 | "project": "src/tsconfig.app.json"
40 | },
41 | {
42 | "project": "src/tsconfig.spec.json"
43 | },
44 | {
45 | "project": "e2e/tsconfig.e2e.json"
46 | }
47 | ],
48 | "test": {
49 | "karma": {
50 | "config": "./karma.conf.js"
51 | }
52 | },
53 | "defaults": {
54 | "styleExt": "less",
55 | "component": {}
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/app/month-calendar/month-calendar.component.html:
--------------------------------------------------------------------------------
1 |
33 |
--------------------------------------------------------------------------------
/e2e/move-to-date-api-e2e.spec.ts:
--------------------------------------------------------------------------------
1 | import {DemoPage} from './app.po';
2 |
3 | describe('Move to date api', () => {
4 |
5 | let page: DemoPage;
6 |
7 | beforeEach(() => {
8 | page = new DemoPage();
9 | page.navigateTo();
10 | });
11 |
12 | it('should move to date API on day', () => {
13 | const runner = (menuItem, input, isPicker, cont) => {
14 | menuItem.click();
15 | page.moveCalendarTo.click();
16 |
17 | if (isPicker) {
18 | input.click();
19 | }
20 |
21 | expect(cont.getText()).toContain('1987');
22 | };
23 |
24 | runner(page.dayPickerMenu, page.dayPickerInput, true, page.dayCalendarNavHeaderBtn);
25 | runner(page.dayDirectiveMenu, page.dayDirectiveInput, true, page.dayCalendarNavHeaderBtn);
26 | runner(page.dayInlineMenu, null, false, page.dayCalendarNavHeaderBtnInline);
27 |
28 | runner(page.daytimePickerMenu, page.daytimePickerInput, true, page.dayCalendarNavHeaderBtn);
29 | runner(page.daytimeDirectiveMenu, page.daytimeDirectiveInput, true, page.dayCalendarNavHeaderBtn);
30 | runner(page.daytimeInlineMenu, null, false, page.dayTimeCalendarNavHeaderBtnInline);
31 |
32 | runner(page.monthPickerMenu, page.monthPickerInput, true, page.navHeader);
33 | runner(page.monthDirectiveMenu, page.monthDirectiveInput, true, page.navHeader);
34 | runner(page.monthInlineMenu, null, false, page.monthCalendarNavHeaderInline);
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/0.13/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular/cli'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular/cli/plugins/karma')
14 | ],
15 | client:{
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | files: [
19 | { pattern: './src/test.ts', watched: false }
20 | ],
21 | preprocessors: {
22 | './src/test.ts': ['@angular/cli']
23 | },
24 | mime: {
25 | 'text/x-typescript': ['ts','tsx']
26 | },
27 | coverageIstanbulReporter: {
28 | reports: [ 'html', 'lcovonly' ],
29 | fixWebpackSourcePaths: true
30 | },
31 | angularCli: {
32 | environment: 'dev'
33 | },
34 | reporters: config.angularCli && config.angularCli.codeCoverage
35 | ? ['progress', 'coverage-istanbul']
36 | : ['progress', 'kjhtml'],
37 | port: 9876,
38 | colors: true,
39 | logLevel: config.LOG_INFO,
40 | autoWatch: true,
41 | browsers: ['Chrome'],
42 | singleRun: false
43 | });
44 | };
45 |
--------------------------------------------------------------------------------
/src/app/date-picker/date-picker-config.model.ts:
--------------------------------------------------------------------------------
1 | import {TDrops, TOpens} from '../common/types/poistions.type';
2 | import {IDayCalendarConfig, IDayCalendarConfigInternal} from '../day-calendar/day-calendar-config.model';
3 | import {IMonthCalendarConfig, IMonthCalendarConfigInternal} from '../month-calendar/month-calendar-config';
4 | import {ITimeSelectConfig, ITimeSelectConfigInternal} from '../time-select/time-select-config.model';
5 |
6 | export interface IConfig {
7 | closeOnSelect?: boolean;
8 | closeOnSelectDelay?: number;
9 | openOnFocus?: boolean;
10 | openOnClick?: boolean;
11 | onOpenDelay?: number;
12 | disableKeypress?: boolean;
13 | appendTo?: string | HTMLElement;
14 | inputElementContainer?: HTMLElement | string;
15 | drops?: TDrops;
16 | opens?: TOpens;
17 | hideInputContainer?: boolean;
18 | hideOnOutsideClick?: boolean;
19 | }
20 |
21 | export interface IDatePickerConfig extends IConfig,
22 | IDayCalendarConfig,
23 | IMonthCalendarConfig,
24 | ITimeSelectConfig {
25 |
26 | }
27 |
28 | export interface IDatePickerConfigInternal extends IConfig,
29 | IDayCalendarConfigInternal,
30 | IMonthCalendarConfigInternal,
31 | ITimeSelectConfigInternal {
32 | }
33 |
--------------------------------------------------------------------------------
/src/app/date-picker/date-picker-directive.service.ts:
--------------------------------------------------------------------------------
1 | import {UtilsService} from '../common/services/utils/utils.service';
2 | import {IDatePickerDirectiveConfig} from './date-picker-directive-config.model';
3 | import {ElementRef, Injectable} from '@angular/core';
4 |
5 | @Injectable()
6 | export class DatePickerDirectiveService {
7 | constructor(public utilsService: UtilsService) {
8 | }
9 |
10 | convertToHTMLElement(attachTo: ElementRef | string, baseElement: HTMLElement): HTMLElement {
11 | if (typeof attachTo === 'string') {
12 | return this.utilsService.closestParent(baseElement, attachTo);
13 | } else if (attachTo) {
14 | return attachTo.nativeElement;
15 | }
16 |
17 | return undefined;
18 | }
19 |
20 | getConfig(config: IDatePickerDirectiveConfig = {},
21 | baseElement?: ElementRef,
22 | attachTo?: ElementRef | string): IDatePickerDirectiveConfig {
23 | const _config: IDatePickerDirectiveConfig = {...config};
24 | _config.hideInputContainer = true;
25 |
26 | let native;
27 |
28 | if (config.inputElementContainer) {
29 | native = this.utilsService.getNativeElement(config.inputElementContainer);
30 | } else {
31 | native = baseElement ? baseElement.nativeElement : null;
32 | }
33 |
34 | if (native) {
35 | _config.inputElementContainer = attachTo
36 | ? this.convertToHTMLElement(attachTo, native)
37 | : native;
38 | }
39 |
40 | return _config;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/app/day-calendar/day-calendar-config.model.ts:
--------------------------------------------------------------------------------
1 | import {ICalendar, ICalendarInternal} from '../common/models/calendar.model';
2 | import {WeekDays} from '../common/types/week-days.type';
3 | import {Moment} from 'jalali-moment';
4 | import {ECalendarValue} from '../common/types/calendar-value-enum';
5 |
6 | export interface IConfig {
7 | isDayDisabledCallback?: (date: Moment) => boolean;
8 | isMonthDisabledCallback?: (date: Moment) => boolean;
9 | weekDayFormat?: string;
10 | weekDayFormatter?: (dayIndex: number) => string;
11 | showNearMonthDays?: boolean;
12 | showWeekNumbers?: boolean;
13 | firstDayOfWeek?: WeekDays;
14 | format?: string;
15 | allowMultiSelect?: boolean;
16 | monthFormat?: string;
17 | monthFormatter?: (month: Moment) => string;
18 | enableMonthSelector?: boolean;
19 | yearFormat?: string;
20 | yearFormatter?: (year: Moment) => string;
21 | dayBtnFormat?: string;
22 | dayBtnFormatter?: (day: Moment) => string;
23 | dayBtnCssClassCallback?: (day: Moment) => string;
24 | monthBtnFormat?: string;
25 | monthBtnFormatter?: (day: Moment) => string;
26 | monthBtnCssClassCallback?: (day: Moment) => string;
27 | multipleYearsNavigateBy?: number;
28 | showMultipleYearsNavigation?: boolean;
29 | returnedValueType?: ECalendarValue;
30 | showGoToCurrent?: boolean;
31 | unSelectOnClick?: boolean;
32 | }
33 |
34 | export interface IDayCalendarConfig extends IConfig,
35 | ICalendar {
36 | }
37 |
38 | export interface IDayCalendarConfigInternal extends IConfig,
39 | ICalendarInternal {
40 | }
41 |
--------------------------------------------------------------------------------
/src/app/time-select/time-select.component.less:
--------------------------------------------------------------------------------
1 | @import '../common/styles/variables';
2 |
3 | & {
4 |
5 | dp-time-select {
6 | display: inline-block;
7 |
8 | .dp-time-select-controls {
9 | margin: 0;
10 | padding: 0;
11 | text-align: center;
12 | line-height: normal;
13 | background: @c-white;
14 | }
15 |
16 | .dp-time-select-control {
17 | display: inline-block;
18 | //width: 35px;
19 | margin: 0 auto;
20 | vertical-align: middle;
21 | font-size: inherit;
22 | letter-spacing: 1px;
23 | }
24 |
25 | .dp-time-select-control-up, .dp-time-select-control-down {
26 | position: relative;
27 | display: block;
28 | width: 24px;
29 | height: 24px;
30 | margin: 3px auto;
31 | cursor: pointer;
32 | color:@c-light-gray;.arrow(0deg)
33 | }
34 |
35 | .dp-time-select-control-up::before {
36 | transform: rotate(-45deg);
37 | top: 4px;
38 | }
39 |
40 | .dp-time-select-control-down::before {
41 | transform: rotate(135deg);
42 | }
43 |
44 | .dp-time-select-separator {
45 | width: 5px;
46 | }
47 |
48 | &.dp-material {
49 | .dp-time-select-control-up, .dp-time-select-control-down {
50 | box-sizing: border-box;
51 | background: transparent;
52 | border: none;
53 | outline: none;
54 | border-radius: 50%;
55 |
56 | &::before {
57 | left: 0;
58 | }
59 |
60 | &:hover {
61 | background: @c-light-gray;
62 | color: @c-white;
63 | }
64 | }
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/app/time-select/time-select.component.spec.ts:
--------------------------------------------------------------------------------
1 | import {async, ComponentFixture, TestBed} from '@angular/core/testing';
2 | import {UtilsService} from '../common/services/utils/utils.service';
3 | import {CalendarNavComponent} from '../calendar-nav/calendar-nav.component';
4 | import * as momentNs from 'jalali-moment';
5 | import {TimeSelectComponent} from './time-select.component';
6 | import {TimeSelectService} from './time-select.service';
7 | import {MonthCalendarComponent} from '../month-calendar/month-calendar.component';
8 | const moment = momentNs;
9 |
10 | describe('Component: TimeSelectComponent', () => {
11 | let component: TimeSelectComponent;
12 | let fixture: ComponentFixture;
13 |
14 | beforeEach(async(() => {
15 | TestBed.configureTestingModule({
16 | declarations: [TimeSelectComponent, CalendarNavComponent, MonthCalendarComponent],
17 | providers: [TimeSelectService, UtilsService]
18 | }).compileComponents();
19 | }));
20 |
21 | beforeEach(() => {
22 | fixture = TestBed.createComponent(TimeSelectComponent);
23 | component = fixture.componentInstance;
24 | component.config = component.timeSelectService.getConfig({});
25 | component.config.locale = 'en';
26 | fixture.detectChanges();
27 | });
28 |
29 | it('should create', () => {
30 | expect(component).toBeTruthy();
31 | });
32 |
33 | it('should calculate time parts', () => {
34 | component.selected = moment('5:33:44', 'H:mm:ss');
35 | expect(component.hours).toEqual('05');
36 | expect(component.minutes).toEqual('33');
37 | expect(component.seconds).toEqual('44');
38 | expect(component.meridiem).toEqual('AM');
39 | });
40 | });
41 |
--------------------------------------------------------------------------------
/src/app/date-picker/date-picker.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import {UtilsService} from '../common/services/utils/utils.service';
2 | import {DatePickerDirective} from './date-picker.directive';
3 | import {Component} from '@angular/core';
4 | import {inject, TestBed} from '@angular/core/testing';
5 |
6 | @Component({
7 | template: ''
8 | })
9 | class TestComponent {
10 | }
11 |
12 | describe('Directive: DpDayPicker', () => {
13 | beforeEach(() => {
14 | TestBed.configureTestingModule({
15 | declarations: [TestComponent],
16 | providers: [UtilsService]
17 | });
18 | });
19 |
20 | const directive = new DatePickerDirective(null, null, null, null, null, null);
21 |
22 | it('should create an instance', () => {
23 | expect(directive).toBeTruthy();
24 | });
25 |
26 | it('should check UtilsService.closestParent', inject([UtilsService], (service: UtilsService) => {
27 | const root = TestBed.createComponent(TestComponent).nativeElement;
28 | root.innerHTML = `
29 |
30 |
31 |
32 |
33 |
34 | `;
35 | const inputElement = root.querySelector('input');
36 |
37 | const wrapperElement = service.closestParent(inputElement, '.wrapper');
38 | expect(wrapperElement.tagName).toBe('SPAN');
39 | expect(wrapperElement.className).toBe('wrapper');
40 |
41 | const topElement = service.closestParent(inputElement, '#top');
42 | expect(topElement.tagName).toBe('DIV');
43 | expect(topElement.id).toBe('top');
44 |
45 | const notFoundElement = service.closestParent(inputElement, '.notFound');
46 | expect(notFoundElement).toBeUndefined();
47 | }));
48 | });
49 |
--------------------------------------------------------------------------------
/src/app/month-calendar/month-calendar.component.less:
--------------------------------------------------------------------------------
1 | @import '../common/styles/variables';
2 |
3 | & {
4 | dp-month-calendar {
5 | display: inline-block;
6 |
7 | .dp-month-calendar-container {
8 | background: @c-white;
9 | }
10 |
11 | .dp-calendar-wrapper {
12 | //border: 1px solid @c-black;
13 | &.rtl{
14 | direction:rtl;
15 | }}
16 |
17 | .dp-calendar-month {
18 | box-sizing: border-box;
19 | width: @basic-height * 11 / 6;
20 | height: @basic-height * 11 / 6;
21 | cursor: pointer;
22 |
23 | &.dp-selected {
24 | background: @c-primary;
25 | color: @c-white;
26 | }
27 | }
28 |
29 | &.dp-material {
30 | .dp-calendar-weekday {
31 | height: @basic-height - 5px;
32 | width: @basic-height;
33 | line-height: @basic-height - 5px;
34 | background: @c-light-gray;
35 | border: 1px solid @c-light-gray;
36 | }
37 |
38 | .dp-calendar-wrapper {
39 | padding:15px;
40 | //border: 1px solid @c-light-gray;
41 | }
42 |
43 | .dp-calendar-month {
44 | box-sizing: border-box;
45 | background: @c-white;
46 | border-radius: 0;
47 | transition:border-radius 0.1s ease;
48 | border: none;
49 | outline: none;
50 | font-size: 0.7rem;
51 |
52 | &:hover {
53 | border-radius: 50%;background: @c-light-gray;
54 | }
55 | }
56 |
57 | .dp-selected {
58 | background: @c-primary;
59 | color: @c-white;
60 | border-radius: 50%;
61 | &:hover {
62 | background: @c-primary;
63 | }
64 | }
65 |
66 | .dp-current-month {
67 | border-radius: 50%;
68 | border: 1px solid @c-primary;
69 | padding: 0;
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/app/calendar-nav/calendar-nav.component.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | Component,
4 | EventEmitter,
5 | HostBinding,
6 | Input,
7 | Output,
8 | ViewEncapsulation
9 | } from '@angular/core';
10 |
11 | @Component({
12 | selector: 'dp-calendar-nav',
13 | templateUrl: './calendar-nav.component.html',
14 | styleUrls: ['./calendar-nav.component.less'],
15 | encapsulation: ViewEncapsulation.None,
16 | changeDetection: ChangeDetectionStrategy.OnPush
17 | })
18 | export class CalendarNavComponent {
19 | @Input() label: string;
20 | @Input() isLabelClickable: boolean = false;
21 | @Input() showLeftNav: boolean = true;
22 | @Input() showLeftSecondaryNav: boolean = false;
23 | @Input() showRightNav: boolean = true;
24 | @Input() showRightSecondaryNav: boolean = false;
25 | @Input() leftNavDisabled: boolean = false;
26 | @Input() leftSecondaryNavDisabled: boolean = false;
27 | @Input() rightNavDisabled: boolean = false;
28 | @Input() rightSecondaryNavDisabled: boolean = false;
29 | @Input() showGoToCurrent: boolean = true;
30 | @HostBinding('class') @Input() theme: string;
31 |
32 | @Output() onLeftNav: EventEmitter = new EventEmitter();
33 | @Output() onLeftSecondaryNav: EventEmitter = new EventEmitter();
34 | @Output() onRightNav: EventEmitter = new EventEmitter();
35 | @Output() onRightSecondaryNav: EventEmitter = new EventEmitter();
36 | @Output() onLabelClick: EventEmitter = new EventEmitter();
37 | @Output() onGoToCurrent: EventEmitter = new EventEmitter();
38 |
39 | leftNavClicked() {
40 | this.onLeftNav.emit();
41 | }
42 |
43 | leftSecondaryNavClicked() {
44 | this.onLeftSecondaryNav.emit();
45 | }
46 |
47 | rightNavClicked() {
48 | this.onRightNav.emit();
49 | }
50 |
51 | rightSecondaryNavClicked() {
52 | this.onRightSecondaryNav.emit();
53 | }
54 |
55 | labelClicked() {
56 | this.onLabelClick.emit();
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/app/calendar-nav/calendar-nav.component.html:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
18 |
24 |
31 |
32 |
37 |
38 |
45 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/app/date-picker.module.ts:
--------------------------------------------------------------------------------
1 | import {NgModule} from '@angular/core';
2 | import {FormsModule} from '@angular/forms';
3 | import {CommonModule} from '@angular/common';
4 | import {DomHelper} from './common/services/dom-appender/dom-appender.service';
5 | import {UtilsService} from './common/services/utils/utils.service';
6 | import {DatePickerComponent} from './date-picker/date-picker.component';
7 | import {DatePickerDirective} from './date-picker/date-picker.directive';
8 | import {DayCalendarComponent} from './day-calendar/day-calendar.component';
9 | import {MonthCalendarComponent} from './month-calendar/month-calendar.component';
10 | import {TimeSelectComponent} from './time-select/time-select.component';
11 | import {CalendarNavComponent} from './calendar-nav/calendar-nav.component';
12 | import {DayTimeCalendarComponent} from './day-time-calendar/day-time-calendar.component';
13 | export {DatePickerComponent} from './date-picker/date-picker.component';
14 | export {DatePickerDirective} from './date-picker/date-picker.directive';
15 | export {DayCalendarComponent} from './day-calendar/day-calendar.component';
16 | export {DayTimeCalendarComponent} from './day-time-calendar/day-time-calendar.component';
17 | export {TimeSelectComponent} from './time-select/time-select.component';
18 | export {MonthCalendarComponent} from './month-calendar/month-calendar.component';
19 |
20 | @NgModule({
21 | providers: [
22 | DomHelper,
23 | UtilsService
24 | ],
25 | declarations: [
26 | DatePickerComponent,
27 | DatePickerDirective,
28 | DayCalendarComponent,
29 | MonthCalendarComponent,
30 | CalendarNavComponent,
31 | TimeSelectComponent,
32 | DayTimeCalendarComponent
33 | ],
34 | entryComponents: [
35 | DatePickerComponent
36 | ],
37 | imports: [
38 | CommonModule,
39 | FormsModule
40 | ],
41 | exports: [
42 | DatePickerComponent,
43 | DatePickerDirective,
44 | MonthCalendarComponent,
45 | DayCalendarComponent,
46 | TimeSelectComponent,
47 | DayTimeCalendarComponent
48 | ]
49 | })
50 | export class DpDatePickerModule {
51 | }
52 |
--------------------------------------------------------------------------------
/src/app/day-time-calendar/day-time-calendar.service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 | import * as momentNs from 'jalali-moment';
3 | import {Moment} from 'jalali-moment';
4 |
5 | import {UtilsService} from '../common/services/utils/utils.service';
6 | import {DayCalendarService} from '../day-calendar/day-calendar.service';
7 | import {TimeSelectService} from '../time-select/time-select.service';
8 | import {IDayTimeCalendarConfig} from './day-time-calendar-config.model';
9 | const moment = momentNs;
10 |
11 | const DAY_FORMAT = 'YYYYMMDD';
12 | const TIME_FORMAT = 'HH:mm:ss';
13 | const COMBINED_FORMAT = DAY_FORMAT + TIME_FORMAT;
14 |
15 | @Injectable()
16 | export class DayTimeCalendarService {
17 | readonly DEFAULT_CONFIG: IDayTimeCalendarConfig = {
18 | locale: 'fa'
19 | };
20 |
21 | constructor(private utilsService: UtilsService,
22 | private dayCalendarService: DayCalendarService,
23 | private timeSelectService: TimeSelectService) {
24 | }
25 |
26 | getConfig(config: IDayTimeCalendarConfig): IDayTimeCalendarConfig {
27 | const _config = {
28 | ...this.DEFAULT_CONFIG,
29 | ...this.timeSelectService.getConfig(config),
30 | ...this.dayCalendarService.getConfig(config)
31 | };
32 |
33 | // moment.locale(config.locale);
34 |
35 | return _config;
36 | }
37 |
38 | updateDay(current: Moment, day: Moment, config: IDayTimeCalendarConfig): Moment {
39 | const time = current ? current : moment();
40 | let updated = moment.from(day.format(DAY_FORMAT) + time.format(TIME_FORMAT), day.locale(), COMBINED_FORMAT)
41 |
42 | if (config.min) {
43 | const min = config.min;
44 | updated = min.isAfter(updated) ? min : updated;
45 | }
46 |
47 | if (config.max) {
48 | const max = config.max;
49 | updated = max.isBefore(updated) ? max : updated;
50 | }
51 |
52 | return updated;
53 | }
54 |
55 | updateTime(current: Moment, time: Moment): Moment {
56 | const day = current ? current : moment();
57 |
58 | return moment.from(day.format(DAY_FORMAT) + time.format(TIME_FORMAT), day.locale(), COMBINED_FORMAT);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/e2e/locale-e2e.spec.ts:
--------------------------------------------------------------------------------
1 | import {DemoPage} from './app.po';
2 |
3 | describe('Locales', () => {
4 | let page: DemoPage;
5 |
6 | beforeEach(() => {
7 | page = new DemoPage();
8 | page.navigateTo();
9 | });
10 |
11 | it('should check day time picker locale', () => {
12 | page.hebrewLocale.click();
13 |
14 | page.daytimePickerMenu.click();
15 | page.daytimePickerInput.click();
16 | expect(page.weekDayNames.getText()).toEqual(['א׳ב׳ג׳ד׳ה׳ו׳ש׳']);
17 |
18 | page.daytimeInlineMenu.click();
19 | expect(page.weekDayInline.getText()).toEqual(['א׳ב׳ג׳ד׳ה׳ו׳ש׳']);
20 |
21 | page.daytimeDirectiveMenu.click();
22 | page.daytimeDirectiveInput.click();
23 | expect(page.weekDayNames.getText()).toEqual(['א׳ב׳ג׳ד׳ה׳ו׳ש׳']);
24 |
25 | page.dayPickerMenu.click();
26 | page.dayPickerInput.click();
27 | expect(page.weekDayNames.getText()).toEqual(['א׳ב׳ג׳ד׳ה׳ו׳ש׳']);
28 |
29 | page.dayInlineMenu.click();
30 | expect(page.weekDayInline.getText()).toEqual(['א׳ב׳ג׳ד׳ה׳ו׳ש׳']);
31 |
32 | page.dayDirectiveMenu.click();
33 | page.dayDirectiveInput.click();
34 | expect(page.weekDayNames.getText()).toEqual(['א׳ב׳ג׳ד׳ה׳ו׳ש׳']);
35 |
36 | page.monthPickerMenu.click();
37 | page.monthPickerInput.click();
38 | expect(page.calendarFirstMonthOfYear.getText()).toEqual('ינו׳');
39 |
40 | page.monthInlineMenu.click();
41 | expect(page.calendarFirstMonthOfYearInline.getText()).toEqual('ינו׳');
42 |
43 | page.monthDirectiveMenu.click();
44 | page.monthDirectiveInput.click();
45 | expect(page.calendarFirstMonthOfYear.getText()).toEqual('ינו׳');
46 |
47 | page.timePickerMenu.click();
48 | page.timePickerInput.click();
49 | expect(page.meridiemDisplay.getText()).toMatch(/(בערב|בבוקר|לפני הצהריים|אחרי הצהריים|לפנות בוקר)/);
50 |
51 | page.timeDirectiveMenu.click();
52 | page.timeSelectDirectiveInput.click();
53 | expect(page.meridiemDisplay.getText()).toMatch(/(בערב|בבוקר|לפני הצהריים|אחרי הצהריים|לפנות בוקר)/);
54 |
55 | page.timeInlineMenu.click();
56 | expect(page.meridiemDisplayInline.getText()).toMatch(/(בערב|בבוקר|לפני הצהריים|אחרי הצהריים|לפנות בוקר)/);
57 | });
58 | });
59 |
--------------------------------------------------------------------------------
/src/app/day-time-calendar/day-time-calendar.component.spec.ts:
--------------------------------------------------------------------------------
1 | import {async, ComponentFixture, TestBed} from '@angular/core/testing';
2 | import {FormsModule} from '@angular/forms';
3 | import {DayTimeCalendarComponent} from './day-time-calendar.component';
4 | import {DayTimeCalendarService} from './day-time-calendar.service';
5 | import {DayCalendarComponent} from '../day-calendar/day-calendar.component';
6 | import {TimeSelectComponent} from '../time-select/time-select.component';
7 | import {TimeSelectService} from '../time-select/time-select.service';
8 | import {DayCalendarService} from '../day-calendar/day-calendar.service';
9 | import {UtilsService} from '../common/services/utils/utils.service';
10 | import {MonthCalendarComponent} from '../month-calendar/month-calendar.component';
11 | import {CalendarNavComponent} from '../calendar-nav/calendar-nav.component';
12 |
13 | describe('Component: DayTimeCalendarComponent', () => {
14 | let component: DayTimeCalendarComponent;
15 | let fixture: ComponentFixture;
16 |
17 | beforeEach(async(() => {
18 | TestBed.configureTestingModule({
19 | imports: [ FormsModule ],
20 | declarations: [
21 | DayTimeCalendarComponent,
22 | DayCalendarComponent,
23 | TimeSelectComponent,
24 | CalendarNavComponent,
25 | MonthCalendarComponent
26 | ],
27 | providers: [
28 | DayTimeCalendarService,
29 | DayCalendarService,
30 | TimeSelectService,
31 | UtilsService
32 | ]
33 | }).compileComponents();
34 | }));
35 |
36 | beforeEach(() => {
37 | fixture = TestBed.createComponent(DayTimeCalendarComponent);
38 | component = fixture.componentInstance;
39 | component.config = component.dayTimeCalendarService.getConfig({});
40 | fixture.detectChanges();
41 | });
42 |
43 | it('should create', () => {
44 | expect(component).toBeTruthy();
45 | });
46 |
47 | it('should emit event goToCurrent when nav emit', () => {
48 | spyOn(component.onGoToCurrent, 'emit');
49 | component.dayCalendarRef.onGoToCurrent.emit();
50 | expect(component.onGoToCurrent.emit).toHaveBeenCalledWith();
51 | });
52 | });
53 |
--------------------------------------------------------------------------------
/src/app/date-picker/date-picker.service.spec.ts:
--------------------------------------------------------------------------------
1 | import {inject, TestBed} from '@angular/core/testing';
2 | import {DatePickerService} from './date-picker.service';
3 | import * as momentNs from 'jalali-moment';
4 | import {Moment} from 'jalali-moment';
5 | import {UtilsService} from '../common/services/utils/utils.service';
6 | import {DayTimeCalendarService} from '../day-time-calendar/day-time-calendar.service';
7 | import {DayCalendarService} from '../day-calendar/day-calendar.service';
8 | import {TimeSelectService} from '../time-select/time-select.service';
9 | const moment = momentNs;
10 |
11 | describe('Service: DatePicker', () => {
12 | beforeEach(() => {
13 | TestBed.configureTestingModule({
14 | providers: [
15 | DatePickerService,
16 | DayTimeCalendarService,
17 | DayCalendarService,
18 | TimeSelectService,
19 | UtilsService
20 | ]
21 | });
22 | });
23 |
24 | it('should check getConfig method for dates format', inject([DatePickerService],
25 | (service: DatePickerService) => {
26 | const config1 = service.getConfig({
27 | min: '2016-10-25',
28 | max: '2017-10-25',
29 | format: 'YYYY-MM-DD'
30 | });
31 |
32 | expect((config1.min).isSame(moment('2016-10-25', 'YYYY-MM-DD'), 'day')).toBe(true);
33 | expect((config1.max).isSame(moment('2017-10-25', 'YYYY-MM-DD'), 'day')).toBe(true);
34 |
35 | const config2 = service.getConfig({
36 | min: moment('2016-10-25', 'YYYY-MM-DD'),
37 | max: moment('2017-10-25', 'YYYY-MM-DD')
38 | });
39 |
40 | expect((config2.min).isSame(moment('2016-10-25', 'YYYY-MM-DD'), 'day')).toBe(true);
41 | expect((config2.max).isSame(moment('2017-10-25', 'YYYY-MM-DD'), 'day')).toBe(true);
42 |
43 | expect(service.getConfig({}, 'time').format).toEqual('HH:mm:ss');
44 | // expect(service.getConfig({}, 'daytime').format).toEqual('DD-MM-YYYY HH:mm:ss');
45 | // expect(service.getConfig({}, 'month').format).toEqual('MMM, YYYY');
46 | // expect(service.getConfig({}, 'day').format).toEqual('DD-MM-YYYY');
47 | // expect(service.getConfig({}).format).toEqual('DD-MM-YYYY HH:mm:ss');
48 | }));
49 | });
50 |
--------------------------------------------------------------------------------
/src/app/day-calendar/day-calendar.component.html:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
17 |
18 |
21 |
22 |
23 |
24 |
27 |
28 |
37 |
38 |
39 |
40 |
41 |
52 |
53 |
--------------------------------------------------------------------------------
/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/docs/ts/latest/guide/browser-support.html
15 | */
16 | /***************************************************************************************************
17 | * BROWSER POLYFILLS
18 | */
19 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/
20 | import 'core-js/es6/symbol';
21 | import 'core-js/es6/object';
22 | import 'core-js/es6/function';
23 | import 'core-js/es6/parse-int';
24 | import 'core-js/es6/parse-float';
25 | import 'core-js/es6/number';
26 | import 'core-js/es6/math';
27 | import 'core-js/es6/string';
28 | import 'core-js/es6/date';
29 | import 'core-js/es6/array';
30 | import 'core-js/es6/regexp';
31 | import 'core-js/es6/map';
32 | import 'core-js/es6/set';
33 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
34 | import 'classlist.js'; // Run `npm install --save classlist.js`.
35 | /** IE10 and IE11 requires the following to support `@angular/animation`. */
36 | import 'web-animations-js'; // Run `npm install --save web-animations-js`.
37 | /** Evergreen browsers require these. **/
38 | import 'core-js/es6/reflect';
39 | import 'core-js/es7/reflect';
40 | /** ALL Firefox browsers require the following to support `@angular/animation`. **/
41 | import 'web-animations-js'; // Run `npm install --save web-animations-js`.
42 | /***************************************************************************************************
43 | * Zone JS is required by Angular itself.
44 | */
45 | import 'zone.js/dist/zone'; // Included with Angular CLI.
46 |
47 | /***************************************************************************************************
48 | * APPLICATION IMPORTS
49 | */
50 |
51 | /**
52 | * Date, currency, decimal and percent pipes.
53 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
54 | */
55 | // import 'intl'; // Run `npm install --save intl`.
56 |
--------------------------------------------------------------------------------
/src/app/time-select/time-select.component.html:
--------------------------------------------------------------------------------
1 |
2 | -
3 |
8 |
10 |
11 |
15 |
16 | -
18 |
19 | -
20 |
24 |
26 |
27 |
30 |
31 |
32 | -
34 |
35 | -
36 |
40 |
42 |
43 |
47 |
48 |
49 | -
50 |
54 |
56 |
57 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/README.fa.md:
--------------------------------------------------------------------------------
1 | # Angular Jalali Date Picker
2 | انتخاب گر تاریخ شمسی در انگولار ۴
3 | [دمو](https://fingerpich.github.io/jalali-angular-datepicker/)
4 |
5 | [](https://travis-ci.org/fingerpich/jalali-angular-datepicker)
6 | [](https://badge.fury.io/js/ng2-jalali-date-picker)
7 | [](http://packagequality.com/#?package=ng2-jalali-date-picker)
8 | [](https://david-dm.org/fingerpich/jalali-angular-datepicker)
9 | [](https://david-dm.org/fingerpich/jalali-angular-datepicker?type=dev)
10 | [](https://www.codacy.com/app/zarei-bs/jalali-angular-datepicker?utm_source=github.com&utm_medium=referral&utm_content=fingerpich/jalali-angular-datepicker&utm_campaign=Badge_Grade)
11 | [](https://www.codacy.com/app/zarei-bs/jalali-angular-datepicker?utm_source=github.com&utm_medium=referral&utm_content=fingerpich/jalali-angular-datepicker&utm_campaign=Badge_Coverage)
12 |
13 | ## تصاویر
14 |
15 | 

16 |
17 | ## فهرست مطالب :
18 |
19 | − [نصب کتابخانه](https://github.com/fingerpich/jalali-angular-datepicker#installation)
20 | - [نحوه استفاده از انتخابگر تاریخ شمسی](https://github.com/fingerpich/jalali-angular-datepicker#how_to_use)
21 | - [نمایش تاریخ انتخاب شده به صورت های مختلف در تاریخ خورشیدی](https://github.com/fingerpich/jalali-angular-datepicker#how_to_use_the_output_as_a_jalali_shamsi_date)
22 | - [استفاده یا تغییر صفات انتخابگر تاریخ جلالی](https://github.com/fingerpich/jalali-angular-datepicker#Attributes)
23 | - [تنظمات انتخابگر تاریخ فارسی](https://github.com/fingerpich/jalali-angular-datepicker#Configuration)
24 | - [نحوه استفاده از انتخاب گر در داخل کلاس ها](https://github.com/fingerpich/jalali-angular-datepicker#API)
25 | - [نحوه استفاده از انتخابگر به صورت همیشه ظاهر](https://github.com/fingerpich/jalali-angular-datepicker#Inline_-_Day_Calendar)
26 | - [نحوه استفاده از انتخابگر مستقل از المنت ورودی](https://github.com/fingerpich/jalali-angular-datepicker#Directive)
27 |
--------------------------------------------------------------------------------
/e2e/directive-e2e.spec.ts:
--------------------------------------------------------------------------------
1 | import {DemoPage} from './app.po';
2 | import {browser} from 'protractor';
3 | import * as moment from 'jalali-moment';
4 | import {Key} from 'selenium-webdriver';
5 |
6 | describe('dpDayPicker directive', () => {
7 | let page: DemoPage;
8 |
9 | beforeEach(() => {
10 | page = new DemoPage();
11 | page.navigateTo();
12 |
13 | page.dayDirectiveMenu.click();
14 | });
15 |
16 | it('should check that the popup appended to body', () => {
17 | page.dayDirectiveInput.click();
18 | expect(page.datePickerPopup.isDisplayed()).toBe(true);
19 | page.clickOnBody();
20 | expect(page.datePickerPopup.isDisplayed()).toBe(false);
21 | });
22 |
23 | it('should make sure that day directive keeps the prev state of the calendar', () => {
24 | page.dayDirectiveInput.click();
25 | page.dayCalendarLeftNavBtn.click();
26 | page.clickOnBody();
27 |
28 | page.dayDirectiveInput.click();
29 | expect(page.dayCalendarNavHeaderBtn.getText())
30 | .toEqual(moment().subtract(1, 'month').format('MMM, YYYY'));
31 | });
32 |
33 | it('should check that the theme is added and removed', () => {
34 | page.themeOnRadio.click();
35 | expect(page.datePickerPopup.getAttribute('class')).toContain('dp-material');
36 | page.themeOffRadio.click();
37 | expect(page.datePickerPopup.getAttribute('class')).not.toContain('dp-material');
38 | page.themeOnRadio.click();
39 | expect(page.datePickerPopup.getAttribute('class')).toContain('dp-material');
40 | });
41 |
42 | it('should check that the onOpenDelay is working', () => {
43 | page.onOpenDelayInput.clear();
44 | page.onOpenDelayInput.sendKeys(1000);
45 | page.scrollIntoView(page.openBtn);
46 | page.openBtn.click();
47 | expect(page.datePickerPopup.isDisplayed()).toBe(true);
48 | page.clickOnBody();
49 | browser.sleep(200);
50 | browser.waitForAngularEnabled(false);
51 | page.dayDirectiveInput.click();
52 | expect(page.datePickerPopup.isDisplayed()).toBe(false);
53 | browser.waitForAngularEnabled(true);
54 | browser.sleep(1000);
55 | expect(page.datePickerPopup.isDisplayed()).toBe(true);
56 | });
57 |
58 | it('should allow input to be modified from beginning', () => {
59 | page.dayDirectiveInput.sendKeys('10-04-2017');
60 | for (let i = 0; i < 11; i++) {
61 | page.dayDirectiveInput.sendKeys(Key.LEFT);
62 | }
63 | page.dayDirectiveInput.sendKeys(Key.DELETE);
64 | page.dayDirectiveInput.sendKeys('2');
65 | expect(page.dayDirectiveInput.getAttribute('value')).toBe('20-04-2017');
66 | expect(page.selectedDays.count()).toBe(1);
67 | expect(page.selectedDays.first().getText()).toBe('20');
68 | expect(page.dayCalendarNavHeaderBtn.getText()).toBe('Apr, 2017');
69 | });
70 | });
71 |
--------------------------------------------------------------------------------
/src/app/day-calendar/day-calendar.component.less:
--------------------------------------------------------------------------------
1 | @import '../common/styles/variables';
2 |
3 | & {
4 | dp-day-calendar {
5 | display: inline-block;
6 |
7 | .dp-day-calendar-container {
8 | background: @c-white;
9 | }
10 |
11 | .dp-calendar-wrapper {
12 | box-sizing: border-box;
13 | //border: 1px solid @c-black;
14 |
15 | .dp-calendar-weekday:first-child {
16 | border-left: none;
17 | }
18 | }
19 |
20 | .dp-weekdays {
21 | font-size: 15px;
22 | margin-bottom: 5px;
23 | }
24 |
25 | .dp-calendar-weekday {
26 | box-sizing: border-box;
27 | display: inline-block;
28 | width: @basic-height;
29 | text-align: center;
30 | border-left: 1px solid @c-black;
31 | border-bottom: 1px solid @c-black;
32 | }
33 |
34 | .dp-calendar-day {
35 | box-sizing: border-box;
36 | width: @basic-height;
37 | height: @basic-height;
38 | cursor: pointer;
39 | }
40 |
41 | .dp-selected {
42 | background: @c-primary;
43 | color: @c-white;
44 | }
45 |
46 | .dp-prev-month, .dp-next-month {
47 | opacity: 0.5;
48 | }
49 |
50 | .dp-hide-near-month {
51 | .dp-prev-month, .dp-next-month {
52 | visibility: hidden;
53 | }
54 | }
55 |
56 | .dp-week-number {
57 | position: absolute;
58 | font-size: 9px;
59 | }
60 |
61 | &.dp-material {
62 |
63 | .dp-calendar-weekday {
64 | height: @basic-height - 5px;
65 | width: @basic-height;
66 | line-height: @basic-height - 5px;
67 | color: @c-primary;
68 | border: none;
69 | font-size: 0.75rem;
70 | opacity: 0.6;
71 | }
72 | .dp-calendar-weekday:last-child{
73 | color:red;}
74 |
75 | .dp-calendar-wrapper {
76 | padding: 20px;
77 | &.rtl{
78 | direction:rtl;
79 | }
80 | }
81 |
82 | .dp-calendar-month,
83 | .dp-calendar-day {
84 | box-sizing: border-box;
85 | background: @c-white;
86 | border-radius: 0%;
87 | transition:border-radius 0.1s ease;
88 | border: none;
89 | outline: none;
90 | padding: 0;
91 |
92 | &:hover {
93 | background: @c-light-gray;
94 | border-radius: 50%;
95 | }
96 | }
97 |
98 | .dp-selected {
99 | border-radius: 50%;
100 | background: @c-primary;
101 | color: @c-white;
102 |
103 | &:hover {
104 | background: @c-primary;
105 | }
106 | }
107 |
108 | .dp-current-day {
109 | border-radius: 50%;
110 | border: 1px solid @c-primary;
111 | }
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/app/month-calendar/month-calendar.component.spec.ts:
--------------------------------------------------------------------------------
1 | import {async, ComponentFixture, TestBed} from '@angular/core/testing';
2 | import {MonthCalendarComponent} from './month-calendar.component';
3 | import {UtilsService} from '../common/services/utils/utils.service';
4 | import {CalendarNavComponent} from '../calendar-nav/calendar-nav.component';
5 | import {MonthCalendarService} from './month-calendar.service';
6 | import * as momentNs from 'jalali-moment';
7 | import {Moment} from 'jalali-moment';
8 | import {IMonth} from './month.model';
9 | const moment = momentNs;
10 |
11 | describe('Component: MonthCalendarComponent', () => {
12 | let component: MonthCalendarComponent;
13 | let fixture: ComponentFixture;
14 |
15 | beforeEach(async(() => {
16 | TestBed.configureTestingModule({
17 | declarations: [MonthCalendarComponent, CalendarNavComponent],
18 | providers: [MonthCalendarService, UtilsService]
19 | }).compileComponents();
20 | }));
21 |
22 | beforeEach(() => {
23 | fixture = TestBed.createComponent(MonthCalendarComponent);
24 | component = fixture.componentInstance;
25 | component.config = component.monthCalendarService.getConfig({});
26 | fixture.detectChanges();
27 | });
28 |
29 | it('should create', () => {
30 | expect(component).toBeTruthy();
31 | });
32 |
33 | describe('should have the right CSS classes for', () => {
34 | const defaultMonth: IMonth = {
35 | date: undefined,
36 | selected: false,
37 | currentMonth: false,
38 | disabled: false,
39 | text: ''
40 | };
41 | const defaultCssClasses: {[klass: string]: boolean} = {
42 | 'dp-selected': false,
43 | 'dp-current-month': false
44 | };
45 |
46 | it('the selected month', () => {
47 | expect(component.getMonthBtnCssClass({
48 | ...defaultMonth,
49 | selected: true
50 | } as IMonth)).toEqual({
51 | ...defaultCssClasses,
52 | 'dp-selected': true
53 | });
54 | });
55 |
56 | it('the current month', () => {
57 | expect(component.getMonthBtnCssClass({
58 | ...defaultMonth,
59 | currentMonth: true
60 | } as IMonth)).toEqual({
61 | ...defaultCssClasses,
62 | 'dp-current-month': true
63 | });
64 | });
65 |
66 | it('custom days', () => {
67 | component.componentConfig.monthBtnCssClassCallback = (day: Moment) => 'custom-class';
68 |
69 | expect(component.getMonthBtnCssClass({
70 | ...defaultMonth
71 | } as IMonth)).toEqual({
72 | ...defaultCssClasses,
73 | 'custom-class': true
74 | });
75 | });
76 |
77 | it('should emit event goToCurrent function called', () => {
78 | spyOn(component.onGoToCurrent, 'emit');
79 | component.goToCurrent();
80 | expect(component.onGoToCurrent.emit).toHaveBeenCalledWith();
81 | });
82 | });
83 | });
84 |
--------------------------------------------------------------------------------
/src/app/date-picker/date-picker.component.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
13 |
14 |
15 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/e2e/daypicker-e2e.spec.ts:
--------------------------------------------------------------------------------
1 | import {DemoPage} from './app.po';
2 |
3 | describe('dpDayPicker daytimePicker', () => {
4 | let page: DemoPage;
5 |
6 | beforeEach(() => {
7 | page = new DemoPage();
8 | page.navigateTo();
9 |
10 | page.dateFormatInput.clear();
11 | page.dateFormatInput.sendKeys('DD-MM-YYYY HH:mm:ss');
12 | page.daytimePickerMenu.click();
13 | });
14 |
15 | it('should check if min date validation is working', () => {
16 | page.minDateValidationPickerInput.clear();
17 | expect(page.minDateValidationMsg.isPresent()).toBe(false);
18 | page.minDateValidationPickerInput.sendKeys('10-04-2017 10:08:07');
19 | page.daytimePickerInput.sendKeys('10-04-2017 09:08:07');
20 | expect(page.minDateValidationMsg.getText()).toEqual('minDate invalid');
21 | page.minDateValidationPickerInput.clear();
22 | page.minDateValidationPickerInput.sendKeys('10-04-2017 09:08:07');
23 | expect(page.minDateValidationMsg.isPresent()).toBe(false);
24 | });
25 |
26 | it('should check if max date validation is working', () => {
27 | page.maxDateValidationPickerInput.clear();
28 | expect(page.maxDateValidationMsg.isPresent()).toBe(false);
29 | page.maxDateValidationPickerInput.sendKeys('12-04-2017 08:08:07');
30 | page.daytimePickerInput.sendKeys('12-04-2017 09:08:07');
31 | expect(page.maxDateValidationMsg.getText()).toEqual('maxDate invalid');
32 | page.maxDateValidationPickerInput.clear();
33 | page.maxDateValidationPickerInput.sendKeys('12-04-2017 09:08:07');
34 | expect(page.maxDateValidationMsg.isPresent()).toBe(false);
35 | });
36 |
37 | it('should check that the min selectable option is working', () => {
38 | page.minSelectableInput.clear();
39 | page.minSelectableInput.sendKeys('11-04-2017 09:08:07');
40 | page.daytimePickerInput.sendKeys('17-04-2017 09:08:07');
41 | page.daytimePickerInput.click();
42 | expect(page.calendarDisabledDays.count()).toBe(16);
43 | page.daytimePickerInput.clear();
44 | page.daytimePickerInput.sendKeys('11-04-2017 09:18:07');
45 | expect(page.hourDownBtn.getAttribute('disabled')).toEqual('true');
46 | expect(page.minuteDownBtn.getAttribute('disabled')).toBe(null);
47 | expect(page.meridiemUpBtn.getAttribute('disabled')).toBe(null);
48 | expect(page.meridiemDownBtn.getAttribute('disabled')).toBe(null);
49 | });
50 |
51 | it('should check that the max selectable option is working', () => {
52 | page.maxSelectableInput.clear();
53 | page.maxSelectableInput.sendKeys('11-04-2017 09:08:07');
54 | page.daytimePickerInput.sendKeys('12-04-2017 09:08:07');
55 | page.daytimePickerInput.click();
56 | expect(page.calendarDisabledDays.count()).toBe(25);
57 | page.daytimePickerInput.clear();
58 | page.daytimePickerInput.sendKeys('11-04-2017 09:06:07');
59 | expect(page.hourUpBtn.getAttribute('disabled')).toEqual('true');
60 | expect(page.minuteUpBtn.getAttribute('disabled')).toBe(null);
61 | expect(page.meridiemUpBtn.getAttribute('disabled')).toBe('true');
62 | expect(page.meridiemDownBtn.getAttribute('disabled')).toBe('true');
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ng2-jalali-date-picker",
3 | "author": "Mojtaba Zarei",
4 | "version": "2.4.3",
5 | "license": "MIT",
6 | "main": "index.js",
7 | "scripts": {
8 | "ng": "ng",
9 | "start": "ng serve -o",
10 | "build": "ng build",
11 | "test": "ng test",
12 | "coverage": "export CODACY_PROJECT_TOKEN=a92cd223e71b45ff99f24f7350ea89c3 ; ng test --watch=false --code-coverage && cat ./coverage/lcov.info | ./node_modules/.bin/codacy-coverage",
13 | "lint": "ng lint",
14 | "e2e": "ng e2e",
15 | "e2e:headless": "ng e2e -- headless",
16 | "build:demo": "rm -rf demo/*.js && rm -rf demo/*.css && ng build --prod --aot=false --bh /jalali-angular-datepicker/ && npm run build:index && cp dist/* demo",
17 | "build:index": "cd build-helpers && node index-maker.js",
18 | "build:prod": "ng-packagr -p package.json && cp -R screenshots bin",
19 | "release": "npm run build:prod && npm publish bin",
20 | "pack": "cd bin && npm pack"
21 | },
22 | "repository": {
23 | "type": "git",
24 | "url": "https://github.com/fingerpich/angular-datepicker.git"
25 | },
26 | "private": false,
27 | "keywords": [
28 | "angular",
29 | "jalali",
30 | "shamsi",
31 | "khorshidi",
32 | "persian",
33 | "date",
34 | "picker",
35 | "datepicker",
36 | "datepicker farsi",
37 | "typescript",
38 | "ts",
39 | "farsi"
40 | ],
41 | "dependencies": {
42 | "jalali-moment": "^3.3.10"
43 | },
44 | "devDependencies": {
45 | "@angular/cli": "^1.6.1",
46 | "@angular/common": "^5.1.1",
47 | "@angular/compiler": "^5.1.1",
48 | "@angular/compiler-cli": "^5.2.11",
49 | "@angular/core": "^5.1.1",
50 | "@angular/forms": "^5.1.1",
51 | "@angular/platform-browser": "^5.1.1",
52 | "@angular/platform-browser-dynamic": "^5.1.1",
53 | "@angular/router": "^5.1.1",
54 | "@types/google.analytics": "0.0.33",
55 | "@types/jasmine": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.5.38.tgz",
56 | "@types/node": "https://registry.npmjs.org/@types/node/-/node-6.0.94.tgz",
57 | "classlist.js": "^1.1.20150312",
58 | "codelyzer": "^4.0.2",
59 | "core-js": "^2.4.1",
60 | "jasmine-core": "~2.5.2",
61 | "jasmine-spec-reporter": "~3.2.0",
62 | "karma": "~1.4.1",
63 | "karma-chrome-launcher": "~2.0.0",
64 | "karma-cli": "~1.0.1",
65 | "karma-coverage-istanbul-reporter": "^1.3.0",
66 | "karma-jasmine": "~1.1.0",
67 | "karma-jasmine-html-reporter": "^0.2.2",
68 | "less": "^2.7.2",
69 | "ncp": "^2.0.0",
70 | "ng-packagr": "^1.6.0",
71 | "protractor": "^5.2.0",
72 | "rimraf": "^2.6.1",
73 | "rxjs": "^5.1.0",
74 | "ts-node": "^4.0.2",
75 | "tslint": "~5.8.0",
76 | "typescript": "~2.4.2",
77 | "uglify-js": "^2.8.12",
78 | "web-animations-js": "^2.2.5",
79 | "zone.js": "^0.8.4"
80 | },
81 | "angularCompilerOptions": {
82 | "skipTemplateCodegen": true,
83 | "strictMetadataEmit": true
84 | },
85 | "ngPackage": {
86 | "lib": {
87 | "entryFile": "src/app/index.ts",
88 | "externals": {
89 | "jalali-moment": "jalali-moment"
90 | }
91 | },
92 | "dest": "bin"
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/app/date-picker/date-picker.component.spec.ts:
--------------------------------------------------------------------------------
1 | import {FormsModule} from '@angular/forms';
2 | import {DatePickerComponent} from './date-picker.component';
3 | import {DayTimeCalendarComponent} from '../day-time-calendar/day-time-calendar.component';
4 | import {DayTimeCalendarService} from '../day-time-calendar/day-time-calendar.service';
5 | import {DomHelper} from '../common/services/dom-appender/dom-appender.service';
6 | import {CalendarMode} from '../common/types/calendar-mode';
7 | import {ComponentFixture, TestBed} from '@angular/core/testing';
8 | import {DayCalendarComponent} from '../day-calendar/day-calendar.component';
9 | import {TimeSelectComponent} from '../time-select/time-select.component';
10 | import {CalendarNavComponent} from '../calendar-nav/calendar-nav.component';
11 | import {MonthCalendarComponent} from '../month-calendar/month-calendar.component';
12 | import {DayCalendarService} from '../day-calendar/day-calendar.service';
13 | import {TimeSelectService} from '../time-select/time-select.service';
14 | import {UtilsService} from '../common/services/utils/utils.service';
15 |
16 | describe('Component: DatePickerComponent', () => {
17 | let component: DatePickerComponent;
18 | let fixture: ComponentFixture;
19 |
20 | const setComponentMode = function(mode: CalendarMode) {
21 | component.mode = mode;
22 | component.init();
23 | fixture.detectChanges();
24 | };
25 |
26 | beforeEach(() => {
27 | TestBed.configureTestingModule({
28 | imports: [ FormsModule ],
29 | declarations: [
30 | DatePickerComponent,
31 | DayTimeCalendarComponent,
32 | DayCalendarComponent,
33 | TimeSelectComponent,
34 | CalendarNavComponent,
35 | MonthCalendarComponent
36 | ],
37 | providers: [
38 | DayTimeCalendarService,
39 | DayCalendarService,
40 | TimeSelectService,
41 | UtilsService,
42 | DomHelper
43 | ]
44 | }).compileComponents();
45 | });
46 |
47 | beforeEach(() => {
48 | fixture = TestBed.createComponent(DatePickerComponent);
49 | component = fixture.componentInstance;
50 | fixture.detectChanges();
51 | });
52 |
53 | it('should create', () => {
54 | expect(component).toBeTruthy();
55 | });
56 |
57 | it('should emit event goToCurrent when day calendar emit', () => {
58 | setComponentMode('day');
59 |
60 | spyOn(component.onGoToCurrent, 'emit');
61 | component.dayCalendarRef.onGoToCurrent.emit();
62 | expect(component.onGoToCurrent.emit).toHaveBeenCalledWith();
63 | });
64 |
65 | it('should emit event goToCurrent when month calendar emit', () => {
66 | setComponentMode('month');
67 |
68 | spyOn(component.onGoToCurrent, 'emit');
69 | component.monthCalendarRef.onGoToCurrent.emit();
70 | expect(component.onGoToCurrent.emit).toHaveBeenCalledWith();
71 | });
72 |
73 | it('should emit event goToCurrent when daytime calendar emit', () => {
74 | setComponentMode('daytime');
75 |
76 | spyOn(component.onGoToCurrent, 'emit');
77 | component.dayTimeCalendarRef.onGoToCurrent.emit();
78 | expect(component.onGoToCurrent.emit).toHaveBeenCalledWith();
79 | });
80 | });
81 |
--------------------------------------------------------------------------------
/src/app/day-time-calendar/day-time-calendar.service.spec.ts:
--------------------------------------------------------------------------------
1 | import {inject, TestBed} from '@angular/core/testing';
2 | import {DayTimeCalendarService} from './day-time-calendar.service';
3 | import * as momentNs from 'jalali-moment';
4 | import {UtilsService} from '../common/services/utils/utils.service';
5 | import {DayCalendarService} from '../day-calendar/day-calendar.service';
6 | import {TimeSelectService} from '../time-select/time-select.service';
7 | import {IDayCalendarConfigInternal} from '../day-calendar/day-calendar-config.model';
8 |
9 | const moment = momentNs;
10 |
11 | const DAY_FORMAT = 'YYYYMMDD';
12 | const TIME_FORMAT = 'HH:mm:ss';
13 | const COMBINED_FORMAT = DAY_FORMAT + TIME_FORMAT;
14 |
15 | describe('Service: DayTimeCalendarService', () => {
16 | beforeEach(() => {
17 | TestBed.configureTestingModule({
18 | providers: [DayTimeCalendarService, DayCalendarService, TimeSelectService, UtilsService]
19 | });
20 | });
21 |
22 | it('should check the updateDay method', inject([DayTimeCalendarService],
23 | (service: DayTimeCalendarService) => {
24 | const daytime = moment('2011091313:12:11', COMBINED_FORMAT);
25 | const day = moment('10110203', DAY_FORMAT);
26 | expect(service.updateDay(daytime, day, {}).format(COMBINED_FORMAT)).toEqual('1011020313:12:11');
27 | })
28 | );
29 |
30 | it('should check the updateTime method when time is before min', inject([DayTimeCalendarService],
31 | (service: DayTimeCalendarService) => {
32 | const daytime = moment('2011091313:12:11', COMBINED_FORMAT);
33 | const config: IDayCalendarConfigInternal = {
34 | min: daytime.clone().add(10, 'm'),
35 | max: daytime.clone().add(50, 'm')
36 | };
37 |
38 | const time = daytime.clone();
39 | expect(service.updateDay(daytime, time, config).format(COMBINED_FORMAT))
40 | .toEqual(daytime.clone().add(10, 'm').format(COMBINED_FORMAT));
41 |
42 | expect(service.updateDay(daytime, time, {}).format(COMBINED_FORMAT))
43 | .toEqual(daytime.format(COMBINED_FORMAT));
44 | })
45 | );
46 |
47 | it('should check the updateTime method when time is before max', inject([DayTimeCalendarService],
48 | (service: DayTimeCalendarService) => {
49 | const daytime = moment('2011091313:12:11', COMBINED_FORMAT);
50 | const config: IDayCalendarConfigInternal = {
51 | min: daytime.clone().subtract(50, 'm'),
52 | max: daytime.clone().subtract(10, 'm')
53 | };
54 |
55 | const time = daytime.clone();
56 | expect(service.updateDay(daytime, time, config).format(COMBINED_FORMAT))
57 | .toEqual(daytime.clone().subtract(10, 'm').format(COMBINED_FORMAT));
58 |
59 | expect(service.updateDay(daytime, time, {}).format(COMBINED_FORMAT))
60 | .toEqual(daytime.format(COMBINED_FORMAT));
61 | })
62 | );
63 |
64 | it('should check the updateTime method', inject([DayTimeCalendarService], (service: DayTimeCalendarService) => {
65 | const daytime = moment('2011091313:12:11', COMBINED_FORMAT);
66 | const time = moment('03:11:10', TIME_FORMAT);
67 | expect(service.updateTime(daytime, time).format(COMBINED_FORMAT)).toEqual('2011091303:11:10');
68 | })
69 | );
70 | });
71 |
--------------------------------------------------------------------------------
/src/app/date-picker/date-picker-directive.service.spec.ts:
--------------------------------------------------------------------------------
1 | import {inject, TestBed} from '@angular/core/testing';
2 | import {DatePickerDirectiveService} from './date-picker-directive.service';
3 | import {UtilsService} from '../common/services/utils/utils.service';
4 |
5 | describe('Service: DatePickerDirective', () => {
6 | beforeEach(() => {
7 | TestBed.configureTestingModule({
8 | providers: [DatePickerDirectiveService, UtilsService]
9 | });
10 | });
11 |
12 | it('should check convertToElement method', inject([DatePickerDirectiveService, UtilsService],
13 | (service: DatePickerDirectiveService, stubUtilsService: UtilsService) => {
14 | stubUtilsService.closestParent = jasmine.createSpy('closestParent').and.returnValue('fakeElement');
15 |
16 | const baseElement = {};
17 | const element1 = service.convertToHTMLElement({nativeElement: 'fakeElement'}, baseElement);
18 | expect(element1).toBe('fakeElement');
19 | expect(stubUtilsService.closestParent).not.toHaveBeenCalled();
20 |
21 | const element2 = service.convertToHTMLElement('.notFound', baseElement);
22 | expect(element2).toBe('fakeElement');
23 | expect(stubUtilsService.closestParent).toHaveBeenCalledWith(baseElement, '.notFound');
24 | }));
25 |
26 | it('should check getConfig method', inject([DatePickerDirectiveService],
27 | (service: DatePickerDirectiveService) => {
28 | service.convertToHTMLElement = jasmine.createSpy('convertToHTMLElement').and.returnValue('fakeElement');
29 |
30 | const config1 = service.getConfig();
31 | expect(config1).toEqual({hideInputContainer: true});
32 | expect(service.convertToHTMLElement).not.toHaveBeenCalled();
33 |
34 | const config2 = service.getConfig({allowMultiSelect: true});
35 | expect(config2).toEqual({
36 | allowMultiSelect: true,
37 | hideInputContainer: true
38 | });
39 | expect(service.convertToHTMLElement).not.toHaveBeenCalled();
40 |
41 | const fakeElement = {};
42 | const config3 = service.getConfig({allowMultiSelect: true}, {nativeElement: fakeElement});
43 | expect(config3).toEqual({
44 | allowMultiSelect: true,
45 | hideInputContainer: true,
46 | inputElementContainer: fakeElement
47 | });
48 | expect(service.convertToHTMLElement).not.toHaveBeenCalled();
49 |
50 | const fakeAttachElementRef = {nativeElement: {}};
51 | const fakeElementRef = {nativeElement: fakeElement};
52 | const config4 = service.getConfig({allowMultiSelect: true}, fakeElementRef, fakeAttachElementRef);
53 | expect(config4).toEqual({
54 | allowMultiSelect: true,
55 | hideInputContainer: true,
56 | inputElementContainer: 'fakeElement'
57 | });
58 | expect(service.convertToHTMLElement).toHaveBeenCalledWith(fakeAttachElementRef, fakeElement);
59 |
60 | const config5 = service.getConfig({allowMultiSelect: true}, fakeElementRef, 'someSelector');
61 | expect(config5).toEqual({
62 | allowMultiSelect: true,
63 | hideInputContainer: true,
64 | inputElementContainer: 'fakeElement'
65 | });
66 | expect(service.convertToHTMLElement).toHaveBeenCalledWith('someSelector', fakeElement);
67 | }));
68 | });
69 |
--------------------------------------------------------------------------------
/src/app/month-calendar/month-calendar.service.spec.ts:
--------------------------------------------------------------------------------
1 | import {inject, TestBed} from '@angular/core/testing';
2 | import * as momentNs from 'jalali-moment';
3 | import {UtilsService} from '../common/services/utils/utils.service';
4 | import {MonthCalendarService} from './month-calendar.service';
5 | import {IMonth} from './month.model';
6 | const moment = momentNs;
7 |
8 | describe('Service: MonthCalendarService', () => {
9 | beforeEach(() => {
10 | TestBed.configureTestingModule({
11 | providers: [MonthCalendarService, UtilsService]
12 | });
13 | });
14 |
15 | it('should check the generateYear method', inject([MonthCalendarService], (service: MonthCalendarService) => {
16 | const year = moment('14-01-1987', 'DD-MM-YYYY');
17 | const selected = moment('14-01-1987', 'DD-MM-YYYY');
18 | const genYear = service.generateYear({}, year, [selected]);
19 |
20 | const current = year.clone().startOf('year');
21 | genYear.forEach((row) => {
22 | row.forEach((month) => {
23 | expect(month.date.isSame(current, 'month')).toBe(true);
24 | if (month.date.format('MMM') === 'Jan') {
25 | expect(month.selected).toBe(true);
26 | } else {
27 | expect(month.selected).toBe(false);
28 | }
29 | expect(month.currentMonth).toBe(false);
30 |
31 | current.add(1, 'month');
32 | });
33 | });
34 | }));
35 |
36 | it('should check the isDateDisabled method', inject([MonthCalendarService], (service: MonthCalendarService) => {
37 | const month: IMonth = {
38 | date: moment('09-04-2017', 'DD-MM-YYYY'),
39 | selected: false,
40 | currentMonth: false,
41 | disabled: false,
42 | text: moment('09-04-2017', 'DD-MM-YYYY').format('MMM')
43 | };
44 | const config1: any = {
45 | min: month.date.clone().subtract(1, 'month'),
46 | max: month.date.clone().add(1, 'month')
47 | };
48 |
49 | expect(service.isMonthDisabled(month.date, config1)).toBe(false);
50 | month.date.subtract(1, 'month');
51 | expect(service.isMonthDisabled(month.date, config1)).toBe(false);
52 | month.date.subtract(1, 'month');
53 | expect(service.isMonthDisabled(month.date, config1)).toBe(true);
54 | month.date.add(3, 'month');
55 | expect(service.isMonthDisabled(month.date, config1)).toBe(false);
56 | month.date.add(1, 'month');
57 | expect(service.isMonthDisabled(month.date, config1)).toBe(true);
58 | }));
59 |
60 | it('should check getDayBtnText method',
61 | inject([MonthCalendarService],
62 | (service: MonthCalendarService) => {
63 | const date = moment('05-04-2017', 'DD-MM-YYYY');
64 | expect(service.getMonthBtnText({monthBtnFormat: 'M'}, date)).toEqual('4');
65 | expect(service.getMonthBtnText({monthBtnFormat: 'MM'}, date)).toEqual('04');
66 | expect(service.getMonthBtnText({monthBtnFormatter: (m => 'bla')}, date)).toEqual('bla');
67 | expect(service.getMonthBtnText({monthBtnFormat: 'MM', monthBtnFormatter: (m => m.format('M'))}, date))
68 | .toEqual('4');
69 | }));
70 |
71 | it('should check getMonthBtnCssClass method',
72 | inject([MonthCalendarService],
73 | (service: MonthCalendarService) => {
74 | const date = moment('05-04-2017', 'DD-MM-YYYY');
75 | expect(service.getMonthBtnCssClass({}, date)).toEqual('');
76 | expect(service.getMonthBtnCssClass({monthBtnCssClassCallback: (m => 'class1 class2')}, date))
77 | .toEqual('class1 class2');
78 | }));
79 | });
80 |
--------------------------------------------------------------------------------
/e2e/current-btn-e2e.spec.ts:
--------------------------------------------------------------------------------
1 | import {DemoPage} from './app.po';
2 | import * as moment from 'jalali-moment';
3 | import {ElementFinder} from 'protractor';
4 |
5 | describe('dpDayPicker dayPicker', () => {
6 |
7 | let page: DemoPage;
8 |
9 | beforeEach(() => {
10 | page = new DemoPage();
11 | page.navigateTo();
12 | });
13 |
14 | it('should check if go to current location btn is working as expected', () => {
15 | const currentMonth = moment().format('MMM, YYYY');
16 | const currentYear = moment().format('YYYY');
17 | const prevMonth = moment().subtract(1, 'month').format('MMM, YYYY');
18 | const prevYear = moment().subtract(1, 'year').format('YYYY');
19 |
20 | const commonDayCalendar = (menu: ElementFinder, input: ElementFinder) => {
21 | menu.click();
22 | page.showGoToCurrentRadio.click();
23 | input.click();
24 | expect(page.currentLocationBtn.isPresent()).toBe(true);
25 | expect(page.dayCalendarNavHeaderBtn.getText()).toEqual(currentMonth);
26 | page.dayCalendarLeftNavBtn.click();
27 | expect(page.dayCalendarNavHeaderBtn.getText()).toEqual(prevMonth);
28 | page.currentLocationBtn.click();
29 | expect(page.dayCalendarNavHeaderBtn.getText()).toEqual(currentMonth);
30 | page.dayCalendarNavHeaderBtn.click();
31 | expect(page.dayCalendarNavMonthHeaderBtn.getText()).toEqual(currentYear);
32 | page.monthCalendarLeftNavBtn.click();
33 | expect(page.dayCalendarNavMonthHeaderBtn.getText()).toEqual(prevYear);
34 | page.dayCalendarNavMonthHeaderBtn.click();
35 |
36 | page.currentLocationBtn.click();
37 | expect(page.dayCalendarNavHeaderBtn.getText()).toEqual(currentMonth);
38 |
39 | page.hideGoToCurrentRadio.click();
40 | input.click();
41 | expect(page.currentLocationBtn.isPresent()).toBe(false);
42 | page.dayCalendarNavHeaderBtn.click();
43 | expect(page.currentLocationBtn.isPresent()).toBe(false);
44 | };
45 |
46 | const commonMonth = (menu: ElementFinder, input?: ElementFinder) => {
47 | menu.click();
48 | page.showGoToCurrentRadio.click();
49 | input.click();
50 | expect(page.currentLocationBtn.isPresent()).toBe(true);
51 | expect(page.deyCalendarMonthNavHeader.getText()).toEqual(currentYear);
52 | page.monthCalendarLeftNavBtn.click();
53 | expect(page.deyCalendarMonthNavHeader.getText()).toEqual(prevYear);
54 | page.currentLocationBtn.click();
55 | expect(page.deyCalendarMonthNavHeader.getText()).toEqual(currentYear);
56 |
57 | page.hideGoToCurrentRadio.click();
58 | input.click();
59 | expect(page.currentLocationBtn.isPresent()).toBe(false);
60 | };
61 |
62 | commonDayCalendar(page.daytimePickerMenu, page.daytimePickerInput);
63 | commonDayCalendar(page.daytimeDirectiveMenu, page.daytimeDirectiveInput);
64 |
65 | commonDayCalendar(page.dayPickerMenu, page.dayPickerInput);
66 | commonDayCalendar(page.dayDirectiveMenu, page.dayDirectiveInput);
67 | commonDayCalendar(page.dayDirectiveReactiveMenu, page.dayDirectiveReactiveInput);
68 |
69 | commonMonth(page.monthPickerMenu, page.monthPickerInput);
70 | commonMonth(page.monthDirectiveMenu, page.monthDirectiveInput);
71 | });
72 |
73 | it('should hide current date button when not between min and max', () => {
74 | page.dayPickerMenu.click();
75 | page.minSelectableInput.sendKeys(moment().add(3, 'month').format('DD-MM-YYYY'));
76 | page.dayPickerInput.click();
77 | expect(page.currentLocationBtn.isPresent()).toBe(false);
78 | });
79 | });
80 |
--------------------------------------------------------------------------------
/e2e/daytimepicker-e2e.spec.ts:
--------------------------------------------------------------------------------
1 | import {DemoPage} from './app.po';
2 | import {browser} from 'protractor';
3 |
4 | describe('dpDayPicker daytimePicker', () => {
5 | let page: DemoPage;
6 |
7 | beforeEach(() => {
8 | page = new DemoPage();
9 | page.navigateTo();
10 |
11 | page.dateFormatInput.clear();
12 | page.dateFormatInput.sendKeys('DD-MM-YYYY HH:mm:ss');
13 | page.daytimePickerMenu.click();
14 | });
15 |
16 | it('should check if min date validation is working', () => {
17 | page.minDateValidationPickerInput.clear();
18 | expect(page.minDateValidationMsg.isPresent()).toBe(false);
19 | page.minDateValidationPickerInput.sendKeys('10-04-2017 10:08:07');
20 | page.daytimePickerInput.sendKeys('10-04-2017 09:08:07');
21 | expect(page.minDateValidationMsg.getText()).toEqual('minDate invalid');
22 | page.minDateValidationPickerInput.clear();
23 | page.minDateValidationPickerInput.sendKeys('10-04-2017 09:08:07');
24 | expect(page.minDateValidationMsg.isPresent()).toBe(false);
25 | });
26 |
27 | it('should check if max date validation is working', () => {
28 | page.maxDateValidationPickerInput.clear();
29 | expect(page.maxDateValidationMsg.isPresent()).toBe(false);
30 | page.maxDateValidationPickerInput.sendKeys('12-04-2017 08:08:07');
31 | page.daytimePickerInput.sendKeys('12-04-2017 09:08:07');
32 | expect(page.maxDateValidationMsg.getText()).toEqual('maxDate invalid');
33 | page.maxDateValidationPickerInput.clear();
34 | page.maxDateValidationPickerInput.sendKeys('12-04-2017 09:08:07');
35 | expect(page.maxDateValidationMsg.isPresent()).toBe(false);
36 | });
37 |
38 | it('should check that the min selectable option is working', () => {
39 | page.minSelectableInput.clear();
40 | page.minSelectableInput.sendKeys('11-04-2017 09:08:07');
41 | page.daytimePickerInput.sendKeys('17-04-2017 09:08:07');
42 | page.daytimePickerInput.click();
43 | expect(page.calendarDisabledDays.count()).toBe(16);
44 | page.daytimePickerInput.clear();
45 | page.daytimePickerInput.sendKeys('11-04-2017 09:18:07');
46 | expect(page.hourDownBtn.getAttribute('disabled')).toEqual('true');
47 | expect(page.minuteDownBtn.getAttribute('disabled')).toBe(null);
48 | expect(page.meridiemUpBtn.getAttribute('disabled')).toBe(null);
49 | expect(page.meridiemDownBtn.getAttribute('disabled')).toBe(null);
50 | });
51 |
52 | it('should check that the max selectable option is working', () => {
53 | page.maxSelectableInput.clear();
54 | page.maxSelectableInput.sendKeys('11-04-2017 09:08:07');
55 | page.daytimePickerInput.sendKeys('12-04-2017 09:08:07');
56 | page.daytimePickerInput.click();
57 | expect(page.calendarDisabledDays.count()).toBe(25);
58 | page.daytimePickerInput.clear();
59 | page.daytimePickerInput.sendKeys('11-04-2017 09:06:07');
60 | expect(page.hourUpBtn.getAttribute('disabled')).toEqual('true');
61 | expect(page.minuteUpBtn.getAttribute('disabled')).toBe(null);
62 | expect(page.meridiemUpBtn.getAttribute('disabled')).toBe('true');
63 | expect(page.meridiemDownBtn.getAttribute('disabled')).toBe('true');
64 | });
65 |
66 | it('should check that the max selectable option is working', () => {
67 | page.daytimePickerInput.click();
68 | page.daytimePickerInput.sendKeys('11-04-2017 09:08:07');
69 | expect(page.selectedDays.count()).toEqual(1);
70 | page.daytimePickerInput.clear();
71 | page.daytimePickerInput.sendKeys(' ');
72 |
73 | expect(page.selectedDays.count()).toEqual(0);
74 | });
75 | });
76 |
--------------------------------------------------------------------------------
/src/app/calendar-nav/calendar-nav.component.less:
--------------------------------------------------------------------------------
1 | @import '../common/styles/variables';
2 |
3 | & {
4 |
5 | dp-calendar-nav {
6 | @navHeight: 25px;
7 |
8 | .dp-calendar-nav-container {
9 | position: relative;
10 | box-sizing: border-box;
11 | height: @navHeight;
12 | border: 1px solid @c-black;
13 | border-bottom: none;
14 | }
15 |
16 | .dp-nav-date-btn {
17 | box-sizing: border-box;
18 | height: 25px;
19 | border: 1px solid @c-black;
20 | border-bottom: none;
21 | }
22 |
23 | .dp-nav-btns-container {
24 | position: absolute;
25 | top: 50%;
26 | transform: translateY(-50%);
27 | right: 5px;
28 | display: inline-block;
29 | direction: ltr;
30 | }
31 |
32 | .dp-calendar-nav-container-left, .dp-calendar-nav-container-right {
33 | display: inline-block;
34 | }
35 |
36 | .dp-calendar-nav-left,
37 | .dp-calendar-nav-right,
38 | .dp-calendar-secondary-nav-left,
39 | .dp-calendar-secondary-nav-right {
40 | position: relative;
41 | width: 16px;
42 | cursor: pointer;
43 | }
44 |
45 | .dp-calendar-nav-left, .dp-calendar-nav-right {
46 | line-height: 0;
47 | .arrow(45deg);
48 | }
49 |
50 | .dp-calendar-secondary-nav-left, .dp-calendar-secondary-nav-right {
51 | .double-arrow(45deg);
52 | padding: 0;
53 | }
54 |
55 | .dp-calendar-secondary-nav-right {
56 | left: initial;
57 | right: 5px;
58 | }
59 |
60 | .dp-calendar-nav-left {
61 | .arrow(-135deg);
62 | }
63 |
64 | .dp-calendar-secondary-nav-left {
65 | .double-arrow(-135deg);
66 | }
67 |
68 | .dp-nav-header {
69 | position: absolute;
70 | top: 50%;
71 | transform: translateY(-50%);
72 | left: 5px;
73 | display: inline-block;
74 | font-size: 13px;
75 | }
76 |
77 | .dp-nav-header-btn {
78 | cursor: pointer;
79 | }
80 |
81 | .dp-current-location-btn {
82 | position: relative;
83 | top: -1px;
84 | height: 16px;
85 | width: 16px;
86 | vertical-align: middle;
87 | background: fade(@c-black, 60);
88 | border: 1px solid fade(@c-black, 60);
89 | outline: none;
90 | border-radius: 50%;
91 | box-shadow: inset 0 0 0 3px @c-white;
92 | cursor: pointer;
93 |
94 | &:hover {
95 | background: @c-black;
96 | }
97 | }
98 |
99 | &.dp-material {
100 | .dp-calendar-nav-container {
101 | height: @basic-height;
102 | border: 1px solid @c-light-gray;
103 | }
104 |
105 | .dp-calendar-nav-left,
106 | .dp-calendar-nav-right,
107 | .dp-calendar-secondary-nav-left,
108 | .dp-calendar-secondary-nav-right {
109 | border: none;
110 | background: @c-white;
111 | outline: none;
112 | font-size: 16px;
113 | padding: 0;
114 | }
115 |
116 | .dp-calendar-secondary-nav-left,
117 | .dp-calendar-secondary-nav-right {
118 | width: 20px;
119 | }
120 |
121 | .dp-nav-header-btn {
122 | height: 20px;
123 | width: 80px;
124 | border: none;
125 | background: @c-white;
126 | outline: none;
127 |
128 | &:hover {
129 | background: fade(@c-black, 5);
130 | }
131 |
132 | &:active {
133 | background: fade(@c-black, 10);
134 | }
135 | }
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/e2e/reactive-directive-e2e.spec.ts:
--------------------------------------------------------------------------------
1 | import {DemoPage} from './app.po';
2 | import {browser} from 'protractor';
3 |
4 | describe('dpDayPicker reactive directive', () => {
5 | let page: DemoPage;
6 |
7 | beforeEach(() => {
8 | page = new DemoPage();
9 | page.navigateTo();
10 |
11 | page.dateFormatInput.clear();
12 | page.dateFormatInput.sendKeys('DD-MM-YYYY');
13 | page.dayDirectiveReactiveMenu.click();
14 | });
15 |
16 | it('should check that the popup appended to body', () => {
17 | page.dayDirectiveReactiveInput.click();
18 | expect(page.datePickerPopup.isDisplayed()).toBe(true);
19 | page.clickOnBody();
20 | expect(page.datePickerPopup.isDisplayed()).toBe(false);
21 | });
22 |
23 | it('should check that the theme is added and removed', () => {
24 | page.themeOnRadio.click();
25 | expect(page.datePickerPopup.getAttribute('class')).toContain('dp-material');
26 | page.themeOffRadio.click();
27 | expect(page.datePickerPopup.getAttribute('class')).not.toContain('dp-material');
28 | page.themeOnRadio.click();
29 | expect(page.datePickerPopup.getAttribute('class')).toContain('dp-material');
30 | });
31 |
32 | it('should check that the onOpenDelay is working', () => {
33 | page.onOpenDelayInput.clear();
34 | page.onOpenDelayInput.sendKeys(1000);
35 | page.scrollIntoView(page.openBtn);
36 | page.openBtn.click();
37 | expect(page.datePickerPopup.isDisplayed()).toBe(true);
38 | page.clickOnBody();
39 | browser.sleep(200);
40 | browser.waitForAngularEnabled(false);
41 | page.dayDirectiveReactiveInput.click();
42 | expect(page.datePickerPopup.isDisplayed()).toBe(false);
43 | browser.waitForAngularEnabled(true);
44 | browser.sleep(1000);
45 | expect(page.datePickerPopup.isDisplayed()).toBe(true);
46 | });
47 |
48 | it('should check if enable/disable required validation is working', () => {
49 | page.dayDirectiveReactiveInput.clear();
50 | expect(page.reactiveRequiredValidationMsg.isPresent()).toBe(false);
51 | page.enableRequiredValidationRadio.click();
52 | expect(page.reactiveRequiredValidationMsg.getText()).toEqual('required');
53 | page.disableRequiredValidationRadio.click();
54 | expect(page.reactiveRequiredValidationMsg.isPresent()).toBe(false);
55 | });
56 |
57 | it('should check if min date validation is working', () => {
58 | page.minDateValidationPickerInput.clear();
59 | expect(page.reactiveMinDateValidationMsg.isPresent()).toBe(false);
60 | page.minDateValidationPickerInput.sendKeys('11-04-2017');
61 | page.dayDirectiveReactiveInput.sendKeys('10-04-2017');
62 | page.clickOnBody();
63 | expect(page.reactiveMinDateValidationMsg.getText()).toEqual('minDate invalid');
64 | page.minDateValidationPickerInput.clear();
65 | page.minDateValidationPickerInput.sendKeys('10-04-2017');
66 | expect(page.reactiveMinDateValidationMsg.isPresent()).toBe(false);
67 | });
68 |
69 | it('should check if max date validation is working', () => {
70 | page.maxDateValidationPickerInput.clear();
71 | expect(page.reactiveMaxDateValidationMsg.isPresent()).toBe(false);
72 | page.maxDateValidationPickerInput.sendKeys('11-04-2017');
73 | page.dayDirectiveReactiveInput.sendKeys('12-04-2017');
74 | page.clickOnBody();
75 | expect(page.reactiveMaxDateValidationMsg.getText()).toEqual('maxDate invalid');
76 | page.maxDateValidationPickerInput.clear();
77 | page.maxDateValidationPickerInput.sendKeys('12-04-2017');
78 | expect(page.reactiveMaxDateValidationMsg.isPresent()).toBe(false);
79 | });
80 | });
81 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "callable-types": true,
7 | "class-name": true,
8 | "comment-format": [
9 | true,
10 | "check-space"
11 | ],
12 | "curly": true,
13 | "eofline": false,
14 | "forin": true,
15 | "import-blacklist": [
16 | true,
17 | "rxjs"
18 | ],
19 | "import-spacing": true,
20 | "indent": [
21 | true,
22 | "spaces"
23 | ],
24 | "interface-over-type-literal": true,
25 | "label-position": true,
26 | "max-line-length": [
27 | true,
28 | 140
29 | ],
30 | "trailing-comma": [
31 | true,
32 | {
33 | "multiline": "never",
34 | "singleline": "never"
35 | }
36 | ],
37 | "member-access": false,
38 | "member-ordering": [
39 | true,
40 | "static-before-instance",
41 | "variables-before-functions"
42 | ],
43 | "no-arg": true,
44 | "no-bitwise": true,
45 | "no-console": [
46 | true,
47 | "debug",
48 | "info",
49 | "time",
50 | "timeEnd",
51 | "trace"
52 | ],
53 | "no-construct": true,
54 | "no-debugger": true,
55 | "no-duplicate-variable": true,
56 | "no-empty": false,
57 | "no-empty-interface": true,
58 | "no-eval": true,
59 | "no-inferrable-types": [
60 | false,
61 | "ignore-params"
62 | ],
63 | "no-shadowed-variable": true,
64 | "no-string-literal": false,
65 | "no-string-throw": true,
66 | "no-switch-case-fall-through": true,
67 | "no-trailing-whitespace": true,
68 | "no-unused-expression": true,
69 | "no-use-before-declare": true,
70 | "no-var-keyword": true,
71 | "object-literal-sort-keys": false,
72 | "one-line": [
73 | true,
74 | "check-open-brace",
75 | "check-catch",
76 | "check-else",
77 | "check-whitespace"
78 | ],
79 | "prefer-const": true,
80 | "quotemark": [
81 | true,
82 | "single"
83 | ],
84 | "radix": true,
85 | "semicolon": [
86 | "always"
87 | ],
88 | "triple-equals": [
89 | true,
90 | "allow-null-check"
91 | ],
92 | "typedef-whitespace": [
93 | true,
94 | {
95 | "call-signature": "nospace",
96 | "index-signature": "nospace",
97 | "parameter": "nospace",
98 | "property-declaration": "nospace",
99 | "variable-declaration": "nospace"
100 | }
101 | ],
102 | "typeof-compare": true,
103 | "unified-signatures": true,
104 | "variable-name": false,
105 | "whitespace": [
106 | true,
107 | "check-branch",
108 | "check-decl",
109 | "check-operator",
110 | "check-separator",
111 | "check-type"
112 | ],
113 | "directive-selector": [
114 | true,
115 | "attribute",
116 | "dp",
117 | "camelCase"
118 | ],
119 | "component-selector": [
120 | true,
121 | "element",
122 | "dp",
123 | "kebab-case"
124 | ],
125 | "use-input-property-decorator": true,
126 | "use-output-property-decorator": true,
127 | "use-host-property-decorator": true,
128 | "no-input-rename": false,
129 | "no-output-rename": true,
130 | "use-life-cycle-interface": true,
131 | "use-pipe-transform-interface": true,
132 | "component-class-suffix": true,
133 | "directive-class-suffix": true,
134 | "no-access-missing-member": true,
135 | "templates-use-public": true,
136 | "invoke-injectable": true
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/app/month-calendar/month-calendar.service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 | import * as momentNs from 'jalali-moment';
3 | import {Moment} from 'jalali-moment';
4 | import {UtilsService} from '../common/services/utils/utils.service';
5 | import {IMonth} from './month.model';
6 | import {IMonthCalendarConfig, IMonthCalendarConfigInternal} from './month-calendar-config';
7 | const moment = momentNs;
8 |
9 | @Injectable()
10 | export class MonthCalendarService {
11 | readonly DEFAULT_CONFIG: IMonthCalendarConfigInternal = {
12 | allowMultiSelect: false,
13 | yearFormat: 'YYYY',
14 | format: 'MMMM-YYYY',
15 | isNavHeaderBtnClickable: false,
16 | monthBtnFormat: 'MMMM',
17 | locale: 'fa',
18 | multipleYearsNavigateBy: 10,
19 | showMultipleYearsNavigation: false,
20 | unSelectOnClick: true
21 | };
22 | readonly GREGORIAN_DEFAULT_CONFIG: IMonthCalendarConfig = {
23 | format: 'MM-YYYY',
24 | monthBtnFormat: 'MMM',
25 | locale: 'en'
26 | };
27 |
28 | constructor(private utilsService: UtilsService) {
29 | }
30 |
31 | getConfig(config: IMonthCalendarConfig): IMonthCalendarConfigInternal {
32 | const _config = {
33 | ...this.DEFAULT_CONFIG,
34 | ...((config && config.locale && config.locale !== 'fa') ? this.GREGORIAN_DEFAULT_CONFIG : {}),
35 | ...this.utilsService.clearUndefined(config)
36 | };
37 |
38 | this.utilsService.convertPropsToMoment(_config, _config.format, ['min', 'max'], _config.locale);
39 |
40 | // moment.locale(_config.locale);
41 |
42 | return _config;
43 | }
44 |
45 | generateYear(config: IMonthCalendarConfig, year: Moment, selected: Moment[] = null): IMonth[][] {
46 | const index = year.clone().startOf('year');
47 |
48 | return this.utilsService.createArray(3).map(() => {
49 | return this.utilsService.createArray(4).map(() => {
50 | const date = index.clone();
51 | const month = {
52 | date,
53 | selected: !!selected.find(s => index.isSame(s, 'month')),
54 | currentMonth: index.isSame(moment(), 'month'),
55 | disabled: this.isMonthDisabled(date, config),
56 | text: this.getMonthBtnText(config, date)
57 | };
58 |
59 | index.add(1, 'month');
60 |
61 | return month;
62 | });
63 | });
64 | }
65 |
66 | isMonthDisabled(date: Moment, config: IMonthCalendarConfig) {
67 | if (config.min && date.isBefore(config.min, 'month')) {
68 | return true;
69 | }
70 |
71 | return !!(config.max && date.isAfter(config.max, 'month'));
72 | }
73 |
74 | shouldShowLeft(min: Moment, currentMonthView: Moment): boolean {
75 | return min ? min.isBefore(currentMonthView, 'year') : true;
76 | }
77 |
78 | shouldShowRight(max: Moment, currentMonthView: Moment): boolean {
79 | return max ? max.isAfter(currentMonthView, 'year') : true;
80 | }
81 |
82 | getHeaderLabel(config: IMonthCalendarConfig, year: Moment): string {
83 | if (config.yearFormatter) {
84 | return config.yearFormatter(year);
85 | }
86 | year.locale(config.locale);
87 | return year.format(config.yearFormat);
88 | }
89 |
90 | getMonthBtnText(config: IMonthCalendarConfig, month: Moment): string {
91 | if (config.monthBtnFormatter) {
92 | return config.monthBtnFormatter(month);
93 | }
94 |
95 | return month.format(config.monthBtnFormat);
96 | }
97 |
98 | getMonthBtnCssClass(config: IMonthCalendarConfig, month: Moment): string {
99 | if (config.monthBtnCssClassCallback) {
100 | return config.monthBtnCssClassCallback(month);
101 | }
102 |
103 | return '';
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/e2e/selected-unselect-e2e.spec.ts:
--------------------------------------------------------------------------------
1 | import {DemoPage} from './app.po';
2 | import * as moment from 'jalali-moment';
3 | import {browser} from 'protractor';
4 | import * as fs from 'fs';
5 | import {TestUtils} from './test-utils';
6 |
7 | describe('dpDayPicker timePicker', () => {
8 |
9 | let page: DemoPage;
10 |
11 | beforeEach(() => {
12 | page = new DemoPage();
13 | page.navigateTo();
14 | });
15 |
16 | it('should make sure unSelectOnClick feature works as expected for day calendar', () => {
17 | const dayRunner = (menuItem, input, isPicker) => {
18 | const date = moment();
19 | const dayClick = () => {
20 | if (isPicker) {
21 | page.clickOnDayButton(date.format('DD'));
22 | } else {
23 | page.clickOnDayButtonInline(date.format('DD'));
24 | }
25 | };
26 |
27 | menuItem.click();
28 | page.enableUnselectSelected.click();
29 |
30 | if (isPicker) {
31 | page.scrollIntoView(page.noCloseOnSelect);
32 | page.noCloseOnSelect.click();
33 | input.click();
34 | }
35 |
36 | dayClick();
37 | expect(page.selectedDay.isPresent()).toEqual(true);
38 | dayClick();
39 | expect(page.selectedDay.isPresent()).toEqual(false);
40 |
41 | page.clickOnBody();
42 |
43 | page.disableUnselectSelected.click();
44 |
45 | if (isPicker) {
46 | input.click();
47 | }
48 |
49 | dayClick();
50 |
51 | expect(page.selectedDay.isPresent()).toEqual(true);
52 |
53 | dayClick();
54 | expect(page.selectedDay.isPresent()).toEqual(true);
55 |
56 | page.enableUnselectSelected.click();
57 |
58 | if (isPicker) {
59 | input.click();
60 | }
61 |
62 | dayClick();
63 | expect(page.selectedDay.isPresent()).toEqual(false);
64 | };
65 |
66 | dayRunner(page.dayPickerMenu, page.dayPickerInput, true);
67 | dayRunner(page.dayDirectiveMenu, page.dayDirectiveInput, true);
68 | dayRunner(page.dayInlineMenu, null, false);
69 | });
70 |
71 | it('should make sure unSelectOnClick feature works as expected for month calendar', () => {
72 | const monthRunner = (menuItem, input, isPicker) => {
73 | const date = moment();
74 | const dayClick = () => {
75 | if (isPicker) {
76 | page.clickOnMonthButton(date.format('MMM'));
77 | } else {
78 | page.clickOnMonthButtonInline(date.format('MMM'));
79 | }
80 | };
81 |
82 | menuItem.click();
83 | page.enableUnselectSelected.click();
84 |
85 | if (isPicker) {
86 | page.scrollIntoView(page.noCloseOnSelect);
87 | page.noCloseOnSelect.click();
88 | input.click();
89 | }
90 |
91 | dayClick();
92 | expect(page.selectedMonth.isPresent()).toEqual(true);
93 | dayClick();
94 | expect(page.selectedMonth.isPresent()).toEqual(false);
95 |
96 | page.clickOnBody();
97 | page.scrollIntoView(page.disableUnselectSelected);
98 | page.disableUnselectSelected.click();
99 |
100 | if (isPicker) {
101 | input.click();
102 | }
103 |
104 | dayClick();
105 |
106 | expect(page.selectedMonth.isPresent()).toEqual(true);
107 |
108 | dayClick();
109 | expect(page.selectedMonth.isPresent()).toEqual(true);
110 |
111 | page.enableUnselectSelected.click();
112 |
113 | if (isPicker) {
114 | input.click();
115 | }
116 |
117 | dayClick();
118 | expect(page.selectedMonth.isPresent()).toEqual(false);
119 | };
120 |
121 | monthRunner(page.monthPickerMenu, page.monthPickerInput, true);
122 | monthRunner(page.monthDirectiveMenu, page.monthDirectiveInput, true);
123 | monthRunner(page.monthInlineMenu, null, false);
124 | });
125 | });
126 |
--------------------------------------------------------------------------------
/src/app/common/services/dom-appender/dom-appender.service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 | import {TDrops, TOpens} from '../../types/poistions.type';
3 |
4 | @Injectable()
5 | export class DomHelper {
6 |
7 | private static setYAxisPosition(element: HTMLElement, container: HTMLElement, anchor: HTMLElement, drops: TDrops) {
8 | const anchorRect = anchor.getBoundingClientRect();
9 | const containerRect = container.getBoundingClientRect();
10 | const bottom = anchorRect.bottom - containerRect.top;
11 | const top = anchorRect.top - containerRect.top;
12 |
13 | if (drops === 'down') {
14 | element.style.top = (bottom + 1 + 'px');
15 | } else {
16 | element.style.top = (top - 1 - element.scrollHeight) + 'px';
17 | }
18 | }
19 |
20 | private static setXAxisPosition(element: HTMLElement, container: HTMLElement, anchor: HTMLElement, dimElem: HTMLElement, opens: TOpens) {
21 | const anchorRect = anchor.getBoundingClientRect();
22 | const containerRect = container.getBoundingClientRect();
23 | const left = anchorRect.left - containerRect.left;
24 |
25 | if (opens === 'right') {
26 | element.style.left = left + 'px';
27 | } else {
28 | element.style.left = left - dimElem.offsetWidth + anchor.offsetWidth + 'px';
29 | }
30 | }
31 |
32 | private static isTopInView(el: HTMLElement): boolean {
33 | const {top} = el.getBoundingClientRect();
34 | return (top >= 0);
35 | }
36 |
37 | private static isBottomInView(el: HTMLElement): boolean {
38 | const {bottom} = el.getBoundingClientRect();
39 | return (bottom <= window.innerHeight);
40 | }
41 |
42 | private static isLeftInView(el: HTMLElement): boolean {
43 | const {left} = el.getBoundingClientRect();
44 | return (left >= 0);
45 | }
46 |
47 | private static isRightInView(el: HTMLElement): boolean {
48 | const {right} = el.getBoundingClientRect();
49 | return (right <= window.innerWidth);
50 | }
51 |
52 | appendElementToPosition(config: IAppendToArgs): void {
53 | const {container, element} = config;
54 |
55 | if (!container.style.position || container.style.position === 'static') {
56 | container.style.position = 'relative';
57 | }
58 |
59 | if (element.style.position !== 'absolute') {
60 | element.style.position = 'absolute';
61 | }
62 |
63 | element.style.visibility = 'hidden';
64 |
65 | setTimeout(() => {
66 | this.setElementPosition(config);
67 |
68 | element.style.visibility = 'visible';
69 | });
70 | }
71 |
72 | setElementPosition({element, container, anchor, dimElem, drops, opens}: IAppendToArgs) {
73 | DomHelper.setYAxisPosition(element, container, anchor, 'down');
74 | DomHelper.setXAxisPosition(element, container, anchor, dimElem, 'right');
75 |
76 | if (drops !== 'down' && drops !== 'up') {
77 | if (DomHelper.isBottomInView(dimElem)) {
78 | DomHelper.setYAxisPosition(element, container, anchor, 'down');
79 | } else if (DomHelper.isTopInView(dimElem)) {
80 | DomHelper.setYAxisPosition(element, container, anchor, 'up');
81 | }
82 | } else {
83 | DomHelper.setYAxisPosition(element, container, anchor, drops);
84 | }
85 |
86 | if (opens !== 'left' && opens !== 'right') {
87 | if (DomHelper.isRightInView(dimElem)) {
88 | DomHelper.setXAxisPosition(element, container, anchor, dimElem, 'right');
89 | } else if (DomHelper.isLeftInView(dimElem)) {
90 | DomHelper.setXAxisPosition(element, container, anchor, dimElem, 'left');
91 | }
92 | } else {
93 | DomHelper.setXAxisPosition(element, container, anchor, dimElem, opens);
94 | }
95 | }
96 | }
97 |
98 | export interface IAppendToArgs {
99 | container: HTMLElement;
100 | element: HTMLElement;
101 | anchor: HTMLElement;
102 | dimElem: HTMLElement;
103 | drops: TDrops;
104 | opens: TOpens;
105 | }
106 |
--------------------------------------------------------------------------------
/src/app/demo/demo/demo.component.less:
--------------------------------------------------------------------------------
1 | @import '../../common/styles/variables';
2 |
3 | input{
4 | padding: 5px 0;
5 | }
6 | & {
7 |
8 | :host {
9 | font-family: 'Roboto', sans-serif;
10 | }
11 |
12 | @stickyHeight: 120px;
13 | @stickyHeightInlineDay: 340px;
14 | @stickyHeightInlineMonth: 280px;
15 | @stickyHeightInlineTime: 180px;
16 | @stickyHeightInlineDayTime: 420px;
17 | @menuWidth: 200px;
18 |
19 | .dp-demo-page {
20 | padding: 5px;
21 | margin-left: @menuWidth;
22 | }
23 |
24 | .dp-donate-btn {
25 | position: absolute;
26 | left: 20px;
27 | top: 20px;
28 | border-radius: 3px;
29 | background: #f9fafb;
30 | border: 0;
31 | padding: 6px 12px;
32 | font-weight: bold;
33 | box-shadow: 0 0 3px 0px rgba(0, 0, 0, 0.49);
34 | outline: none;
35 | color: #333333;
36 | }
37 |
38 | .dp-github-container {
39 | position: absolute;
40 | right: 20px;
41 | top: 20px;
42 | }
43 |
44 | .dp-menu {
45 | .dp-menu-header {
46 | padding-left: 10px;
47 | padding-bottom: 10px;
48 | color: @c-white;
49 | }
50 |
51 | position: fixed;
52 | top: 0;
53 | left: 0;
54 | height: 100vh;
55 | width: @menuWidth;
56 | background: @demo-primary;
57 | z-index: 100;
58 | overflow: auto;
59 |
60 | ul {
61 | list-style-type: none;
62 | margin: 0;
63 | padding: 0;
64 |
65 | li {
66 | height: 50px;
67 |
68 | a {
69 | display: flex;
70 | height: 100%;
71 | align-items: center;
72 | padding-left: 10px;
73 | text-decoration: none;
74 | color: @c-white;
75 |
76 | &:hover, &.dp-active-item {
77 | background: darken(@demo-primary, 20%);
78 | }
79 | }
80 | }
81 | }
82 | }
83 |
84 | .dp-page-header {
85 | font-weight: 700;
86 | }
87 |
88 | .dp-demo-container {
89 | position: fixed;
90 | top: 0;
91 | left: @menuWidth;
92 | right: 0;
93 | height: @stickyHeight;
94 | text-align: center;
95 | background: lighten(@demo-primary, 50%);;
96 | z-index: 10;
97 |
98 | &.dp-not-top {
99 | box-shadow: 0 2px 6px 1px @c-light-gray;
100 | }
101 |
102 | &.dp-inline-day {
103 | height: @stickyHeightInlineDay;
104 | }
105 |
106 | &.dp-inline-month {
107 | height: @stickyHeightInlineMonth;
108 | }
109 |
110 | &.dp-inline-time {
111 | height: @stickyHeightInlineTime;
112 | }
113 |
114 | &.dp-inline-day-time {
115 | height: @stickyHeightInlineDayTime;
116 | }
117 | }
118 |
119 | .dp-inline {
120 | display: inline-block;
121 | }
122 |
123 | .dp-attributes, .dp-configs, .dp-api {
124 | width: 33%;
125 | display: inline-block;
126 | vertical-align: top;
127 | }
128 |
129 | .dp-option {
130 | padding: 10px;
131 |
132 | &:nth-child(odd) {
133 | background: fade(@c-light-gray, 30);
134 | }
135 | }
136 |
137 | .dp-week-days {
138 | input {
139 | width: 37px;
140 | }
141 | }
142 |
143 | .dp-page-content {
144 | margin-top: @stickyHeight;
145 | margin-bottom: @stickyHeight / 3;
146 |
147 | &.dp-inline-day {
148 | margin-top: @stickyHeightInlineDay;
149 | }
150 |
151 | &.dp-inline-month {
152 | margin-top: @stickyHeightInlineMonth;
153 | }
154 |
155 | &.dp-inline-time {
156 | margin-top: @stickyHeightInlineTime;
157 | }
158 |
159 | &.dp-inline-day-time {
160 | margin-top: @stickyHeightInlineDayTime;
161 | }
162 | }
163 | .code{
164 | position:fixed;
165 | left:200px;
166 | right:0;
167 | bottom:0;
168 | background: lighten(@demo-primary, 30%);
169 | color: #fff;
170 | padding:10px;
171 | text-align: center;
172 | z-index: 5;
173 | }
174 |
175 | .dp-place-holder {
176 | width: 1px;
177 | height: 1px;
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/src/app/day-calendar/day-calendar.component.spec.ts:
--------------------------------------------------------------------------------
1 | import {async, ComponentFixture, TestBed} from '@angular/core/testing';
2 | import {UtilsService} from '../common/services/utils/utils.service';
3 | import {CalendarNavComponent} from '../calendar-nav/calendar-nav.component';
4 | import * as momentNs from 'jalali-moment';
5 | import {Moment} from 'jalali-moment';
6 | import {DayCalendarComponent} from './day-calendar.component';
7 | import {DayCalendarService} from './day-calendar.service';
8 | import {MonthCalendarComponent} from '../month-calendar/month-calendar.component';
9 | import {IDay} from './day.model';
10 | const moment = momentNs;
11 |
12 | describe('Component: DayCalendarComponent', () => {
13 | let component: DayCalendarComponent;
14 | let fixture: ComponentFixture;
15 |
16 | beforeEach(async(() => {
17 | TestBed.configureTestingModule({
18 | declarations: [DayCalendarComponent, CalendarNavComponent, MonthCalendarComponent],
19 | providers: [DayCalendarService, UtilsService]
20 | }).compileComponents();
21 | }));
22 |
23 | beforeEach(() => {
24 | fixture = TestBed.createComponent(DayCalendarComponent);
25 | component = fixture.componentInstance;
26 | component.config = component.dayCalendarService.getConfig({});
27 | fixture.detectChanges();
28 | });
29 |
30 | it('should create', () => {
31 | expect(component).toBeTruthy();
32 | });
33 |
34 | it('should check getMonthBtnText default value', () => {
35 | expect(component.getDayBtnText({
36 | date: moment('05-04-2017', 'DD-MM-YYYY')
37 | } as IDay)).toEqual('5');
38 | });
39 |
40 | describe('should have the right CSS classes for', () => {
41 | const defaultDay: IDay = {
42 | date: undefined,
43 | selected: false,
44 | currentMonth: false,
45 | prevMonth: false,
46 | nextMonth: false,
47 | currentDay: false
48 | };
49 | const defaultCssClasses: {[klass: string]: boolean} = {
50 | 'dp-selected': false,
51 | 'dp-current-month': false,
52 | 'dp-prev-month': false,
53 | 'dp-next-month': false,
54 | 'dp-current-day': false
55 | };
56 |
57 | it('the selected day', () => {
58 | expect(component.getDayBtnCssClass({
59 | ...defaultDay,
60 | selected: true
61 | } as IDay)).toEqual({
62 | ...defaultCssClasses,
63 | 'dp-selected': true
64 | });
65 | });
66 |
67 | it('the current month', () => {
68 | expect(component.getDayBtnCssClass({
69 | ...defaultDay,
70 | currentMonth: true
71 | } as IDay)).toEqual({
72 | ...defaultCssClasses,
73 | 'dp-current-month': true
74 | });
75 | });
76 |
77 | it('the previous month', () => {
78 | expect(component.getDayBtnCssClass({
79 | ...defaultDay,
80 | prevMonth: true
81 | } as IDay)).toEqual({
82 | ...defaultCssClasses,
83 | 'dp-prev-month': true
84 | });
85 | });
86 |
87 | it('the next month', () => {
88 | expect(component.getDayBtnCssClass({
89 | ...defaultDay,
90 | nextMonth: true
91 | } as IDay)).toEqual({
92 | ...defaultCssClasses,
93 | 'dp-next-month': true
94 | });
95 | });
96 |
97 | it('the current day', () => {
98 | expect(component.getDayBtnCssClass({
99 | ...defaultDay,
100 | currentDay: true
101 | } as IDay)).toEqual({
102 | ...defaultCssClasses,
103 | 'dp-current-day': true
104 | });
105 | });
106 |
107 | it('custom days', () => {
108 | component.componentConfig.dayBtnCssClassCallback = (day: Moment) => 'custom-class';
109 |
110 | expect(component.getDayBtnCssClass({
111 | ...defaultDay
112 | } as IDay)).toEqual({
113 | ...defaultCssClasses,
114 | 'custom-class': true
115 | });
116 | });
117 | });
118 |
119 | describe('should have the correct weekday format', () => {
120 | it('weekdayFormat', () => {
121 | component.componentConfig.weekDayFormat = 'd';
122 |
123 | expect(component.getWeekdayName(moment())).toBe(moment().format('d'));
124 | });
125 |
126 | it('weekdayFormatter', () => {
127 | component.componentConfig.weekDayFormatter = (x: number) => x.toString();
128 |
129 | expect(component.getWeekdayName(moment())).toBe(moment().day().toString());
130 | });
131 | });
132 |
133 | it('should emit event goToCurrent function called', () => {
134 | spyOn(component.onGoToCurrent, 'emit');
135 | component.goToCurrent();
136 | expect(component.onGoToCurrent.emit).toHaveBeenCalled();
137 | });
138 | });
139 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribute
2 |
3 | ## Introduction
4 |
5 | First, thank you for considering contributing to angular-datepicker! It's people like you that make the open source community such a great community! 😊
6 |
7 | We welcome any type of contribution, not only code. You can help with
8 | - **QA**: file bug reports, the more details you can give the better (e.g. screenshots with the console open)
9 | - **Marketing**: writing blog posts, howto's, printing stickers, ...
10 | - **Community**: presenting the project at meetups, organizing a dedicated meetup for the local community, ...
11 | - **Code**: take a look at the [open issues](issues). Even if you can't write code, commenting on them, showing that you care about a given issue matters. It helps us triage them.
12 | - **Money**: we welcome financial contributions in full transparency on our [open collective](https://opencollective.com/angular-datepicker).
13 |
14 | ## Your First Contribution
15 |
16 | Working on your first Pull Request? You can learn how from this *free* series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).
17 |
18 | ## Submitting code
19 |
20 | Any code change should be submitted as a pull request. The description should explain what the code does and give steps to execute it. The pull request should also contain tests.
21 |
22 | ## Code review process
23 |
24 | The bigger the pull request, the longer it will take to review and merge. Try to break down large pull requests in smaller chunks that are easier to review and merge.
25 | It is also always helpful to have some context for your pull request. What was the purpose? Why does it matter to you?
26 |
27 | ## Financial contributions
28 |
29 | We also welcome financial contributions in full transparency on our [open collective](https://opencollective.com/angular-datepicker).
30 | Anyone can file an expense. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed.
31 |
32 | ## Questions
33 |
34 | If you have any questions, create an [issue](issue) (protip: do a quick search first to see if someone else didn't ask the same question before!).
35 | You can also reach us at hello@angular-datepicker.opencollective.com.
36 |
37 | ## Credits
38 |
39 | ### Contributors
40 |
41 | Thank you to all the people who have already contributed to angular-datepicker!
42 |
43 |
44 |
45 | ### Backers
46 |
47 | Thank you to all our backers! [[Become a backer](https://opencollective.com/angular-datepicker#backer)]
48 |
49 |
50 |
51 |
52 | ### Sponsors
53 |
54 | Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/angular-datepicker#sponsor))
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/src/app/time-select/time-select.service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 | import * as momentNs from 'jalali-moment';
3 | import {Moment} from 'jalali-moment';
4 | import {UtilsService} from '../common/services/utils/utils.service';
5 | import {ITimeSelectConfig, ITimeSelectConfigInternal} from './time-select-config.model';
6 | const moment = momentNs;
7 |
8 | export type TimeUnit = 'hour' | 'minute' | 'second';
9 | export const FIRST_PM_HOUR = 12;
10 |
11 | @Injectable()
12 | export class TimeSelectService {
13 | readonly DEFAULT_CONFIG: ITimeSelectConfigInternal = {
14 | hours12Format: 'hh',
15 | hours24Format: 'HH',
16 | meridiemFormat: 'A',
17 | minutesFormat: 'mm',
18 | minutesInterval: 1,
19 | secondsFormat: 'ss',
20 | secondsInterval: 1,
21 | showSeconds: false,
22 | showTwentyFourHours: false,
23 | timeSeparator: ':',
24 | locale: 'fa'
25 | };
26 |
27 | constructor(private utilsService: UtilsService) {
28 | }
29 |
30 | getConfig(config: ITimeSelectConfig): ITimeSelectConfigInternal {
31 | const timeConfigs = {
32 | maxTime: this.utilsService.onlyTime((config && config.maxTime)),
33 | minTime: this.utilsService.onlyTime((config && config.minTime))
34 | };
35 |
36 | const _config = {
37 | ...this.DEFAULT_CONFIG,
38 | ...this.utilsService.clearUndefined(config),
39 | ...timeConfigs
40 | };
41 |
42 | // moment.locale(_config.locale);
43 |
44 | return _config;
45 | }
46 |
47 | getTimeFormat(config: ITimeSelectConfigInternal): string {
48 | return (config.showTwentyFourHours ? config.hours24Format : config.hours12Format)
49 | + config.timeSeparator + config.minutesFormat
50 | + (config.showSeconds ? (config.timeSeparator + config.secondsFormat) : '')
51 | + (config.showTwentyFourHours ? '' : ' ' + config.meridiemFormat);
52 | }
53 |
54 | getHours(config: ITimeSelectConfigInternal, t: Moment | null): string {
55 | const time = t || moment();
56 | return time && time.format(config.showTwentyFourHours ? config.hours24Format : config.hours12Format);
57 | }
58 |
59 | getMinutes(config: ITimeSelectConfigInternal, t: Moment | null): string {
60 | const time = t || moment();
61 | return time && time.format(config.minutesFormat);
62 | }
63 |
64 | getSeconds(config: ITimeSelectConfigInternal, t: Moment | null): string {
65 | const time = t || moment();
66 | return time && time.format(config.secondsFormat);
67 | }
68 |
69 | getMeridiem(config: ITimeSelectConfigInternal, time: Moment): string {
70 | if (config.locale) {
71 | time.locale(config.locale);
72 | }
73 | return time && time.format(config.meridiemFormat);
74 | }
75 |
76 | decrease(config: ITimeSelectConfigInternal, time: Moment, unit: TimeUnit): Moment {
77 | let amount: number = 1;
78 | switch (unit) {
79 | case 'minute':
80 | amount = config.minutesInterval;
81 | break;
82 | case 'second':
83 | amount = config.secondsInterval;
84 | break;
85 | }
86 | return time.clone().subtract(amount, unit);
87 | }
88 |
89 | increase(config: ITimeSelectConfigInternal, time: Moment, unit: TimeUnit): Moment {
90 | let amount: number = 1;
91 | switch (unit) {
92 | case 'minute':
93 | amount = config.minutesInterval;
94 | break;
95 | case 'second':
96 | amount = config.secondsInterval;
97 | break;
98 | }
99 | return time.clone().add(amount, unit);
100 | }
101 |
102 | toggleMeridiem(time: Moment): Moment {
103 | if (time.hours() < FIRST_PM_HOUR) {
104 | return time.clone().add(12, 'hour');
105 | } else {
106 | return time.clone().subtract(12, 'hour');
107 | }
108 | }
109 |
110 | shouldShowDecrease(config: ITimeSelectConfigInternal, time: Moment, unit: TimeUnit): boolean {
111 | if (!config.min && !config.minTime) {
112 | return true;
113 | }
114 |
115 | const newTime = this.decrease(config, time, unit);
116 |
117 | return (!config.min || config.min.isSameOrBefore(newTime))
118 | && (!config.minTime || config.minTime.isSameOrBefore(this.utilsService.onlyTime(newTime)));
119 | }
120 |
121 | shouldShowIncrease(config: ITimeSelectConfigInternal, time: Moment, unit: TimeUnit): boolean {
122 | if (!config.max && !config.maxTime) {
123 | return true;
124 | }
125 |
126 | const newTime = this.increase(config, time, unit);
127 |
128 | return (!config.max || config.max.isSameOrAfter(newTime))
129 | && (!config.maxTime || config.maxTime.isSameOrAfter(this.utilsService.onlyTime(newTime)));
130 | }
131 |
132 | shouldShowToggleMeridiem(config: ITimeSelectConfigInternal, time: Moment): boolean {
133 | if (!config.min && !config.max && !config.minTime && !config.maxTime) {
134 | return true;
135 | }
136 | const newTime = this.toggleMeridiem(time);
137 | return (!config.max || config.max.isSameOrAfter(newTime))
138 | && (!config.min || config.min.isSameOrBefore(newTime))
139 | && (!config.maxTime || config.maxTime.isSameOrAfter(this.utilsService.onlyTime(newTime)))
140 | && (!config.minTime || config.minTime.isSameOrBefore(this.utilsService.onlyTime(newTime)));
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/app/common/services/utils/utils.service.spec.ts:
--------------------------------------------------------------------------------
1 | import {inject, TestBed} from '@angular/core/testing';
2 | import {UtilsService} from './utils.service';
3 | import * as momentNs from 'jalali-moment';
4 | import {IDate} from '../../models/date.model';
5 |
6 | const moment = momentNs;
7 |
8 | describe('Service: ObUtilsService', () => {
9 | beforeEach(() => {
10 | TestBed.configureTestingModule({
11 | providers: [UtilsService]
12 | });
13 | });
14 |
15 | it('should ...', inject([UtilsService], (service: UtilsService) => {
16 | expect(service).toBeTruthy();
17 | }));
18 |
19 | it('should check isDateValid method', inject([UtilsService], (service: UtilsService) => {
20 | expect(service.isDateValid('02-02-1400', 'DD-MM-YYYY')).toBe(true);
21 | // expect(service.isDateValid('13-10-1400', 'DD-MM-YY')).toBe(false);
22 | expect(service.isDateValid('', 'DD-MM-YY')).toBe(true);
23 | expect(service.isDateValid('1400/02/29', 'YYYY/MM/DD')).toBe(true);
24 | expect(service.isDateValid('1400/02/27', 'YYYY/MM/DD')).toBe(true);
25 | }));
26 |
27 | it('should check updateSelected method for day', inject([UtilsService], (service: UtilsService) => {
28 | const date1: IDate = {
29 | date: moment('21-04-2017', 'DD-MM-YYYY'),
30 | selected: false
31 | };
32 |
33 | const date2: IDate = {
34 | date: moment('22-04-2017', 'DD-MM-YYYY'),
35 | selected: false
36 | };
37 |
38 | let arr1 = service.updateSelected(false, [], date1, 'day');
39 | expect(arr1.length).toEqual(1);
40 | expect(arr1[0]).toBe(date1.date);
41 |
42 | arr1 = service.updateSelected(false, [], date2, 'day');
43 | expect(arr1.length).toEqual(1);
44 | expect(arr1[0]).toBe(date2.date);
45 |
46 | date1.selected = true;
47 | const arr2 = service.updateSelected(false, arr1, date1, 'day');
48 | expect(arr2.length).toEqual(0);
49 |
50 | date2.selected = false;
51 | expect(service.updateSelected(true, [date1.date], date2, 'day').length).toEqual(2);
52 |
53 | date1.selected = true;
54 | expect(service.updateSelected(true, [date1.date], date1, 'day').length).toEqual(0);
55 | }));
56 |
57 | it('should check updateSelected method for month', inject([UtilsService], (service: UtilsService) => {
58 | const date1: IDate = {
59 | date: moment('21-04-2017', 'DD-MM-YYYY'),
60 | selected: false
61 | };
62 |
63 | const date2: IDate = {
64 | date: moment('22-04-2017', 'DD-MM-YYYY'),
65 | selected: false
66 | };
67 |
68 | const date3: IDate = {
69 | date: moment('22-05-2017', 'DD-MM-YYYY'),
70 | selected: false
71 | };
72 |
73 | let arr1 = service.updateSelected(false, [], date1, 'month');
74 | expect(arr1.length).toEqual(1);
75 | expect(arr1[0]).toBe(date1.date);
76 |
77 | arr1 = service.updateSelected(false, [], date2, 'month');
78 | expect(arr1.length).toEqual(1);
79 | expect(arr1[0]).toBe(date2.date);
80 |
81 | date1.selected = true;
82 | const arr2 = service.updateSelected(false, arr1, date1, 'month');
83 | expect(arr2.length).toEqual(0);
84 |
85 | date3.selected = false;
86 | expect(service.updateSelected(true, [date1.date], date3, 'month').length).toEqual(2);
87 |
88 | date1.selected = true;
89 | expect(service.updateSelected(true, [date1.date], date1, 'month').length).toEqual(0);
90 | }));
91 |
92 | it('should check if date is in range', inject([UtilsService], (service: UtilsService) => {
93 | expect(service.isDateInRange(moment(), null, null)).toBeTruthy();
94 | expect(service.isDateInRange(moment(), moment().subtract(1, 'd'), null)).toBeTruthy();
95 | expect(service.isDateInRange(
96 | moment().subtract(2, 'd'),
97 | moment().subtract(1, 'd'),
98 | moment().add(1, 'd'))
99 | ).toBeFalsy();
100 | }));
101 |
102 | it('should convertPropsToMoment method', inject([UtilsService], (service: UtilsService) => {
103 | const obj = {min: '14-01-1987', max: '14-01-1987'};
104 | service.convertPropsToMoment(obj, 'DD-MM-YYYY', ['min', 'max']);
105 | expect(moment.isMoment(obj.min)).toBeTruthy();
106 | expect(moment.isMoment(obj.max)).toBeTruthy();
107 | }));
108 |
109 | it('should test datesStringToStringArray', inject([UtilsService], (service: UtilsService) => {
110 | expect(service.datesStringToStringArray('')).toEqual([]);
111 | expect(service.datesStringToStringArray('14-01-1984')).toEqual(['14-01-1984']);
112 | expect(service.datesStringToStringArray('14-01-1984|15-01-1984'))
113 | .toEqual(['14-01-1984', '15-01-1984']);
114 |
115 | expect(service.datesStringToStringArray(''))
116 | .toEqual([]);
117 | expect(service.datesStringToStringArray('14,01-1984|15,01-1984'))
118 | .toEqual(['14,01-1984', '15,01-1984']);
119 | expect(service.datesStringToStringArray('14,01-1984| asdasd'))
120 | .toEqual(['14,01-1984', 'asdasd']);
121 | }));
122 |
123 | it('check convertToString', inject([UtilsService], (service: UtilsService) => {
124 | const format = 'MM/DD/YYYY';
125 | expect(service.convertToString(null, format)).toEqual('');
126 | expect(service.convertToString('', format)).toEqual('');
127 | expect(service.convertToString(moment(), format)).toEqual(moment().format(format));
128 | expect(service.convertToString([moment()], format)).toEqual(moment().format(format));
129 | expect(service.convertToString([moment(), moment().add(1, 'd')], format))
130 | .toEqual(moment().format(format) + ' | ' + moment().add(1, 'd').format(format));
131 | expect(service.convertToString([moment().format(format), moment().add(1, 'd').format(format)], format))
132 | .toEqual(moment().format(format) + ' | ' + moment().add(1, 'd').format(format));
133 | }));
134 | });
135 |
--------------------------------------------------------------------------------
/src/app/date-picker/date-picker.service.ts:
--------------------------------------------------------------------------------
1 | import {EventEmitter, Injectable} from '@angular/core';
2 | import {IDatePickerConfig, IDatePickerConfigInternal} from './date-picker-config.model';
3 | import * as momentNs from 'jalali-moment';
4 | import {Moment} from 'jalali-moment';
5 | import {UtilsService} from '../common/services/utils/utils.service';
6 | import {IDayCalendarConfig} from '../day-calendar/day-calendar-config.model';
7 | import {TimeSelectService} from '../time-select/time-select.service';
8 | import {DayTimeCalendarService} from '../day-time-calendar/day-time-calendar.service';
9 | import {ITimeSelectConfig} from '../time-select/time-select-config.model';
10 | import {CalendarMode} from '../common/types/calendar-mode';
11 | import {CalendarValue} from '../common/types/calendar-value';
12 | const moment = momentNs;
13 |
14 | @Injectable()
15 | export class DatePickerService {
16 | readonly onPickerClosed: EventEmitter = new EventEmitter();
17 | private defaultConfig: IDatePickerConfigInternal = {
18 | closeOnSelect: true,
19 | closeOnSelectDelay: 100,
20 | format: 'YYYY-MM-D',
21 | openOnFocus: true,
22 | openOnClick: true,
23 | onOpenDelay: 0,
24 | disableKeypress: false,
25 | showNearMonthDays: true,
26 | showWeekNumbers: false,
27 | enableMonthSelector: true,
28 | showGoToCurrent: true,
29 | locale: 'fa',
30 | hideOnOutsideClick: true
31 | };
32 | private gregorianExtensionConfig: IDatePickerConfig = {
33 | format: 'DD-MM-YYYY',
34 | locale: 'en'
35 | };
36 | constructor(private utilsService: UtilsService,
37 | private timeSelectService: TimeSelectService,
38 | private daytimeCalendarService: DayTimeCalendarService) {
39 | }
40 |
41 | // todo:: add unit tests
42 | getConfig(config: IDatePickerConfig, mode: CalendarMode = 'daytime'): IDatePickerConfigInternal {
43 | const _config = {
44 | ...this.defaultConfig,
45 | ...((config && config.locale && config.locale !== 'fa') ? this.gregorianExtensionConfig : {}),
46 | format: this.getDefaultFormatByMode(mode, config),
47 | ...this.utilsService.clearUndefined(config)
48 | };
49 |
50 | this.utilsService.convertPropsToMoment(_config, _config.format, ['min', 'max'], _config.locale);
51 |
52 | if (config && config.allowMultiSelect && config.closeOnSelect === undefined) {
53 | _config.closeOnSelect = false;
54 | }
55 |
56 | // moment.locale(_config.locale);
57 |
58 | return _config;
59 | }
60 |
61 | getDayConfigService(pickerConfig: IDatePickerConfig): IDayCalendarConfig {
62 | return {
63 | min: pickerConfig.min,
64 | max: pickerConfig.max,
65 | isDayDisabledCallback: pickerConfig.isDayDisabledCallback,
66 | weekDayFormat: pickerConfig.weekDayFormat,
67 | showNearMonthDays: pickerConfig.showNearMonthDays,
68 | showWeekNumbers: pickerConfig.showWeekNumbers,
69 | firstDayOfWeek: pickerConfig.firstDayOfWeek,
70 | format: pickerConfig.format,
71 | allowMultiSelect: pickerConfig.allowMultiSelect,
72 | monthFormat: pickerConfig.monthFormat,
73 | monthFormatter: pickerConfig.monthFormatter,
74 | enableMonthSelector: pickerConfig.enableMonthSelector,
75 | yearFormat: pickerConfig.yearFormat,
76 | yearFormatter: pickerConfig.yearFormatter,
77 | dayBtnFormat: pickerConfig.dayBtnFormat,
78 | dayBtnFormatter: pickerConfig.dayBtnFormatter,
79 | dayBtnCssClassCallback: pickerConfig.dayBtnCssClassCallback,
80 | monthBtnFormat: pickerConfig.monthBtnFormat,
81 | monthBtnFormatter: pickerConfig.monthBtnFormatter,
82 | monthBtnCssClassCallback: pickerConfig.monthBtnCssClassCallback,
83 | multipleYearsNavigateBy: pickerConfig.multipleYearsNavigateBy,
84 | showMultipleYearsNavigation: pickerConfig.showMultipleYearsNavigation,
85 | locale: pickerConfig.locale,
86 | returnedValueType: pickerConfig.returnedValueType,
87 | showGoToCurrent: pickerConfig.showGoToCurrent,
88 | unSelectOnClick: pickerConfig.unSelectOnClick
89 | };
90 | }
91 |
92 | getDayTimeConfigService(pickerConfig: IDatePickerConfig): ITimeSelectConfig {
93 | return this.daytimeCalendarService.getConfig(pickerConfig);
94 | }
95 |
96 | getTimeConfigService(pickerConfig: IDatePickerConfig): ITimeSelectConfig {
97 | return this.timeSelectService.getConfig(pickerConfig);
98 | }
99 |
100 | pickerClosed() {
101 | this.onPickerClosed.emit();
102 | }
103 |
104 | // todo:: add unit tests
105 | isValidInputDateValue(value: string, config: IDatePickerConfig): boolean {
106 | value = value ? value : '';
107 | const datesStrArr: string[] = this.utilsService.datesStringToStringArray(value);
108 |
109 | return datesStrArr.every(date => this.utilsService.isDateValid(date, config.format, config.locale));
110 | }
111 |
112 | // todo:: add unit tests
113 | convertInputValueToMomentArray(value: string, config: IDatePickerConfig): Moment[] {
114 | value = value ? value : '';
115 | const datesStrArr: string[] = this.utilsService.datesStringToStringArray(value);
116 |
117 | return this.utilsService.convertToMomentArray(datesStrArr, config.format, config.allowMultiSelect, config.locale);
118 | }
119 |
120 | private getDefaultFormatByMode(mode: CalendarMode, config: IDatePickerConfig): string {
121 | let dateFormat = 'YYYY-MM-DD';
122 | let monthFormat = 'MMMM YY';
123 | const timeFormat = 'HH:mm:ss';
124 | if (config && config.locale && config.locale !== 'fa') {
125 | dateFormat = 'DD-MM-YYYY';
126 | monthFormat = 'MMM, YYYY';
127 | }
128 | switch (mode) {
129 | case 'day':
130 | return dateFormat;
131 | case 'daytime':
132 | return dateFormat + ' ' + timeFormat;
133 | case 'time':
134 | return timeFormat;
135 | case 'month':
136 | return monthFormat;
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/e2e/timepicker-e2e.spec.ts:
--------------------------------------------------------------------------------
1 | import {DemoPage} from './app.po';
2 |
3 | describe('dpDayPicker timePicker', () => {
4 | let page: DemoPage;
5 |
6 | beforeEach(() => {
7 | page = new DemoPage();
8 | page.navigateTo();
9 |
10 | page.timePickerMenu.click();
11 | page.dateFormatInput.clear();
12 | page.dateFormatInput.sendKeys('HH:mm:ss');
13 | });
14 |
15 | it('should check if min time validation is working', () => {
16 | page.minTimeValidationPickerInput.clear();
17 | expect(page.timePickerMinTimeValidationMsg.isPresent()).toBe(false);
18 | page.minTimeValidationPickerInput.clear();
19 | page.minTimeValidationPickerInput.sendKeys('10:11:12');
20 | page.timePickerInput.click();
21 | page.timePickerInput.clear();
22 | page.timePickerInput.sendKeys('09:00:00');
23 | page.clickOnBody();
24 | expect(page.timePickerMinTimeValidationMsg.getText()).toEqual('minTime invalid');
25 | page.minTimeValidationPickerInput.clear();
26 | page.minTimeValidationPickerInput.sendKeys('08:07:06');
27 | page.clickOnBody();
28 | expect(page.timePickerMinTimeValidationMsg.isPresent()).toBe(false);
29 | });
30 |
31 | it('should check if max time validation is working', () => {
32 | page.maxTimeValidationPickerInput.clear();
33 | expect(page.timePickerMaxTimeValidationMsg.isPresent()).toBe(false);
34 | page.maxTimeValidationPickerInput.clear();
35 | page.maxTimeValidationPickerInput.sendKeys('08:07:06');
36 | page.timePickerInput.click();
37 | page.timePickerInput.clear();
38 | page.timePickerInput.sendKeys('09:00:00');
39 | page.clickOnBody();
40 | expect(page.timePickerMaxTimeValidationMsg.getText()).toEqual('maxTime invalid');
41 | page.maxTimeValidationPickerInput.clear();
42 | page.maxTimeValidationPickerInput.sendKeys('10:11:12');
43 | expect(page.timePickerMaxTimeValidationMsg.isPresent()).toBe(false);
44 | });
45 |
46 | it('should check that the min selectable time option is working', () => {
47 | page.minTimeInput.click();
48 | page.minTimeInput.clear();
49 | page.minTimeInput.sendKeys('08:07:06');
50 | page.timePickerInput.click();
51 | page.timePickerInput.clear();
52 | page.timePickerInput.sendKeys('09:00:00');
53 | expect(page.hourDownBtn.getAttribute('disabled')).toEqual('true');
54 | expect(page.minuteDownBtn.getAttribute('disabled')).toBe(null);
55 | expect(page.meridiemUpBtn.getAttribute('disabled')).toBe(null);
56 | expect(page.meridiemDownBtn.getAttribute('disabled')).toBe(null);
57 | });
58 |
59 | it('should check that the max selectable time option is working', () => {
60 | page.maxTimeInput.click();
61 | page.maxTimeInput.clear();
62 | page.maxTimeInput.sendKeys('09:07:06');
63 | page.timePickerInput.click();
64 | page.timePickerInput.clear();
65 | page.timePickerInput.sendKeys('09:00:00');
66 | expect(page.hourUpBtn.getAttribute('disabled')).toEqual('true');
67 | expect(page.minuteUpBtn.getAttribute('disabled')).toBe(null);
68 | expect(page.meridiemUpBtn.getAttribute('disabled')).toEqual('true');
69 | expect(page.meridiemDownBtn.getAttribute('disabled')).toEqual('true');
70 | });
71 |
72 | it('should check that the 12 hour time format options work', () => {
73 | page.showSeconds.click();
74 | page.hours12FormatInput.clear();
75 | page.hours12FormatInput.sendKeys('h');
76 | page.minutesFormatInput.clear();
77 | page.minutesFormatInput.sendKeys('m');
78 | page.secondsFormatInput.clear();
79 | page.secondsFormatInput.sendKeys('s');
80 | page.meridiemFormatInput.clear();
81 | page.meridiemFormatInput.sendKeys('a');
82 | page.timeSeparatorInput.clear();
83 | page.timeSeparatorInput.sendKeys('-');
84 | page.timePickerInput.click();
85 | page.timePickerInput.clear();
86 | page.timePickerInput.sendKeys('09:08:07');
87 | expect(page.hourDisplay.getText()).toEqual('9');
88 | expect(page.minuteDisplay.getText()).toEqual('8');
89 | expect(page.secondDisplay.getText()).toEqual('7');
90 | expect(page.meridiemDisplay.getText()).toEqual('am');
91 | expect(page.timeSeparatorDisplay.getText()).toEqual('-');
92 | });
93 |
94 | it('should check that the 24 hour time format options work', () => {
95 | page.showTwentyFourHours.click();
96 | page.showSeconds.click();
97 | page.hours24FormatInput.clear();
98 | page.hours24FormatInput.sendKeys('H');
99 | page.minutesFormatInput.clear();
100 | page.minutesFormatInput.sendKeys('m');
101 | page.secondsFormatInput.clear();
102 | page.secondsFormatInput.sendKeys('s');
103 | page.timeSeparatorInput.clear();
104 | page.timeSeparatorInput.sendKeys('-');
105 | page.timePickerInput.click();
106 | page.timePickerInput.clear();
107 | page.timePickerInput.sendKeys('09:08:07');
108 | expect(page.hourDisplay.getText()).toEqual('9');
109 | expect(page.minuteDisplay.getText()).toEqual('8');
110 | expect(page.secondDisplay.getText()).toEqual('7');
111 | expect(page.timeSeparatorDisplay.getText()).toEqual('-');
112 | });
113 |
114 | it('should check that the interval options work', () => {
115 | page.showSeconds.click();
116 | page.minutesIntervalInput.clear();
117 | page.minutesIntervalInput.sendKeys('6');
118 | page.secondsIntervalInput.clear();
119 | page.secondsIntervalInput.sendKeys('7');
120 | page.timePickerInput.click();
121 | page.timePickerInput.clear();
122 | page.timePickerInput.sendKeys('09:08:07');
123 | page.minuteUpBtn.click();
124 | expect(page.minuteDisplay.getText()).toEqual('14');
125 | expect(page.timePickerInput.getAttribute('value')).toEqual('09:14:07');
126 | page.minuteDownBtn.click();
127 | expect(page.minuteDisplay.getText()).toEqual('08');
128 | expect(page.timePickerInput.getAttribute('value')).toEqual('09:08:07');
129 | page.secondUpBtn.click();
130 | expect(page.secondDisplay.getText()).toEqual('14');
131 | expect(page.timePickerInput.getAttribute('value')).toEqual('09:08:14');
132 | page.secondDownBtn.click();
133 | expect(page.secondDisplay.getText()).toEqual('07');
134 | expect(page.timePickerInput.getAttribute('value')).toEqual('09:08:07');
135 | });
136 | });
137 |
--------------------------------------------------------------------------------
/src/app/day-time-calendar/day-time-calendar.component.ts:
--------------------------------------------------------------------------------
1 | import {ECalendarValue} from '../common/types/calendar-value-enum';
2 | import {SingleCalendarValue} from '../common/types/single-calendar-value';
3 | import {
4 | ChangeDetectionStrategy,
5 | ChangeDetectorRef,
6 | Component,
7 | EventEmitter,
8 | forwardRef,
9 | HostBinding,
10 | Input,
11 | OnChanges,
12 | OnInit,
13 | Output,
14 | SimpleChanges,
15 | ViewChild,
16 | ViewEncapsulation
17 | } from '@angular/core';
18 | import {
19 | ControlValueAccessor,
20 | FormControl,
21 | NG_VALIDATORS,
22 | NG_VALUE_ACCESSOR,
23 | ValidationErrors,
24 | Validator
25 | } from '@angular/forms';
26 | import {Moment} from 'jalali-moment';
27 | import {CalendarValue} from '../common/types/calendar-value';
28 | import {UtilsService} from '../common/services/utils/utils.service';
29 | import {IDate} from '../common/models/date.model';
30 | import {DayCalendarService} from '../day-calendar/day-calendar.service';
31 | import {TimeSelectService} from '../time-select/time-select.service';
32 | import {IDayTimeCalendarConfig} from './day-time-calendar-config.model';
33 | import {DayTimeCalendarService} from './day-time-calendar.service';
34 | import {DateValidator} from '../common/types/validator.type';
35 | import {DayCalendarComponent} from '../day-calendar/day-calendar.component';
36 | import {INavEvent} from '../common/models/navigation-event.model';
37 |
38 | @Component({
39 | selector: 'dp-day-time-calendar',
40 | templateUrl: 'day-time-calendar.component.html',
41 | styleUrls: ['day-time-calendar.component.less'],
42 | changeDetection: ChangeDetectionStrategy.OnPush,
43 | encapsulation: ViewEncapsulation.None,
44 | providers: [
45 | DayTimeCalendarService,
46 | DayCalendarService,
47 | TimeSelectService,
48 | {
49 | provide: NG_VALUE_ACCESSOR,
50 | useExisting: forwardRef(() => DayTimeCalendarComponent),
51 | multi: true
52 | },
53 | {
54 | provide: NG_VALIDATORS,
55 | useExisting: forwardRef(() => DayTimeCalendarComponent),
56 | multi: true
57 | }
58 | ]
59 | })
60 | export class DayTimeCalendarComponent implements OnInit, OnChanges, ControlValueAccessor, Validator {
61 |
62 | @Input() config: IDayTimeCalendarConfig;
63 | @Input() displayDate: SingleCalendarValue;
64 | @Input() minDate: SingleCalendarValue;
65 | @Input() maxDate: SingleCalendarValue;
66 | @HostBinding('class') @Input() theme: string;
67 |
68 | @Output() onChange: EventEmitter = new EventEmitter();
69 | @Output() onGoToCurrent: EventEmitter = new EventEmitter();
70 | @Output() onLeftNav: EventEmitter = new EventEmitter();
71 | @Output() onRightNav: EventEmitter = new EventEmitter();
72 |
73 | @ViewChild('dayCalendar') dayCalendarRef: DayCalendarComponent;
74 |
75 | isInited: boolean = false;
76 | componentConfig: IDayTimeCalendarConfig;
77 | _selected: Moment;
78 | inputValue: CalendarValue;
79 | inputValueType: ECalendarValue;
80 | validateFn: DateValidator;
81 |
82 | set selected(selected: Moment) {
83 | this._selected = selected;
84 | this.onChangeCallback(this.processOnChangeCallback(selected));
85 | }
86 |
87 | get selected(): Moment {
88 | return this._selected;
89 | }
90 |
91 | api = {
92 | moveCalendarTo: this.moveCalendarTo.bind(this)
93 | };
94 |
95 | constructor(public dayTimeCalendarService: DayTimeCalendarService,
96 | public utilsService: UtilsService,
97 | public cd: ChangeDetectorRef) {
98 | }
99 |
100 | ngOnInit() {
101 | this.isInited = true;
102 | this.init();
103 | this.initValidators();
104 | }
105 |
106 | init() {
107 | this.componentConfig = this.dayTimeCalendarService.getConfig(this.config);
108 | this.inputValueType = this.utilsService.getInputType(this.inputValue, false);
109 | }
110 |
111 | ngOnChanges(changes: SimpleChanges) {
112 | if (this.isInited) {
113 | const {minDate, maxDate} = changes;
114 | this.init();
115 |
116 | if (minDate || maxDate) {
117 | this.initValidators();
118 | }
119 | }
120 | }
121 |
122 | writeValue(value: CalendarValue): void {
123 | this.inputValue = value;
124 |
125 | if (value) {
126 | this.selected = this.utilsService
127 | .convertToMomentArray(value, this.componentConfig.format, false, this.componentConfig.locale)[0];
128 | this.inputValueType = this.utilsService
129 | .getInputType(this.inputValue, false);
130 | } else {
131 | this.selected = null;
132 | }
133 |
134 | this.cd.markForCheck();
135 | }
136 |
137 | registerOnChange(fn: any): void {
138 | this.onChangeCallback = fn;
139 | }
140 |
141 | onChangeCallback(_: any) {
142 | };
143 |
144 | registerOnTouched(fn: any): void {
145 | }
146 |
147 | validate(formControl: FormControl): ValidationErrors | any {
148 | if (this.minDate || this.maxDate) {
149 | return this.validateFn(formControl.value);
150 | } else {
151 | return () => null;
152 | }
153 | }
154 |
155 | processOnChangeCallback(value: Moment): CalendarValue {
156 | return this.utilsService.convertFromMomentArray(
157 | this.componentConfig.format,
158 | [value],
159 | this.componentConfig.returnedValueType || this.inputValueType,
160 | this.componentConfig.locale
161 | );
162 | }
163 |
164 | initValidators() {
165 | this.validateFn = this.utilsService.createValidator(
166 | {
167 | minDate: this.minDate,
168 | maxDate: this.maxDate
169 | }, undefined, 'daytime',
170 | this.componentConfig.locale);
171 |
172 | this.onChangeCallback(this.processOnChangeCallback(this.selected));
173 | }
174 |
175 | dateSelected(day: IDate) {
176 | this.selected = this.dayTimeCalendarService.updateDay(this.selected, day.date, this.config);
177 | this.emitChange();
178 | }
179 |
180 | timeChange(time: IDate) {
181 | this.selected = this.dayTimeCalendarService.updateTime(this.selected, time.date);
182 | this.emitChange();
183 | }
184 |
185 | emitChange() {
186 | this.onChange.emit({date: this.selected, selected: false});
187 | }
188 |
189 | moveCalendarTo(to: SingleCalendarValue) {
190 | if (to) {
191 | this.dayCalendarRef.moveCalendarTo(to);
192 | }
193 | }
194 |
195 | onLeftNavClick(change: INavEvent) {
196 | this.onLeftNav.emit(change);
197 | }
198 |
199 | onRightNavClick(change: INavEvent) {
200 | this.onRightNav.emit(change);
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/src/app/time-select/time-select.component.ts:
--------------------------------------------------------------------------------
1 | import {ECalendarValue} from '../common/types/calendar-value-enum';
2 | import {SingleCalendarValue} from '../common/types/single-calendar-value';
3 | import {
4 | ChangeDetectionStrategy,
5 | ChangeDetectorRef,
6 | Component,
7 | EventEmitter,
8 | forwardRef,
9 | HostBinding,
10 | Input,
11 | OnChanges,
12 | OnInit,
13 | Output,
14 | SimpleChanges,
15 | ViewEncapsulation
16 | } from '@angular/core';
17 | import {TimeSelectService, TimeUnit} from './time-select.service';
18 | import * as momentNs from 'jalali-moment';
19 | import {Moment} from 'jalali-moment';
20 | import {ITimeSelectConfig, ITimeSelectConfigInternal} from './time-select-config.model';
21 | import {
22 | ControlValueAccessor,
23 | FormControl,
24 | NG_VALIDATORS,
25 | NG_VALUE_ACCESSOR,
26 | ValidationErrors,
27 | Validator
28 | } from '@angular/forms';
29 | import {CalendarValue} from '../common/types/calendar-value';
30 | import {UtilsService} from '../common/services/utils/utils.service';
31 | import {IDate} from '../common/models/date.model';
32 | import {DateValidator} from '../common/types/validator.type';
33 | const moment = momentNs;
34 |
35 | @Component({
36 | selector: 'dp-time-select',
37 | templateUrl: 'time-select.component.html',
38 | styleUrls: ['time-select.component.less'],
39 | encapsulation: ViewEncapsulation.None,
40 | changeDetection: ChangeDetectionStrategy.OnPush,
41 | providers: [
42 | TimeSelectService,
43 | {
44 | provide: NG_VALUE_ACCESSOR,
45 | useExisting: forwardRef(() => TimeSelectComponent),
46 | multi: true
47 | },
48 | {
49 | provide: NG_VALIDATORS,
50 | useExisting: forwardRef(() => TimeSelectComponent),
51 | multi: true
52 | }
53 | ]
54 | })
55 | export class TimeSelectComponent implements OnInit, OnChanges, ControlValueAccessor, Validator {
56 |
57 | @Input() config: ITimeSelectConfig;
58 | @Input() displayDate: SingleCalendarValue;
59 | @Input() minDate: SingleCalendarValue;
60 | @Input() maxDate: SingleCalendarValue;
61 | @Input() minTime: SingleCalendarValue;
62 | @Input() maxTime: SingleCalendarValue;
63 | @HostBinding('class') @Input() theme: string;
64 |
65 | @Output() onChange: EventEmitter = new EventEmitter();
66 |
67 | isInited: boolean = false;
68 | componentConfig: ITimeSelectConfigInternal;
69 | _selected: Moment;
70 | inputValue: CalendarValue;
71 | inputValueType: ECalendarValue;
72 | validateFn: DateValidator;
73 |
74 | hours: string;
75 | minutes: string;
76 | seconds: string;
77 | meridiem: string;
78 |
79 | showDecHour: boolean;
80 | showDecMinute: boolean;
81 | showDecSecond: boolean;
82 | showIncHour: boolean;
83 | showIncMinute: boolean;
84 | showIncSecond: boolean;
85 | showToggleMeridiem: boolean;
86 |
87 | set selected(selected: Moment) {
88 | this._selected = selected;
89 | this.calculateTimeParts(this.selected);
90 |
91 | this.showDecHour = this.timeSelectService.shouldShowDecrease(this.componentConfig, this._selected, 'hour');
92 | this.showDecMinute = this.timeSelectService.shouldShowDecrease(this.componentConfig, this._selected, 'minute');
93 | this.showDecSecond = this.timeSelectService.shouldShowDecrease(this.componentConfig, this._selected, 'second');
94 |
95 | this.showIncHour = this.timeSelectService.shouldShowIncrease(this.componentConfig, this._selected, 'hour');
96 | this.showIncMinute = this.timeSelectService.shouldShowIncrease(this.componentConfig, this._selected, 'minute');
97 | this.showIncSecond = this.timeSelectService.shouldShowIncrease(this.componentConfig, this._selected, 'second');
98 |
99 | this.showToggleMeridiem = this.timeSelectService.shouldShowToggleMeridiem(this.componentConfig, this._selected);
100 |
101 | this.onChangeCallback(this.processOnChangeCallback(selected));
102 | }
103 |
104 | get selected(): Moment {
105 | return this._selected;
106 | }
107 |
108 | api = {
109 | triggerChange: this.emitChange.bind(this)
110 | };
111 |
112 | constructor(public timeSelectService: TimeSelectService,
113 | public utilsService: UtilsService,
114 | public cd: ChangeDetectorRef) {
115 | }
116 |
117 | ngOnInit() {
118 | this.isInited = true;
119 | this.init();
120 | this.initValidators();
121 | }
122 |
123 | init() {
124 | this.componentConfig = this.timeSelectService.getConfig(this.config);
125 | this.selected = this.selected || moment();
126 | this.inputValueType = this.utilsService.getInputType(this.inputValue, false);
127 | }
128 |
129 | ngOnChanges(changes: SimpleChanges) {
130 | if (this.isInited) {
131 | const {minDate, maxDate, minTime, maxTime} = changes;
132 | this.init();
133 |
134 | if (minDate || maxDate || minTime || maxTime) {
135 | this.initValidators();
136 | }
137 | }
138 | }
139 |
140 | writeValue(value: CalendarValue): void {
141 | this.inputValue = value;
142 |
143 | if (value) {
144 | const momentValue = this.utilsService
145 | .convertToMomentArray(value, this.timeSelectService.getTimeFormat(this.componentConfig), false, this.componentConfig.locale)[0];
146 | if (momentValue.isValid()) {
147 | this.selected = momentValue;
148 | this.inputValueType = this.utilsService
149 | .getInputType(this.inputValue, false);
150 | }
151 | }
152 |
153 | this.cd.markForCheck();
154 | }
155 |
156 | registerOnChange(fn: any): void {
157 | this.onChangeCallback = fn;
158 | }
159 |
160 | onChangeCallback(_: any) {
161 | };
162 |
163 | registerOnTouched(fn: any): void {
164 | }
165 |
166 | validate(formControl: FormControl): ValidationErrors | any {
167 | if (this.minDate || this.maxDate || this.minTime || this.maxTime) {
168 | return this.validateFn(formControl.value);
169 | } else {
170 | return () => null;
171 | }
172 | }
173 |
174 | processOnChangeCallback(value: Moment): CalendarValue {
175 | return this.utilsService.convertFromMomentArray(
176 | this.timeSelectService.getTimeFormat(this.componentConfig),
177 | [value],
178 | this.componentConfig.returnedValueType || this.inputValueType,
179 | this.componentConfig.locale
180 | );
181 | }
182 |
183 | initValidators() {
184 | this.validateFn = this.utilsService.createValidator(
185 | {
186 | minDate: this.minDate,
187 | maxDate: this.maxDate,
188 | minTime: this.minTime,
189 | maxTime: this.maxTime
190 | }, undefined, 'day',
191 | this.componentConfig.locale);
192 |
193 | this.onChangeCallback(this.processOnChangeCallback(this.selected));
194 | }
195 |
196 | decrease(unit: TimeUnit) {
197 | this.selected = this.timeSelectService.decrease(this.componentConfig, this.selected, unit);
198 | this.emitChange();
199 | }
200 |
201 | increase(unit: TimeUnit) {
202 | this.selected = this.timeSelectService.increase(this.componentConfig, this.selected, unit);
203 | this.emitChange();
204 | }
205 |
206 | toggleMeridiem() {
207 | this.selected = this.timeSelectService.toggleMeridiem(this.selected);
208 | this.emitChange();
209 | }
210 |
211 | emitChange() {
212 | this.onChange.emit({date: this.selected, selected: false});
213 | this.cd.markForCheck();
214 | }
215 |
216 | calculateTimeParts(time: Moment) {
217 | this.hours = this.timeSelectService.getHours(this.componentConfig, time);
218 | this.minutes = this.timeSelectService.getMinutes(this.componentConfig, time);
219 | this.seconds = this.timeSelectService.getSeconds(this.componentConfig, time);
220 | this.meridiem = this.timeSelectService.getMeridiem(this.componentConfig, time);
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/src/app/day-calendar/day-calendar.service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 | import * as momentNs from 'jalali-moment';
3 | import {Moment, unitOfTime} from 'jalali-moment';
4 | import {WeekDays} from '../common/types/week-days.type';
5 | import {UtilsService} from '../common/services/utils/utils.service';
6 | import {IDay} from './day.model';
7 | import {IDayCalendarConfig, IDayCalendarConfigInternal} from './day-calendar-config.model';
8 | import {IMonthCalendarConfig} from '../month-calendar/month-calendar-config';
9 | const moment = momentNs;
10 |
11 | @Injectable()
12 | export class DayCalendarService {
13 | private readonly DAYS = ['su', 'mo', 'tu', 'we', 'th', 'fr', 'sa'];
14 | readonly DEFAULT_CONFIG: IDayCalendarConfig = {
15 | showNearMonthDays: true,
16 | showWeekNumbers: false,
17 | firstDayOfWeek: 'sa',
18 | weekDayFormat: 'dd',
19 | format: 'YYYY/M/D',
20 | monthFormat: 'MMMM YY',
21 | dayBtnFormat: 'D',
22 | allowMultiSelect: false,
23 | enableMonthSelector: true,
24 | locale: 'fa'
25 | };
26 | readonly GREGORIAN_CONFIG_EXTENTION: IDayCalendarConfig = {
27 | firstDayOfWeek: 'su',
28 | weekDayFormat: 'ddd',
29 | format: 'DD-MM-YYYY',
30 | monthFormat: 'MMM, YYYY',
31 | locale: 'en',
32 | dayBtnFormat: 'DD',
33 | unSelectOnClick: true
34 | };
35 |
36 | constructor(private utilsService: UtilsService) {
37 | }
38 |
39 | private removeNearMonthWeeks(currentMonth: Moment, monthArray: IDay[][]): IDay[][] {
40 | if (monthArray[monthArray.length - 1].find((day) => day.date.isSame(currentMonth, 'month'))) {
41 | return monthArray;
42 | } else {
43 | return monthArray.slice(0, -1);
44 | }
45 | }
46 |
47 | getConfig(config: IDayCalendarConfig): IDayCalendarConfigInternal {
48 | const _config = {
49 | ...this.DEFAULT_CONFIG,
50 | ...((config && config.locale && config.locale !== 'fa') ? this.GREGORIAN_CONFIG_EXTENTION : {}),
51 | ...this.utilsService.clearUndefined(config)
52 | };
53 |
54 | this.utilsService.convertPropsToMoment(_config, _config.format, ['min', 'max'], _config.locale);
55 |
56 | // moment.locale(_config.locale);
57 |
58 | return _config;
59 | }
60 |
61 | generateDaysMap(firstDayOfWeek: WeekDays) {
62 | const firstDayIndex = this.DAYS.indexOf(firstDayOfWeek);
63 | const daysArr = this.DAYS.slice(firstDayIndex, 7).concat(this.DAYS.slice(0, firstDayIndex));
64 | return daysArr.reduce((map, day, index) => {
65 | map[day] = index;
66 |
67 | return map;
68 | }, <{[key: number]: string}>{});
69 | }
70 |
71 | generateMonthArray(config: IDayCalendarConfigInternal, month: Moment, selected: Moment[]): IDay[][] {
72 | let monthArray: IDay[][] = [];
73 | const firstDayOfWeekIndex = this.DAYS.indexOf(config.firstDayOfWeek);
74 | const firstDayOfBoard = month.clone().startOf('month');
75 | for (let i = 0; i < 8 && (firstDayOfBoard.day() !== firstDayOfWeekIndex) ; i++) {
76 | firstDayOfBoard.subtract(1, 'day');
77 | if (i === 7) {
78 | throw new Error('first day of Board has set Wrong');
79 | }
80 | }
81 |
82 | const current = firstDayOfBoard.clone();
83 | const prevMonth = month.clone().subtract(1, 'month');
84 | const nextMonth = month.clone().add(1, 'month');
85 | const today = moment();
86 |
87 | const daysOfCalendar: IDay[] = this.utilsService.createArray(42)
88 | .reduce((array: IDay[]) => {
89 | array.push({
90 | date: current.clone(),
91 | selected: !!selected.find(selectedDay => current.isSame(selectedDay, 'day')),
92 | currentMonth: current.isSame(month, 'month'),
93 | prevMonth: current.isSame(prevMonth, 'month'),
94 | nextMonth: current.isSame(nextMonth, 'month'),
95 | currentDay: current.isSame(today, 'day'),
96 | disabled: this.isDateDisabled(current, config)
97 | });
98 | current.add(1, 'day');
99 |
100 | if (current.format('HH') !== '00') {
101 | current.startOf('day');
102 | if (array[array.length - 1].date.format('DD') === current.format('DD')) {
103 | current.add(1, 'day');
104 | }
105 | }
106 |
107 | return array;
108 | }, []);
109 |
110 | daysOfCalendar.forEach((day, index) => {
111 | const weekIndex = Math.floor(index / 7);
112 |
113 | if (!monthArray[weekIndex]) {
114 | monthArray.push([]);
115 | }
116 |
117 | monthArray[weekIndex].push(day);
118 | });
119 |
120 | if (!config.showNearMonthDays) {
121 | monthArray = this.removeNearMonthWeeks(month, monthArray);
122 | }
123 |
124 | return monthArray;
125 | }
126 |
127 | generateWeekdays(firstDayOfWeek: WeekDays, locale?: string): Moment[] {
128 | const weekdayNames: {[key: string]: Moment} = ['su', 'mo', 'tu', 'we', 'th', 'fr', 'sa'].reduce((acc, d, i) => {
129 | const m = moment();
130 | if (locale) {
131 | m.locale(locale);
132 | }
133 | m.day(i);
134 | acc[d] = m;
135 | return acc;
136 | }, {});
137 | const weekdays: Moment[] = [];
138 | const daysMap = this.generateDaysMap(firstDayOfWeek);
139 |
140 | for (const dayKey in daysMap) {
141 | if (daysMap.hasOwnProperty(dayKey)) {
142 | weekdays[daysMap[dayKey]] = weekdayNames[dayKey];
143 | }
144 | }
145 |
146 | return weekdays;
147 | }
148 |
149 | isDateDisabled(date: Moment, config: IDayCalendarConfigInternal): boolean {
150 | if (config.isDayDisabledCallback) {
151 | return config.isDayDisabledCallback(date);
152 | }
153 |
154 | if (config.min && date.isBefore(config.min, 'day')) {
155 | return true;
156 | }
157 |
158 | return !!(config.max && date.isAfter(config.max, 'day'));
159 | }
160 |
161 | // todo:: add unit tests
162 | getHeaderLabel(config: IDayCalendarConfigInternal, month: Moment): string {
163 | if (config.monthFormatter) {
164 | return config.monthFormatter(month);
165 | }
166 | month.locale(config.locale);
167 | return month.format(config.monthFormat);
168 | }
169 |
170 | // todo:: add unit tests
171 | shouldShowLeft(min: Moment, currentMonthView: Moment): boolean {
172 | return min ? min.isBefore(currentMonthView, 'month') : true;
173 | }
174 |
175 | // todo:: add unit tests
176 | shouldShowRight(max: Moment, currentMonthView: Moment): boolean {
177 | return max ? max.isAfter(currentMonthView, 'month') : true;
178 | }
179 |
180 | generateDaysIndexMap(firstDayOfWeek: WeekDays) {
181 | const firstDayIndex = this.DAYS.indexOf(firstDayOfWeek);
182 | const daysArr = this.DAYS.slice(firstDayIndex, 7).concat(this.DAYS.slice(0, firstDayIndex));
183 | return daysArr.reduce((map, day, index) => {
184 | map[index] = day;
185 |
186 | return map;
187 | }, <{[key: number]: string}>{});
188 | }
189 |
190 | getMonthCalendarConfig(componentConfig: IDayCalendarConfigInternal): IMonthCalendarConfig {
191 | return this.utilsService.clearUndefined({
192 | min: componentConfig.min,
193 | max: componentConfig.max,
194 | format: componentConfig.format,
195 | isNavHeaderBtnClickable: true,
196 | allowMultiSelect: false,
197 | yearFormat: componentConfig.yearFormat,
198 | locale: componentConfig.locale,
199 | yearFormatter: componentConfig.yearFormatter,
200 | monthBtnFormat: componentConfig.monthBtnFormat,
201 | monthBtnFormatter: componentConfig.monthBtnFormatter,
202 | monthBtnCssClassCallback: componentConfig.monthBtnCssClassCallback,
203 | multipleYearsNavigateBy: componentConfig.multipleYearsNavigateBy,
204 | showMultipleYearsNavigation: componentConfig.showMultipleYearsNavigation,
205 | showGoToCurrent: componentConfig.showGoToCurrent
206 | });
207 | }
208 |
209 | getDayBtnText(config: IDayCalendarConfigInternal, day: Moment): string {
210 | if (config.dayBtnFormatter) {
211 | return config.dayBtnFormatter(day);
212 | }
213 |
214 | return day.format(config.dayBtnFormat);
215 | }
216 |
217 | getDayBtnCssClass(config: IDayCalendarConfigInternal, day: Moment): string {
218 | if (config.dayBtnCssClassCallback) {
219 | return config.dayBtnCssClassCallback(day);
220 | }
221 |
222 | return '';
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 |
5 | # [2.8.1] (2018-03-13)
6 | ### Bug Fixes
7 | - Resolving moment value in Reactive form ([6055041](https://github.com/vlio20/angular-datepicker/commit/6055041)) closes [#371](https://github.com/vlio20/angular-datepicker/issues/371)
8 |
9 |
10 | # [2.8.0] (2018-03-07)
11 | ### Features
12 | - Add `data-date` attribute to each date button ([052b6a8d](https://github.com/vlio20/angular-datepicker/commit/052b6a8d)) closes [#367](https://github.com/vlio20/angular-datepicker/issues/367)
13 | - Show/Hide the picker popup after click outside of the component `hideOnOutsideClick` ([362](https://github.com/vlio20/angular-datepicker/commit/362)) closes [#362](https://github.com/vlio20/angular-datepicker/issues/362)
14 |
15 |
16 | # [2.7.5] (2018-03-05)
17 | ### Bug Fixes
18 | - Adding documentation for `showGoToCurrent` ([b3e3728](https://github.com/vlio20/angular-datepicker/commit/b3e3728)) closes [#357](https://github.com/vlio20/angular-datepicker/issues/357)
19 | - Fixing `inputElementContainer` which did not work on directive + added to docs ([6344b38](https://github.com/vlio20/angular-datepicker/commit/6344b38)) closes [#359](https://github.com/vlio20/angular-datepicker/issues/359)
20 | - Fixing Not able to bind to moment, getting TypeError: `(value || "").split is not a function` ([19cee2d](https://github.com/vlio20/angular-datepicker/commit/19cee2d)) closes [#355](https://github.com/vlio20/angular-datepicker/issues/355)
21 |
22 |
23 | # [2.7.4] (2018-01-13)
24 | - Fixing disabled dates when selecting past/future time from current day ([18db1ca](https://github.com/vlio20/angular-datepicker/commit/18db1ca)) closes [#340](https://github.com/vlio20/angular-datepicker/issues/340)
25 |
26 | ### Features
27 | - Navigation events are now dispatched from all relevant components ([2552889](https://github.com/vlio20/angular-datepicker/commit/2552889)) closes [#329](https://github.com/vlio20/angular-datepicker/issues/329)
28 | - `moveCalendarTo` was added to all components api ([349d48c](https://github.com/vlio20/angular-datepicker/commit/349d48c)) closes [#306](https://github.com/vlio20/angular-datepicker/issues/306)
29 | - `goToCurrent` event was added when go to current button is clicked ([c39080e](https://github.com/vlio20/angular-datepicker/commit/c39080e)) closes [#328](https://github.com/vlio20/angular-datepicker/issues/328) - PR by [@justtal](https://github.com/justtal)
30 | - `unSelectOnClick` was added, this will disable/enable unselection of already selected date ([45e15ac](https://github.com/vlio20/angular-datepicker/commit/45e15ac)) closes [#298](https://github.com/vlio20/angular-datepicker/issues/298)
31 |
32 | ### Improvements
33 | - Change `changeDetectionStrategy` to `onPush` ([a592932](https://github.com/vlio20/angular-datepicker/commit/a592932), [728b342](https://github.com/vlio20/angular-datepicker/commit/728b342), [2fb7073](https://github.com/vlio20/angular-datepicker/commit/2fb7073)) closes [#325](https://github.com/vlio20/angular-datepicker/issues/325)
34 | - Upgrade to Angular 5 ([5abdae1](https://github.com/vlio20/angular-datepicker/commit/5abdae1)) closes [#315](https://github.com/vlio20/angular-datepicker/issues/315)
35 | - Upgrade angular-cli ([5abdae1](https://github.com/vlio20/angular-datepicker/commit/5abdae1))
36 |
37 | ### Bug Fixes
38 | - Updating docs for showing day-calendar api ([c5533de](https://github.com/vlio20/angular-datepicker/commit/c5533de)) closes [#312](https://github.com/vlio20/angular-datepicker/issues/312)
39 | - Prevent overriding of form control value from input updates ([c96f2](https://github.com/vlio20/angular-datepicker/commit/c96f2)) closes [#297](https://github.com/vlio20/angular-datepicker/issues/297) - PR by [@pklein](https://github.com/pklein)
40 |
41 | ### Breaking Changes
42 | - Multiselect delimiter changed to `|` instead of `,` ([8932f52](https://github.com/vlio20/angular-datepicker/commit/8932f52))
43 |
44 |
45 | # [2.6.2] (2017-11-11)
46 |
47 | ### Improvements
48 | - Removing document HostListeners to improve performance ([6324364](https://github.com/vlio20/angular-datepicker/commit/6324364)) closes [#292](https://github.com/vlio20/angular-datepicker/issues/292) - PR by [@mrenou](https://github.com/mrenou)
49 |
50 |
51 | # [2.6.1] (2017-11-03)
52 |
53 | ### Bug Fixes
54 | - Hidden attribute not working on IE10 ([e4de3cb ](https://github.com/vlio20/angular-datepicker/commit/e4de3cb)) closes [#283](https://github.com/vlio20/angular-datepicker/issues/283) - PR by [@mrenou](https://github.com/mrenou)
55 | - Browser translate causes interpolates values to NOT update ([8f6d69e](https://github.com/vlio20/angular-datepicker/commit/8f6d69e)) closes [#277](https://github.com/vlio20/angular-datepicker/issues/277) - PR by [@chrxs](https://github.com/chrxs)
56 | - Clearing the input will remove the selected date ([5bfe724](https://github.com/vlio20/angular-datepicker/commit/5bfe724)) closes [#278](https://github.com/vlio20/angular-datepicker/issues/278)
57 | - Set `min` property dynamically ([969eb01](https://github.com/vlio20/angular-datepicker/commit/969eb01)) closes [#269](https://github.com/vlio20/angular-datepicker/issues/269)
58 | - `disableKeypress` does not work on the directive ([1c00e48](https://github.com/vlio20/angular-datepicker/commit/1c00e48)) closes [#267](https://github.com/vlio20/angular-datepicker/issues/267)
59 | - Go to current button not centered ([9b6dc99](https://github.com/vlio20/angular-datepicker/commit/9b6dc99)) closes [#275](https://github.com/vlio20/angular-datepicker/issues/275) - PR by [@KevinJannis](https://github.com/KevinJannis)
60 |
61 |
62 | # [2.6.0] (2017-10-22)
63 |
64 | ### Features
65 | - Moving go to current button inside the navigation component, enables go to current in inline components ([dd283c5](https://github.com/vlio20/angular-datepicker/commit/dd283c5)).
66 |
67 | ### UI/UX Changes
68 | - Moving go to current button inside the navigation component ([dd283c5](https://github.com/vlio20/angular-datepicker/commit/dd283c5)).
69 |
70 | ### Improvements
71 | - Min date as default when calendar opens ([5663b7a](https://github.com/vlio20/angular-datepicker/commit/5663b7a)) closes [#256](https://github.com/vlio20/angular-datepicker/issues/256)
72 | - Stop using getters in template ([3858dde](https://github.com/vlio20/angular-datepicker/commit/3858dde)) closes [#239](https://github.com/vlio20/angular-datepicker/issues/239)
73 |
74 | ### Bug Fixes
75 | - Fix for `ngOnDestroy` throws error when in SSR ([c2a0c8b](https://github.com/vlio20/angular-datepicker/commit/c2a0c8b)) closes [#163](https://github.com/vlio20/angular-datepicker/issues/163)
76 | - `displayDate` on the directive bug fix ([984aab8](https://github.com/vlio20/angular-datepicker/commit/984aab8)) closes [#254](https://github.com/vlio20/angular-datepicker/issues/254)
77 | - `max`/`min` date support for strings ([ad98d1b](https://github.com/vlio20/angular-datepicker/commit/ad98d1b)) closes [#250](https://github.com/vlio20/angular-datepicker/issues/250)
78 | - `value.split` is not a function bug fix ([38f6ce2](https://github.com/vlio20/angular-datepicker/commit/38f6ce2)) closes [#225](https://github.com/vlio20/angular-datepicker/issues/245)
79 | - Add outputs of each component to docs ([9ee8035](https://github.com/vlio20/angular-datepicker/commit/9ee8035)) closes [#224](https://github.com/vlio20/angular-datepicker/issues/224)
80 | - More than one picker can be opened at the same time ([dd283c5](https://github.com/vlio20/angular-datepicker/commit/dd283c5)) closes [#223](https://github.com/vlio20/angular-datepicker/issues/223)
81 | - Picker not always opens according to drops/opens ([c26d168](https://github.com/vlio20/angular-datepicker/commit/c26d168)) closes [#222](https://github.com/vlio20/angular-datepicker/issues/222)
82 |
83 | ### Breaking Changes
84 | - Go to current button moved from input element to the navigation component ([dd283c5](https://github.com/vlio20/angular-datepicker/commit/dd283c5)).
85 | - Default locale is now set with [`moment.locale()`](https://momentjs.com/docs/#/i18n/getting-locale/) instead of hard coded `en`
86 |
87 |
88 |
89 |
90 | # [2.5.1](https://github.com/vlio20/angular-datepicker/releases/tag/2.5.1) (2017-10-12)
91 |
92 | ### Bug Fixes
93 | - 29th October 2017 displayed twice [#235](https://github.com/vlio20/angular-datepicker/issues/235#issuecomment-336217634)
94 |
--------------------------------------------------------------------------------
/src/app/day-calendar/day-calendar.service.spec.ts:
--------------------------------------------------------------------------------
1 | import {inject, TestBed} from '@angular/core/testing';
2 | import {DayCalendarService} from './day-calendar.service';
3 | import * as momentNs from 'jalali-moment';
4 | import {Moment} from 'jalali-moment';
5 | import {UtilsService} from '../common/services/utils/utils.service';
6 | import {IDayCalendarConfigInternal} from './day-calendar-config.model';
7 | const moment = momentNs;
8 |
9 | describe('Service: Calendar', () => {
10 | beforeEach(() => {
11 | TestBed.configureTestingModule({
12 | providers: [DayCalendarService, UtilsService]
13 | });
14 | });
15 |
16 | it('should check the generateDaysMap method', inject([DayCalendarService],
17 | (service: DayCalendarService) => {
18 | expect(service.generateDaysIndexMap('su'))
19 | .toEqual({0: 'su', 1: 'mo', 2: 'tu', 3: 'we', 4: 'th', 5: 'fr', 6: 'sa'});
20 | expect(service.generateDaysIndexMap('mo'))
21 | .toEqual({0: 'mo', 1: 'tu', 2: 'we', 3: 'th', 4: 'fr', 5: 'sa', 6: 'su'});
22 | expect(service.generateDaysIndexMap('we'))
23 | .toEqual({0: 'we', 1: 'th', 2: 'fr', 3: 'sa', 4: 'su', 5: 'mo', 6: 'tu'});
24 | }));
25 |
26 | it('should check the generateDaysMap method', inject([DayCalendarService],
27 | (service: DayCalendarService) => {
28 | expect(service.generateDaysMap('su'))
29 | .toEqual({'su': 0, 'mo': 1, 'tu': 2, 'we': 3, 'th': 4, 'fr': 5, 'sa': 6});
30 | expect(service.generateDaysMap('mo'))
31 | .toEqual({'mo': 0, 'tu': 1, 'we': 2, 'th': 3, 'fr': 4, 'sa': 5, 'su': 6});
32 | expect(service.generateDaysMap('we'))
33 | .toEqual({'we': 0, 'th': 1, 'fr': 2, 'sa': 3, 'su': 4, 'mo': 5, 'tu': 6});
34 | }
35 | ));
36 |
37 | it('should check the generateMonthArray method', inject([DayCalendarService],
38 | (service: DayCalendarService) => {
39 | let monthWeeks = service.generateMonthArray(
40 | {
41 | firstDayOfWeek: 'su'
42 | },
43 | moment('11-10-2016', 'DD-MM-YYYY'),
44 | [moment('11-10-2016', 'DD-MM-YYYY')]);
45 | expect(monthWeeks[0][0].date.format('DD-MM-YYYY')).toBe('25-09-2016');
46 | expect(monthWeeks[0][0].prevMonth).toBe(true);
47 | expect(monthWeeks[0][0].currentMonth).toBe(false);
48 | expect(monthWeeks[0][0].nextMonth).toBe(false);
49 |
50 | expect(monthWeeks[5][6].date.format('DD-MM-YYYY')).toBe('05-11-2016');
51 | expect(monthWeeks[5][6].prevMonth).toBe(false);
52 | expect(monthWeeks[5][6].currentMonth).toBe(false);
53 | expect(monthWeeks[5][6].nextMonth).toBe(true);
54 |
55 | monthWeeks = service.generateMonthArray(
56 | {
57 | firstDayOfWeek: 'mo'
58 | },
59 | moment('11-10-2016', 'DD-MM-YYYY'),
60 | [moment('11-10-2016', 'DD-MM-YYYY'), moment('13-10-2016', 'DD-MM-YYYY')]
61 | );
62 | expect(monthWeeks[0][0].date.format('DD-MM-YYYY')).toBe('26-09-2016');
63 | expect(monthWeeks[5][6].date.format('DD-MM-YYYY')).toBe('06-11-2016');
64 | expect(monthWeeks[2][1].selected).toBe(true);
65 | expect(monthWeeks[2][3].selected).toBe(true);
66 |
67 | monthWeeks = service.generateMonthArray(
68 | {
69 | firstDayOfWeek: 'mo'
70 | },
71 | moment('11-10-2016', 'DD-MM-YYYY'),
72 | [moment('11-10-2016', 'DD-MM-YYYY')]);
73 | expect(monthWeeks[0][0].date.format('DD-MM-YYYY')).toBe('26-09-2016');
74 | expect(monthWeeks[5][6].date.format('DD-MM-YYYY')).toBe('06-11-2016');
75 | expect(monthWeeks[2][1].selected).toBe(true);
76 | expect(monthWeeks[2][3].selected).toBe(false);
77 |
78 | monthWeeks = service.generateMonthArray(
79 | {
80 | firstDayOfWeek: 'mo'
81 | },
82 | moment('11-10-2016', 'DD-MM-YYYY'),
83 | []
84 | );
85 | expect(monthWeeks[0][0].date.format('DD-MM-YYYY')).toBe('26-09-2016');
86 | expect(monthWeeks[5][6].date.format('DD-MM-YYYY')).toBe('06-11-2016');
87 | expect(monthWeeks[2][1].selected).toBe(false);
88 | expect(monthWeeks[2][3].selected).toBe(false);
89 |
90 | monthWeeks = service.generateMonthArray(
91 | {
92 | firstDayOfWeek: 'mo'
93 | },
94 | moment('11-10-2016', 'DD-MM-YYYY'),
95 | []
96 | );
97 | expect(monthWeeks[0][0].date.format('DD-MM-YYYY')).toBe('26-09-2016');
98 | expect(monthWeeks[5][6].date.format('DD-MM-YYYY')).toBe('06-11-2016');
99 | expect(monthWeeks[2][1].selected).toBe(false);
100 | expect(monthWeeks[2][3].selected).toBe(false);
101 | }));
102 |
103 | it('should check the generateWeekdays method', inject([DayCalendarService],
104 | (service: DayCalendarService) => {
105 | expect(
106 | service.generateWeekdays('su').map(d => d.format('ddd'))
107 | ).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);
108 |
109 | expect(
110 | service.generateWeekdays('mo').map(d => d.format('ddd'))
111 | ).toEqual(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']);
112 | }));
113 |
114 | it('should check isDateDisabled method', inject([DayCalendarService],
115 | (service: DayCalendarService) => {
116 | const config: IDayCalendarConfigInternal = {
117 | firstDayOfWeek: 'su',
118 | min: moment('13-10-2016', 'DD-MM-YYYY').subtract(1, 'day'),
119 | max: moment('13-10-2016', 'DD-MM-YYYY').add(1, 'day')
120 | };
121 |
122 | expect(service.isDateDisabled(moment('11-10-2016', 'DD-MM-YYYY'), config)).toBe(true);
123 | expect(service.isDateDisabled(moment('12-10-2016', 'DD-MM-YYYY'), config)).toBe(false);
124 | expect(service.isDateDisabled(moment('13-10-2016', 'DD-MM-YYYY'), config)).toBe(false);
125 | expect(service.isDateDisabled(moment('14-10-2016', 'DD-MM-YYYY'), config)).toBe(false);
126 | expect(service.isDateDisabled(moment('15-10-2016', 'DD-MM-YYYY'), config)).toBe(true);
127 |
128 | config.isDayDisabledCallback = (date: Moment) => {
129 | return date.isSame(moment('13-10-2016', 'DD-MM-YYYY'), 'day');
130 | };
131 |
132 | expect(service.isDateDisabled(moment('13-10-2016', 'DD-MM-YYYY'), config)).toBe(true);
133 | expect(service.isDateDisabled(moment('11-10-2016', 'DD-MM-YYYY'), config)).toBe(false);
134 | }));
135 |
136 | it('should show/hide near month according to showNearMonthDays configuration', inject([DayCalendarService],
137 | (service: DayCalendarService) => {
138 | const config: IDayCalendarConfigInternal = {
139 | firstDayOfWeek: 'su',
140 | showNearMonthDays: true
141 | };
142 |
143 | expect(service.generateMonthArray(config, moment('27-03-2017', 'DD-MM-YYYY'), []).length).toBe(6);
144 | config.showNearMonthDays = false;
145 | expect(service.generateMonthArray(config, moment('27-03-2017', 'DD-MM-YYYY'), []).length).toBe(5);
146 | }));
147 |
148 | it('should not effect the calendar when no full near weeks even if showNearMonthDays is false',
149 | inject([DayCalendarService],
150 | (service: DayCalendarService) => {
151 | const config: IDayCalendarConfigInternal = {
152 | firstDayOfWeek: 'su',
153 | showNearMonthDays: false
154 | };
155 |
156 | expect(service.generateMonthArray(config, moment('27-04-2017', 'DD-MM-YYYY'), []).length).toBe(6);
157 | }));
158 |
159 | it('should check getDayBtnText method',
160 | inject([DayCalendarService],
161 | (service: DayCalendarService) => {
162 | const date = moment('05-04-2017', 'DD-MM-YYYY');
163 | expect(service.getDayBtnText({dayBtnFormat: 'DD'}, date)).toEqual('05');
164 | expect(service.getDayBtnText({dayBtnFormat: 'D'}, date)).toEqual('5');
165 | expect(service.getDayBtnText({dayBtnFormatter: (m => 'bla')}, date)).toEqual('bla');
166 | expect(service.getDayBtnText({dayBtnFormat: 'DD', dayBtnFormatter: (m => m.format('D'))}, date))
167 | .toEqual('5');
168 | }));
169 |
170 | it('should check getDayBtnCssClass method',
171 | inject([DayCalendarService],
172 | (service: DayCalendarService) => {
173 | const date = moment('05-04-2017', 'DD-MM-YYYY');
174 | expect(service.getDayBtnCssClass({}, date)).toEqual('');
175 | expect(service.getDayBtnCssClass({dayBtnCssClassCallback: (m => 'class1 class2')}, date))
176 | .toEqual('class1 class2');
177 | }));
178 | });
179 |
--------------------------------------------------------------------------------
/src/app/date-picker/date-picker.directive.ts:
--------------------------------------------------------------------------------
1 | import {CalendarMode} from '../common/types/calendar-mode';
2 | import {IDatePickerDirectiveConfig} from './date-picker-directive-config.model';
3 | import {DatePickerDirectiveService} from './date-picker-directive.service';
4 | import {IDpDayPickerApi} from './date-picker.api';
5 | import {DatePickerComponent} from './date-picker.component';
6 | import {
7 | ComponentFactoryResolver,
8 | Directive,
9 | ElementRef,
10 | EventEmitter,
11 | HostListener,
12 | Input,
13 | OnInit,
14 | Optional,
15 | Output,
16 | ViewContainerRef
17 | } from '@angular/core';
18 | import {NgControl} from '@angular/forms';
19 | import {CalendarValue} from '../common/types/calendar-value';
20 | import {SingleCalendarValue} from '../common/types/single-calendar-value';
21 | import {INavEvent} from '../common/models/navigation-event.model';
22 | import {UtilsService} from '../common/services/utils/utils.service'
23 |
24 | @Directive({
25 | exportAs: 'dpDayPicker',
26 | providers: [DatePickerDirectiveService],
27 | selector: '[dpDayPicker]'
28 | })
29 | export class DatePickerDirective implements OnInit {
30 | private _config: IDatePickerDirectiveConfig;
31 | private _attachTo: ElementRef | string;
32 | private _theme: string;
33 | private _mode: CalendarMode = 'day';
34 | private _minDate: SingleCalendarValue;
35 | private _maxDate: SingleCalendarValue;
36 | private _minTime: SingleCalendarValue;
37 | private _maxTime: SingleCalendarValue;
38 | private _displayDate: SingleCalendarValue;
39 |
40 | get config(): IDatePickerDirectiveConfig {
41 | return this._config;
42 | }
43 |
44 | @Input('dpDayPicker') set config(config: IDatePickerDirectiveConfig) {
45 | this._config = this.service.getConfig(config, this.viewContainerRef.element, this.attachTo);
46 | this.updateDatepickerConfig();
47 | this.markForCheck();
48 | }
49 |
50 | get attachTo(): ElementRef | string {
51 | return this._attachTo;
52 | }
53 |
54 | @Input() set attachTo(attachTo: ElementRef | string) {
55 | this._attachTo = attachTo;
56 | this._config = this.service.getConfig(this.config, this.viewContainerRef.element, this.attachTo);
57 | this.updateDatepickerConfig();
58 | this.markForCheck();
59 | }
60 |
61 | get theme(): string {
62 | return this._theme;
63 | }
64 |
65 | @Input() set theme(theme: string) {
66 | this._theme = theme;
67 | if (this.datePicker) {
68 | this.datePicker.theme = theme;
69 | }
70 |
71 | this.markForCheck();
72 | }
73 |
74 | get mode(): CalendarMode {
75 | return this._mode;
76 | }
77 |
78 | @Input() set mode(mode: CalendarMode) {
79 | this._mode = mode;
80 | if (this.datePicker) {
81 | this.datePicker.mode = mode;
82 | }
83 |
84 | this.markForCheck();
85 | }
86 |
87 | @Input() set minDate(minDate: SingleCalendarValue) {
88 | this._minDate = minDate;
89 | if (this.datePicker) {
90 | this.datePicker.minDate = minDate;
91 | this.datePicker.ngOnInit();
92 | }
93 |
94 | this.markForCheck();
95 | }
96 |
97 | get minDate(): SingleCalendarValue {
98 | return this._minDate;
99 | }
100 |
101 | @Input() set maxDate(maxDate: SingleCalendarValue) {
102 | this._maxDate = maxDate;
103 | if (this.datePicker) {
104 | this.datePicker.maxDate = maxDate;
105 | this.datePicker.ngOnInit();
106 | }
107 |
108 | this.markForCheck();
109 | }
110 |
111 | get maxDate(): SingleCalendarValue {
112 | return this._maxDate;
113 | }
114 |
115 | @Input() set minTime(minTime: SingleCalendarValue) {
116 | this._minTime = minTime;
117 | if (this.datePicker) {
118 | this.datePicker.minTime = minTime;
119 | this.datePicker.ngOnInit();
120 | }
121 |
122 | this.markForCheck();
123 | }
124 |
125 | get minTime(): SingleCalendarValue {
126 | return this._minTime;
127 | }
128 |
129 | @Input() set maxTime(maxTime: SingleCalendarValue) {
130 | this._maxTime = maxTime;
131 | if (this.datePicker) {
132 | this.datePicker.maxTime = maxTime;
133 | this.datePicker.ngOnInit();
134 | }
135 |
136 | this.markForCheck();
137 | }
138 |
139 | get maxTime(): SingleCalendarValue {
140 | return this._maxTime;
141 | }
142 |
143 | get displayDate(): SingleCalendarValue {
144 | return this._displayDate;
145 | }
146 |
147 | @Input() set displayDate(displayDate: SingleCalendarValue) {
148 | this._displayDate = displayDate;
149 | this.updateDatepickerConfig();
150 |
151 | this.markForCheck();
152 | }
153 |
154 | @Output() open = new EventEmitter();
155 | @Output() close = new EventEmitter();
156 | @Output() onChange = new EventEmitter();
157 | @Output() onGoToCurrent: EventEmitter = new EventEmitter();
158 | @Output() onLeftNav: EventEmitter = new EventEmitter();
159 | @Output() onRightNav: EventEmitter = new EventEmitter();
160 |
161 | public datePicker: DatePickerComponent;
162 | public api: IDpDayPickerApi;
163 |
164 | constructor(public viewContainerRef: ViewContainerRef,
165 | public elemRef: ElementRef,
166 | public componentFactoryResolver: ComponentFactoryResolver,
167 | public service: DatePickerDirectiveService,
168 | @Optional() public formControl: NgControl,
169 | public utilsService: UtilsService) {
170 | }
171 |
172 | ngOnInit(): void {
173 | this.datePicker = this.createDatePicker();
174 | this.api = this.datePicker.api;
175 | this.updateDatepickerConfig();
176 | this.attachModelToDatePicker();
177 | this.datePicker.theme = this.theme;
178 | }
179 |
180 | createDatePicker(): DatePickerComponent {
181 | const factory = this.componentFactoryResolver.resolveComponentFactory(DatePickerComponent);
182 | return this.viewContainerRef.createComponent(factory).instance;
183 | }
184 |
185 | attachModelToDatePicker() {
186 | if (!this.formControl) {
187 | return;
188 | }
189 |
190 | this.datePicker.onViewDateChange(this.formControl.value);
191 |
192 | this.formControl.valueChanges.subscribe((value) => {
193 | if (value !== this.datePicker.inputElementValue) {
194 | const strVal = this.utilsService.convertToString(value, this.datePicker.componentConfig.format,
195 | this.datePicker.componentConfig.locale);
196 | this.datePicker.onViewDateChange(strVal);
197 | }
198 | });
199 |
200 | let setup = true;
201 |
202 | this.datePicker.registerOnChange((value, changedByInput) => {
203 | if (value) {
204 | const isMultiselectEmpty = setup && Array.isArray(value) && !value.length;
205 |
206 | if (!isMultiselectEmpty && !changedByInput) {
207 | this.formControl.control.setValue(this.datePicker.inputElementValue);
208 | }
209 | }
210 |
211 | const errors = this.datePicker.validateFn(value);
212 |
213 | if (!setup) {
214 | this.formControl.control.markAsDirty({
215 | onlySelf: true
216 | });
217 | } else {
218 | setup = false;
219 | }
220 |
221 | if (errors) {
222 | if (errors.hasOwnProperty('format')) {
223 | const {given} = errors['format'];
224 | this.datePicker.inputElementValue = given;
225 | if (!changedByInput) {
226 | this.formControl.control.setValue(given);
227 | }
228 | }
229 |
230 | this.formControl.control.setErrors(errors);
231 | }
232 | });
233 | }
234 |
235 | @HostListener('click')
236 | onClick() {
237 | this.datePicker.onClick();
238 | }
239 |
240 | @HostListener('focus')
241 | onFocus() {
242 | this.datePicker.inputFocused();
243 | }
244 |
245 | private updateDatepickerConfig() {
246 | if (this.datePicker) {
247 | this.datePicker.minDate = this.minDate;
248 | this.datePicker.maxDate = this.maxDate;
249 | this.datePicker.minTime = this.minTime;
250 | this.datePicker.maxTime = this.maxTime;
251 | this.datePicker.mode = this.mode || 'day';
252 | this.datePicker.displayDate = this.displayDate;
253 | this.datePicker.config = this.config;
254 | this.datePicker.open = this.open;
255 | this.datePicker.close = this.close;
256 | this.datePicker.onChange = this.onChange;
257 | this.datePicker.onGoToCurrent = this.onGoToCurrent;
258 | this.datePicker.onLeftNav = this.onLeftNav;
259 | this.datePicker.onRightNav = this.onRightNav;
260 |
261 | this.datePicker.init();
262 |
263 | if (this.datePicker.componentConfig.disableKeypress) {
264 | this.elemRef.nativeElement.setAttribute('readonly', true);
265 | } else {
266 | this.elemRef.nativeElement.removeAttribute('readonly');
267 | }
268 | }
269 | }
270 |
271 | markForCheck() {
272 | if (this.datePicker) {
273 | this.datePicker.cd.markForCheck();
274 | }
275 | }
276 | }
277 |
--------------------------------------------------------------------------------