├── .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 | [![Build Status](https://travis-ci.org/Shchepotin/vue-vanilla-datetime-picker.svg?branch=master)](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 | ![vue-datetime-picker](https://user-images.githubusercontent.com/6001723/56603715-ec324600-6608-11e9-9c54-0862878a7168.gif) 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 | 10 | 11 | 26 | -------------------------------------------------------------------------------- /src/components/DatePicker.vue: -------------------------------------------------------------------------------- 1 | 209 | 210 | 435 | 436 | 498 | -------------------------------------------------------------------------------- /src/components/DateTimePicker.vue: -------------------------------------------------------------------------------- 1 | 243 | 244 | 480 | 481 | 533 | -------------------------------------------------------------------------------- /src/components/TextSlider.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 71 | 72 | 77 | -------------------------------------------------------------------------------- /src/components/TimePicker.vue: -------------------------------------------------------------------------------- 1 | 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 | --------------------------------------------------------------------------------