├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── babel.config.js
├── cypress.json
├── jest.config.js
├── package-lock.json
├── package.json
├── public
├── favicon.ico
└── index.html
├── src
├── App.vue
├── components
│ ├── DatePicker.vue
│ ├── DateTimePicker.vue
│ ├── TextSlider.vue
│ └── TimePicker.vue
├── directives
│ └── click-outside.js
├── main.js
└── mixins
│ └── helpers.js
└── tests
├── e2e
├── .eslintrc.js
├── plugins
│ └── index.js
├── specs
│ └── DateTimePickerNoProps.js
└── support
│ ├── commands.js
│ └── index.js
└── unit
├── DateTimePicker.spec.js
├── DateTimePicker.valueFormat.spec.js
└── DateTimePicker.valueType.spec.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw*
22 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /src
4 | /public
5 | babel.config.js
6 |
7 | # local env files
8 | .env.local
9 | .env.*.local
10 |
11 | # Log files
12 | npm-debug.log*
13 | yarn-debug.log*
14 | yarn-error.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw*
24 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 10
4 | cache:
5 | directories:
6 | - node_modules
7 | script:
8 | - npm run test:unit
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Vladyslav Shchepotin
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vue Datetime Picker
2 |
3 | [](https://travis-ci.org/Shchepotin/vue-vanilla-datetime-picker)
4 |
5 | Fast, powerful and easy to use component datetime picker for [VueJS](https://vuejs.org/). The component includes localization, highlight and disable date, 12/24-hour time, inline mode, etc.
6 |
7 | ## Demo
8 |
9 | See demo [here](https://codepen.io/Shchepotin/pen/wEQyQx?editors=1010)
10 |
11 | 
12 |
13 | ## Requirements
14 |
15 | - Vue.js `^2.5.0`
16 |
17 | ## Usage
18 |
19 | ```
20 | npm install vue-vanilla-datetime-picker --save
21 | ```
22 |
23 | ```javascript
24 | import DateTimePicker from 'vue-vanilla-datetime-picker';
25 |
26 | Vue.component('date-time-picker', DateTimePicker);
27 | ```
28 |
29 | ```sass
30 | @import "node_modules/vue-vanilla-datetime-picker/dist/DateTimePicker"
31 | ```
32 |
33 | ### Props:
34 |
35 | | Name | Required | Type | Default | Description |
36 | | --------------------- | -------- | ------------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------- |
37 | | v-model, value | * | String, Date, DateTime (luxon) | | Value |
38 | | value-format | | String | yyyy-LL-dd HH:mm:ss | Value [format](https://github.com/moment/luxon/blob/master/docs/formatting.md#table-of-tokens) |
39 | | max-date | | String, Date, DateTime (luxon) | null | Max date |
40 | | min-date | | String, Date, DateTime (luxon) | null | Min date |
41 | | constraints-format | | String | yyyy-LL-dd | Constraints [format](https://github.com/moment/luxon/blob/master/docs/formatting.md#table-of-tokens) |
42 | | locale | | String | en | Set [locale](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry). |
43 | | inline | | Boolean | false | Enable inline mode. |
44 | | disabled | | Boolean | false | Disable datetime picker. |
45 | | format | | String | yyyy-LL-dd HH:mm | Display [format](https://github.com/moment/luxon/blob/master/docs/formatting.md#table-of-tokens). |
46 | | time-picker | | Boolean | true | Show time picker. |
47 | | hour-time | | Number | 24 | Hour in 12/24-hour time. Values: '12', '24'. |
48 | | no-toggle-time-picker | | Boolean | false | No toggle time picker button. |
49 | | only-time-picker | | Boolean | false | Show only time picker. |
50 | | start-from-sunday | | Boolean | false | Set Sunday as first day of week. |
51 | | minute-step | | Number | 1 | Set step for minute. |
52 | | seconds-picker | | Boolean | false | Show second picker. |
53 | | initial-view | | String | days | Initial view: 'days', 'months', 'years' |
54 | | initial-view-date | | String, Date, DateTime (luxon) | days | Initial date view |
55 | | main-button-class | | String | | Class for main button. |
56 | | disabled-dates | | Array | [] | Array of disabled dates. |
57 | | highlighted | | Array | [] | Array of highlighted dates. Example: [{ date: '2018-09-17', class: 'highlighted' }] |
58 | | auto-close | | Boolean | false | Close date picker after select date. |
59 | | clear-button | | Boolean | false | Show "Clear" button. |
60 | | close-button | | Boolean | false | Show "Close" button. |
61 | | today-button | | Boolean | false | Show "Today" button. |
62 | | value-type | | String | Auto | Set value type. Types: 'Auto', 'String', 'Date', 'Luxon'. |
63 | | empty-value | | Any | '' | Set empty value for clear button. |
64 |
65 | ### Slots:
66 |
67 | | Name | Description |
68 | | --------------------- | ----------------------------------------------------------------- |
69 | | choose-date | For main button if date not selected. |
70 | | formatted-datetime | For main button if date selected. |
71 | | date | For date button. |
72 | | time | For time button. |
73 | | months-prev | For previous month button. |
74 | | months-next | For next month button. |
75 | | years-prev | For previous year button. |
76 | | years-next | For next year button. |
77 | | decades-prev | For previous decade button. |
78 | | decades-prev | For next decade button. |
79 | | hours-up | For hours up button. |
80 | | hours-down | For hours down button. |
81 | | minutes-up | For minutes up button. |
82 | | minutes-down | For minutes down button. |
83 | | seconds-up | For seconds up button. |
84 | | seconds-down | For seconds down button. |
85 | | meridiems-up | For meridiems up button. |
86 | | meridiems-down | For meridiems down button. |
87 | | clear | For clear button. |
88 | | close | For close button. |
89 | | today | For today button. |
90 |
91 | ### Events:
92 |
93 | | Name |
94 | | --------------------- |
95 | | close |
96 | | open |
97 | | change-month |
98 | | change-year |
99 | | change-decade |
100 |
101 | ### Methods:
102 |
103 | | Name | Description |
104 | | --------------------- | --------------------- |
105 | | open | Open datetime picker |
106 | | close | Close datetime picker |
107 |
108 | ## What about RTL support?
109 |
110 | If you need an RTL version of component for your project, recommend use [PostCSS](https://www.npmjs.com/package/postcss) plugin which is called [postcss-rtl](https://www.npmjs.com/package/postcss-rtl).
111 |
112 | ## Development
113 |
114 | ```
115 | npm install
116 | ```
117 |
118 | ### Compiles and hot-reloads for development
119 |
120 | ```
121 | npm run serve
122 | ```
123 |
124 | ### Compiles and minifies for production
125 |
126 | ```
127 | npm run build-lib
128 | ```
129 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset',
4 | ],
5 | };
6 |
--------------------------------------------------------------------------------
/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "pluginsFile": "tests/e2e/plugins/index.js"
3 | }
4 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: '@vue/cli-plugin-unit-jest',
3 | };
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-vanilla-datetime-picker",
3 | "version": "1.2.0",
4 | "scripts": {
5 | "serve": "vue-cli-service serve",
6 | "build": "vue-cli-service build",
7 | "build-lib": "vue-cli-service build --target lib --name DateTimePicker ./src/components/DateTimePicker.vue",
8 | "lint": "vue-cli-service lint",
9 | "test:unit": "vue-cli-service test:unit",
10 | "test:e2e": "vue-cli-service test:e2e"
11 | },
12 | "author": "Vladyslav Shchepotin",
13 | "license": "MIT",
14 | "bugs": {
15 | "url": "https://github.com/Shchepotin/vue-vanilla-datetime-picker/issues"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "git+https://github.com/Shchepotin/vue-vanilla-datetime-picker.git"
20 | },
21 | "keywords": [
22 | "vue",
23 | "date",
24 | "datetime",
25 | "time",
26 | "picker"
27 | ],
28 | "main": "./dist/DateTimePicker.common.js",
29 | "dependencies": {
30 | "core-js": "^3.12.1",
31 | "luxon": "^1.26.0",
32 | "vue": "^2.6.12"
33 | },
34 | "devDependencies": {
35 | "@vue/cli-plugin-babel": "^4.5.0",
36 | "@vue/cli-plugin-e2e-cypress": "^4.5.13",
37 | "@vue/cli-plugin-eslint": "^4.5.13",
38 | "@vue/cli-plugin-unit-jest": "^4.5.13",
39 | "@vue/cli-service": "^4.5.13",
40 | "@vue/eslint-config-airbnb": "^5.3.0",
41 | "@vue/test-utils": "1.2.0",
42 | "babel-core": "7.0.0-bridge.0",
43 | "babel-eslint": "^10.1.0",
44 | "babel-jest": "^26.6.3",
45 | "eslint": "^7.26.0",
46 | "eslint-config-airbnb-base": "^14.2.1",
47 | "eslint-plugin-import": "^2.22.1",
48 | "eslint-plugin-vue": "^7.9.0",
49 | "vue-template-compiler": "^2.6.12"
50 | },
51 | "eslintConfig": {
52 | "root": true,
53 | "env": {
54 | "node": true
55 | },
56 | "extends": [
57 | "plugin:vue/essential",
58 | "airbnb-base"
59 | ],
60 | "rules": {
61 | "no-param-reassign": 0,
62 | "no-multi-assign": 0,
63 | "guard-for-in": 0,
64 | "no-underscore-dangle": 0,
65 | "max-len": 0,
66 | "indent": [
67 | "error",
68 | 2
69 | ],
70 | "quotes": [
71 | "error",
72 | "single"
73 | ],
74 | "semi": [
75 | "error",
76 | "always"
77 | ],
78 | "no-console": "off"
79 | },
80 | "parserOptions": {
81 | "parser": "babel-eslint"
82 | },
83 | "overrides": [
84 | {
85 | "files": [
86 | "**/__tests__/*.{j,t}s?(x)",
87 | "**/tests/unit/**/*.spec.{j,t}s?(x)"
88 | ],
89 | "env": {
90 | "jest": true
91 | }
92 | }
93 | ]
94 | },
95 | "postcss": {
96 | "plugins": {
97 | "autoprefixer": {}
98 | }
99 | },
100 | "browserslist": [
101 | "> 1%",
102 | "last 2 versions"
103 | ]
104 | }
105 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shchepotin/vue-vanilla-datetime-picker/73a78554bf6ca57a2f0b59fe0758e82f36229861/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | vue-vanilla-datetime-picker
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
DateTimePicker without props:
4 |
8 |
9 |
10 |
11 |
26 |
--------------------------------------------------------------------------------
/src/components/DatePicker.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
122 |
126 |
129 |
134 | {{ nameWeekday }}
135 |
136 |
137 |
142 |
157 | {{ info.date !== null ? info.date.toFormat('dd') : '' }}
158 |
159 |
160 |
161 |
165 |
170 |
182 |
183 |
184 |
188 |
193 |
205 |
206 |
207 |
208 |
209 |
210 |
435 |
436 |
498 |
--------------------------------------------------------------------------------
/src/components/DateTimePicker.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
30 |
31 |
39 |
47 |
64 |
67 |
71 | <
72 |
73 |
74 |
77 |
81 | >
82 |
83 |
84 |
85 |
88 |
92 | <
93 |
94 |
95 |
98 |
102 | >
103 |
104 |
105 |
106 |
109 |
113 | <
114 |
115 |
116 |
119 |
123 | >
124 |
125 |
126 |
127 |
135 |
143 |
146 |
149 | ↑
150 |
151 |
152 |
155 |
158 | ↓
159 |
160 |
161 |
164 |
167 | ↑
168 |
169 |
170 |
173 |
176 | ↓
177 |
178 |
179 |
182 |
185 | ↑
186 |
187 |
188 |
191 |
194 | ↓
195 |
196 |
197 |
200 |
203 | ↑
204 |
205 |
206 |
209 |
212 | ↓
213 |
214 |
215 |
216 |
224 |
232 |
240 |
241 |
242 |
243 |
244 |
480 |
481 |
533 |
--------------------------------------------------------------------------------
/src/components/TextSlider.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 | {{ selectedValue ? selectedValue.displayValue : value.displayValue }}
12 |
13 |
20 |
21 |
22 |
23 |
71 |
72 |
77 |
--------------------------------------------------------------------------------
/src/components/TimePicker.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
11 |
14 | ↑
15 |
16 |
17 |
20 |
23 | ↓
24 |
25 |
26 |
27 |
32 |
35 |
38 | ↑
39 |
40 |
41 |
44 |
47 | ↓
48 |
49 |
50 |
51 |
57 |
60 |
63 | ↑
64 |
65 |
66 |
69 |
72 | ↓
73 |
74 |
75 |
76 |
82 |
85 |
88 | ↑
89 |
90 |
91 |
94 |
97 | ↓
98 |
99 |
100 |
101 |
102 |
103 |
104 |
192 |
193 |
201 |
--------------------------------------------------------------------------------
/src/directives/click-outside.js:
--------------------------------------------------------------------------------
1 | const isTouch = typeof window !== 'undefined' && ('ontouchstart' in window || navigator.msMaxTouchPoints > 0);
2 | const detectedEvents = isTouch ? ['touchstart', 'click'] : ['click'];
3 |
4 | const instances = [];
5 |
6 | const funcIndexOf = (array, func) => {
7 | for (let index = array.length - 1; index >= 0; index -= 1) {
8 | if (func(array[index])) return index;
9 | }
10 |
11 | return -1;
12 | };
13 |
14 | const processDirectiveArguments = (bindingValue) => {
15 | const isFunction = typeof bindingValue === 'function';
16 |
17 | if (!isFunction && typeof bindingValue !== 'object') {
18 | throw new Error('v-click-outside: Binding value must be a function or an object');
19 | }
20 |
21 | return {
22 | handler: isFunction ? bindingValue : bindingValue.handler,
23 | middleware: bindingValue.middleware || ((isClickOutside) => isClickOutside),
24 | events: bindingValue.events || detectedEvents,
25 | active: isFunction && bindingValue.active === undefined ? true : !!bindingValue.active,
26 | };
27 | };
28 |
29 | const onEvent = ({
30 | el, event, handler, middleware,
31 | }) => {
32 | const isClickOutside = event.target !== el && !el.contains(event.target);
33 |
34 | if (!isClickOutside) {
35 | return;
36 | }
37 |
38 | if (middleware(event, el)) {
39 | handler(event, el);
40 | }
41 | };
42 |
43 | const createInstance = (el, events, handler, middleware) => {
44 | const instance = {
45 | el,
46 | eventHandlers: events.map((eventName) => ({
47 | event: eventName,
48 | handler: (event) => onEvent({
49 | event, el, handler, middleware,
50 | }),
51 | })),
52 | };
53 |
54 | instances.push(instance);
55 |
56 | return instance;
57 | };
58 |
59 | const destroyInstance = (el) => {
60 | const instanceIndex = funcIndexOf(instances, (instance) => instance.el === el);
61 |
62 | if (instanceIndex === -1) {
63 | throw new Error(`unable to find a v-click-outside instance for el: ${el}`);
64 | }
65 |
66 | const instance = instances[instanceIndex];
67 | instance.eventHandlers.forEach(({ event, handler }) => document.removeEventListener(event, handler, true));
68 | instances.splice(instanceIndex, 1);
69 | };
70 |
71 | const bind = (el, { value }) => {
72 | const {
73 | events, handler, middleware, active,
74 | } = processDirectiveArguments(value);
75 |
76 | if (!active) {
77 | return;
78 | }
79 |
80 | const instance = createInstance(el, events, handler, middleware);
81 |
82 | instance.eventHandlers.forEach(({ event, handler: handlerEvent }) => {
83 | document.addEventListener(event, handlerEvent, true);
84 | });
85 | };
86 |
87 | const update = (el, { value }) => {
88 | const {
89 | events, handler, middleware, active,
90 | } = processDirectiveArguments(value);
91 |
92 | const instance = instances
93 | .find((instanceElement) => instanceElement.el === el) || createInstance(el, events, handler, middleware);
94 |
95 | if (!active) {
96 | destroyInstance(el);
97 | return;
98 | }
99 |
100 | instance.eventHandlers
101 | .forEach(({ event, handler: handlerEvent }) => document.removeEventListener(event, handlerEvent, true));
102 |
103 | instance.eventHandlers = events.map((eventName) => ({
104 | event: eventName,
105 | handler(event) {
106 | onEvent({
107 | event, el, handler, middleware,
108 | });
109 | },
110 | }));
111 |
112 | instance.eventHandlers.forEach(({ event, handler: handlerEvent }) => {
113 | document.addEventListener(event, handlerEvent, true);
114 | });
115 | };
116 |
117 | const directive = {
118 | bind,
119 | update,
120 | unbind: destroyInstance,
121 | instances,
122 | };
123 |
124 | export default directive;
125 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import App from './App.vue';
3 |
4 | Vue.config.productionTip = false;
5 |
6 | new Vue({
7 | render: (h) => h(App),
8 | }).$mount('#app');
9 |
--------------------------------------------------------------------------------
/src/mixins/helpers.js:
--------------------------------------------------------------------------------
1 | import { DateTime } from 'luxon';
2 |
3 | export default {
4 | methods: {
5 | generateSecondsMatrix(date) {
6 | return Array.from({ length: 60 }, (elColumn, columnIndex) => DateTime
7 | .local(date.year, date.month, date.day, date.hour, date.minute, columnIndex));
8 | },
9 | generateMinutesMatrix(date, step = 1) {
10 | return Array.from({ length: Math.ceil(60 / step) }, (elColumn, columnIndex) => DateTime
11 | .local(date.year, date.month, date.day, date.hour, columnIndex * step, date.second));
12 | },
13 | generateHoursMatrix(date) {
14 | return Array.from({ length: 24 }, (elColumn, columnIndex) => DateTime
15 | .local(date.year, date.month, date.day, columnIndex, date.minute, date.second));
16 | },
17 | generateDateMatrix(selectedDate, startFromSunday = false) {
18 | const selectedMonth = selectedDate;
19 | let firstMonthDay = DateTime
20 | .local(selectedMonth.year, selectedMonth.month, 1).weekday + (startFromSunday ? 1 : 0);
21 |
22 | if (startFromSunday) {
23 | firstMonthDay = firstMonthDay !== 8 ? firstMonthDay : 1;
24 | }
25 |
26 | let count = 1;
27 | let countNextMonth = 1;
28 |
29 | const previousMonth = this.getPreviousMonth(selectedMonth);
30 | const nextMonth = this.getNextMonth(selectedMonth);
31 |
32 | return Array.from({ length: 6 }, (elColumn, columnIndex) => Array
33 | .from({ length: 7 }, (elRow, rowIndex) => {
34 | const day = {
35 | status: null,
36 | date: null,
37 | };
38 |
39 | const time = [
40 | selectedMonth.hour,
41 | selectedMonth.minute,
42 | selectedMonth.second,
43 | ];
44 |
45 | if ((columnIndex === 0 && (rowIndex >= firstMonthDay - 1))
46 | || (columnIndex > 0 && count <= selectedMonth.daysInMonth)) {
47 | day.status = 'current';
48 | day.date = DateTime
49 | .local(selectedMonth.year, selectedMonth.month, count, ...time);
50 |
51 | count += 1;
52 | } else if (columnIndex === 0 && rowIndex < firstMonthDay - 1) {
53 | day.status = 'previous';
54 | day.date = DateTime
55 | .local(
56 | previousMonth.year, previousMonth.month,
57 | (
58 | (
59 | previousMonth.daysInMonth - firstMonthDay
60 | ) + 2
61 | ) + rowIndex,
62 | ...time,
63 | );
64 | } else {
65 | day.status = 'next';
66 | day.date = DateTime
67 | .local(nextMonth.year, nextMonth.month, countNextMonth, ...time);
68 |
69 | countNextMonth += 1;
70 | }
71 |
72 | return day;
73 | }));
74 | },
75 | generateMonthsMatrix(date, locale = 'en') {
76 | return Array.from({ length: 12 }, (elColumn, columnIndex) => {
77 | const dateTime = DateTime.local(
78 | date.year, columnIndex + 1, 1,
79 | date.hour, date.minute, date.second,
80 | ).setLocale(locale);
81 |
82 | return {
83 | month: dateTime,
84 | monthLong: dateTime.monthLong,
85 | monthShort: dateTime.monthShort,
86 | };
87 | });
88 | },
89 | generateYearsMatrix(date) {
90 | return Array.from({ length: 10 }, (elColumn, columnIndex) => {
91 | const dateTime = DateTime.local(
92 | (date.year - (date.year % 10)) + columnIndex, date.month, 1,
93 | date.hour, date.minute, date.second,
94 | );
95 |
96 | return {
97 | year: dateTime,
98 | yearNumber: dateTime.year,
99 | };
100 | });
101 | },
102 | getPreviousMonth(date) {
103 | return DateTime.local(date.year, date.month, 1, date.hour, date.minute, date.second)
104 | .minus({ month: 1 });
105 | },
106 | getNextMonth(date) {
107 | return DateTime.local(date.year, date.month, 1, date.hour, date.minute, date.second)
108 | .plus({ month: 1 });
109 | },
110 | getPreviousDecade(date) {
111 | return DateTime.local(date.year, date.month, 1, date.hour, date.minute, date.second)
112 | .minus({ year: 10 });
113 | },
114 | getNextDecade(date) {
115 | return DateTime.local(date.year, date.month, 1, date.hour, date.minute, date.second)
116 | .plus({ year: 10 });
117 | },
118 | getPreviousYear(date) {
119 | return DateTime.local(date.year, date.month, 1, date.hour, date.minute, date.second)
120 | .minus({ year: 1 });
121 | },
122 | toDecade(date) {
123 | return DateTime.local(date.year - (date.year % 10), date.month, 1, date.hour, date.minute, date.second);
124 | },
125 | getNextYear(date) {
126 | return DateTime.local(date.year, date.month, 1, date.hour, date.minute, date.second)
127 | .plus({ year: 1 });
128 | },
129 | toDateTime(value, format, defaultDate = null) {
130 | let date = defaultDate;
131 |
132 | if (value !== '' && (typeof value) === 'string') {
133 | date = DateTime.fromFormat(value, format);
134 | } else if (value !== null && value.isValid === true) {
135 | date = value;
136 | } else if (value instanceof Date) {
137 | date = DateTime.fromJSDate(value);
138 | }
139 |
140 | return date;
141 | },
142 | getShortNameWeekdays(startFromSunday = false, locale = 'en') {
143 | return Array.from({ length: 7 }, (elColumn, columnIndex) => {
144 | let weekday = columnIndex + 1;
145 |
146 | if (startFromSunday) {
147 | if (columnIndex === 0) {
148 | weekday = 7;
149 | } else {
150 | weekday = columnIndex;
151 | }
152 | }
153 |
154 | return DateTime
155 | .fromObject({
156 | weekday,
157 | }).setLocale(locale).weekdayShort;
158 | });
159 | },
160 | getDateTimeLocal() {
161 | return DateTime.fromObject({
162 | minutes: 0,
163 | hours: 0,
164 | seconds: 0,
165 | });
166 | },
167 | },
168 | };
169 |
--------------------------------------------------------------------------------
/tests/e2e/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | 'cypress',
4 | ],
5 | env: {
6 | mocha: true,
7 | 'cypress/globals': true,
8 | },
9 | rules: {
10 | strict: 'off',
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/tests/e2e/plugins/index.js:
--------------------------------------------------------------------------------
1 | // https://docs.cypress.io/guides/guides/plugins-guide.html
2 |
3 | // if you need a custom webpack configuration you can uncomment the following import
4 | // and then use the `file:preprocessor` event
5 | // as explained in the cypress docs
6 | // https://docs.cypress.io/api/plugins/preprocessors-api.html#Examples
7 |
8 | /* eslint-disable import/no-extraneous-dependencies, global-require, arrow-body-style */
9 | // const webpack = require('@cypress/webpack-preprocessor')
10 |
11 | module.exports = (on, config) => {
12 | // on('file:preprocessor', webpack({
13 | // webpackOptions: require('@vue/cli-service/webpack.config'),
14 | // watchOptions: {}
15 | // }))
16 |
17 | return {
18 | ...config,
19 | fixturesFolder: 'tests/e2e/fixtures',
20 | integrationFolder: 'tests/e2e/specs',
21 | screenshotsFolder: 'tests/e2e/screenshots',
22 | videosFolder: 'tests/e2e/videos',
23 | supportFile: 'tests/e2e/support/index.js',
24 | };
25 | };
26 |
--------------------------------------------------------------------------------
/tests/e2e/specs/DateTimePickerNoProps.js:
--------------------------------------------------------------------------------
1 | // https://docs.cypress.io/api/introduction/api.html
2 |
3 | describe('DateTimePicker flow', () => {
4 | it('Picker without props', () => {
5 | cy.visit('/');
6 | });
7 |
8 | it('Check empty value', () => {
9 | cy.contains('#picker-no-props', 'Choose date');
10 | });
11 |
12 | it('Check toggle picker', () => {
13 | cy.get('#picker-no-props > .datetime-picker__button').click();
14 | cy.get('#picker-no-props > .datetime-picker-main').should('be.visible');
15 | cy.get('#picker-no-props > .datetime-picker__button').click();
16 |
17 | cy.get('#picker-no-props > .datetime-picker__button').click();
18 | cy.get('#picker-no-props > .datetime-picker-main').should('be.visible');
19 | cy.get('body').click();
20 | });
21 |
22 | it('Check time open picker', () => {
23 | cy.get('#picker-no-props > .datetime-picker__button').click();
24 | cy.get('#picker-no-props .time-picker__button').click();
25 | });
26 |
27 | it('Check time picker hours slider', () => {
28 | cy.get('#picker-no-props .datetime-picker-main .time-picker .text-slider')
29 | .eq(0)
30 | .children('.text-slider__value')
31 | .contains('00');
32 |
33 | cy.get('#picker-no-props .datetime-picker-main .time-picker .text-slider')
34 | .eq(0)
35 | .children('.text-slider__button-previous')
36 | .click()
37 | .click()
38 | .click()
39 | .click()
40 | .click();
41 |
42 | cy.get('#picker-no-props .datetime-picker-main .time-picker .text-slider')
43 | .eq(0)
44 | .children('.text-slider__value')
45 | .contains('05');
46 | });
47 |
48 | it('Check time picker minutes slider', () => {
49 | cy.get('#picker-no-props .datetime-picker-main .time-picker .text-slider')
50 | .eq(1)
51 | .children('.text-slider__value')
52 | .contains('00');
53 |
54 | cy.get('#picker-no-props .datetime-picker-main .time-picker .text-slider')
55 | .eq(1)
56 | .children('.text-slider__button-previous')
57 | .click()
58 | .click()
59 | .click()
60 | .click()
61 | .click();
62 |
63 | cy.get('#picker-no-props .datetime-picker-main .time-picker .text-slider')
64 | .eq(1)
65 | .children('.text-slider__value')
66 | .contains('05');
67 | });
68 |
69 | it('Check time close picker', () => {
70 | cy.get('body').click();
71 | });
72 | });
73 |
--------------------------------------------------------------------------------
/tests/e2e/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This is will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/tests/e2e/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands';
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/tests/unit/DateTimePicker.spec.js:
--------------------------------------------------------------------------------
1 | import { shallowMount } from '@vue/test-utils';
2 | import DateTimePicker from '../../src/components/DateTimePicker.vue';
3 |
4 | describe('Simple DateTimePicker', () => {
5 | it('renders props.value when passed', () => {
6 | const value = '2019-03-06 00:00:00';
7 | const wrapper = shallowMount(DateTimePicker, {
8 | propsData: { value },
9 | });
10 | expect(wrapper.text()).toMatch('2019-03-06 00:00');
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/tests/unit/DateTimePicker.valueFormat.spec.js:
--------------------------------------------------------------------------------
1 | import { DateTime } from 'luxon';
2 | import { shallowMount } from '@vue/test-utils';
3 | import DateTimePicker from '../../src/components/DateTimePicker.vue';
4 |
5 | describe('DateTimePicker. Prop value-format', () => {
6 | it('Props default value-format', () => {
7 | const testDate = DateTime.local();
8 | const value = testDate.toFormat('yyyy-LL-dd HH:mm:ss');
9 | const wrapper = shallowMount(DateTimePicker, {
10 | propsData: {
11 | value,
12 | },
13 | });
14 | expect(wrapper.text()).toMatch(testDate.toFormat('yyyy-LL-dd HH:mm'));
15 | });
16 |
17 | it('Props value-format="yyyy-LL-dd HH:mm"', () => {
18 | const testDate = DateTime.local();
19 | const value = testDate.toFormat('yyyy-LL-dd HH:mm');
20 | const wrapper = shallowMount(DateTimePicker, {
21 | propsData: {
22 | value,
23 | valueFormat: 'yyyy-LL-dd HH:mm',
24 | },
25 | });
26 | expect(wrapper.text()).toMatch(testDate.toFormat('yyyy-LL-dd HH:mm'));
27 | });
28 |
29 | it('Props value-format="yyyy-LL-dd hh:mm a"', () => {
30 | const testDate = DateTime.local();
31 | const value = testDate.toFormat('yyyy-LL-dd hh:mm a');
32 | const wrapper = shallowMount(DateTimePicker, {
33 | propsData: {
34 | value,
35 | valueFormat: 'yyyy-LL-dd hh:mm a',
36 | },
37 | });
38 | expect(wrapper.text()).toMatch(testDate.toFormat('yyyy-LL-dd HH:mm'));
39 | });
40 |
41 | it('Props value-format="HH:mm yyyy-LL-dd"', () => {
42 | const testDate = DateTime.local();
43 | const value = testDate.toFormat('HH:mm yyyy-LL-dd');
44 | const wrapper = shallowMount(DateTimePicker, {
45 | propsData: {
46 | value,
47 | valueFormat: 'HH:mm yyyy-LL-dd',
48 | },
49 | });
50 | expect(wrapper.text()).toMatch(testDate.toFormat('yyyy-LL-dd HH:mm'));
51 | });
52 | });
53 |
--------------------------------------------------------------------------------
/tests/unit/DateTimePicker.valueType.spec.js:
--------------------------------------------------------------------------------
1 | import { DateTime } from 'luxon';
2 | import { shallowMount } from '@vue/test-utils';
3 | import DateTimePicker from '../../src/components/DateTimePicker.vue';
4 |
5 | describe('DateTimePicker. Prop value-type', () => {
6 | it('Props value-type="Auto"', () => {
7 | const value = DateTime.local();
8 | const wrapper = shallowMount(DateTimePicker, {
9 | propsData: {
10 | value,
11 | valueType: 'Auto',
12 | },
13 | });
14 | expect(wrapper.text()).toMatch(value.toFormat('yyyy-LL-dd HH:mm'));
15 | });
16 |
17 | it('Props value-type="String"', () => {
18 | const testDate = DateTime.local();
19 |
20 | const value = testDate.toFormat('yyyy-LL-dd HH:mm:ss');
21 | const wrapper = shallowMount(DateTimePicker, {
22 | propsData: {
23 | value,
24 | valueType: 'String',
25 | },
26 | });
27 | expect(wrapper.text()).toMatch(testDate.toFormat('yyyy-LL-dd HH:mm'));
28 | });
29 |
30 | it('Props value-type="Date"', () => {
31 | const value = new Date();
32 | const wrapper = shallowMount(DateTimePicker, {
33 | propsData: {
34 | value,
35 | valueType: 'String',
36 | },
37 | });
38 | expect(wrapper.text()).toMatch(DateTime.fromJSDate(value).toFormat('yyyy-LL-dd HH:mm'));
39 | });
40 |
41 | it('Props value-type="Luxon"', () => {
42 | const value = DateTime.local();
43 | const wrapper = shallowMount(DateTimePicker, {
44 | propsData: {
45 | value,
46 | valueType: 'Luxon',
47 | },
48 | });
49 | expect(wrapper.text()).toMatch(value.toFormat('yyyy-LL-dd HH:mm'));
50 | });
51 | });
52 |
--------------------------------------------------------------------------------