├── .travis.yml ├── demos ├── script.js ├── index.html ├── demos.config.js └── Demo.vue ├── src ├── plugin.js └── VueTimeSelector.vue ├── CHANGELOG.md ├── docs └── index.html ├── .babelrc ├── styleguide.config.js ├── LICENSE ├── .gitignore ├── webpack.config.js ├── package.json ├── tests └── VueTimeSelector.test.js ├── dist ├── VueTimeSelector.js └── vue-timeselector.min.js └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8.10.0" 4 | -------------------------------------------------------------------------------- /demos/script.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Demo from './Demo.vue'; 3 | 4 | new Vue({ 5 | el: '#app', 6 | render: h => h(Demo) 7 | }) 8 | -------------------------------------------------------------------------------- /src/plugin.js: -------------------------------------------------------------------------------- 1 | import VueTimeSelector from './VueTimeSelector.vue'; 2 | 3 | module.exports = { 4 | install: function (Vue, options) { 5 | Vue.component('timeselector', VueTimeSelector); 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /demos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue-Selector 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.1.4 - 2018-01-22 4 | 5 | - Add full two-way-binding that allow to preselect a date and change it on the fly 6 | - Change `returnFormat` to act as an emit formater 7 | - Add a `formatedTime` event in order to listen for the formated return (`returnFormat`) date 8 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | Vue Timeselector Style Guide
-------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "debug": false, 7 | "targets": { 8 | "browsers": [ 9 | "last 3 versions" 10 | ] 11 | } 12 | } 13 | ] 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /demos/demos.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const merge = require('webpack-merge'); 3 | const baseConfig = require('../webpack.config.js'); 4 | const HtmlWebPackPlugin = require("html-webpack-plugin"); 5 | 6 | module.exports = merge(baseConfig[1], { 7 | devtool: 'eval-source-map', 8 | 9 | entry: { 10 | demos: './demos/script.js' 11 | }, 12 | 13 | devServer: { 14 | compress: true, 15 | open: true, 16 | stats: "errors-only", 17 | port: 9900 18 | }, 19 | 20 | plugins: [ 21 | new HtmlWebPackPlugin({ 22 | template: "./demos/index.html", 23 | filename: "index.html" 24 | }) 25 | ] 26 | }); 27 | -------------------------------------------------------------------------------- /styleguide.config.js: -------------------------------------------------------------------------------- 1 | const VueLoaderPlugin = require('vue-loader/lib/plugin') 2 | module.exports = { 3 | components: 'src/**/[A-Z]*.vue', 4 | styleguideDir: 'docs', 5 | webpackConfig: { 6 | module: { 7 | rules: [ 8 | // Vue loader 9 | { 10 | test: /\.vue$/, 11 | exclude: /node_modules/, 12 | loader: 'vue-loader' 13 | }, 14 | // Babel loader, will use your project’s .babelrc 15 | { 16 | test: /\.js?$/, 17 | exclude: /node_modules/, 18 | loader: 'babel-loader' 19 | }, 20 | // Other loaders that are needed for your components 21 | { 22 | test: /\.css$/, 23 | loader: 'style-loader!css-loader' 24 | } 25 | ] 26 | }, 27 | plugins: [ 28 | // add vue-loader plugin 29 | new VueLoaderPlugin() 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /demos/Demo.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 42 | 43 | 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Alexis Colin 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | BUGS.md 64 | package-lock.json 65 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const merge = require('webpack-merge'); 3 | const VueLoaderPlugin = require('vue-loader/lib/plugin'); 4 | 5 | let commonConfig = { 6 | module: { 7 | rules: [ 8 | { 9 | test: /\.vue$/, 10 | loader: 'vue-loader' 11 | }, { 12 | test: /\.js$/, 13 | loader: 'babel-loader' 14 | }, { 15 | test: /\.css$/, 16 | use: [ 17 | 'vue-style-loader', 18 | 'css-loader' 19 | ] 20 | } 21 | ] 22 | }, 23 | plugins: [ 24 | new VueLoaderPlugin() 25 | ] 26 | }; 27 | 28 | module.exports = [ 29 | // Config 1: For browser environment 30 | merge(commonConfig, { 31 | entry: path.resolve(__dirname + '/src/plugin.js'), 32 | output: { 33 | filename: 'vue-timeselector.min.js', 34 | libraryTarget: 'window', 35 | library: 'VueTimeSelector' 36 | } 37 | }), 38 | 39 | // Config 2: For Node-based development environments 40 | merge(commonConfig, { 41 | entry: { 42 | VueTimeSelector: path.resolve(__dirname + '/src/VueTimeSelector.vue') 43 | }, 44 | output: { 45 | path: path.resolve(__dirname, 'dist'), 46 | filename: '[name].js', 47 | libraryTarget: 'umd', 48 | library: 'vue-timeselector', 49 | umdNamedDefine: true 50 | } 51 | }) 52 | ]; 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-timeselector", 3 | "version": "1.0.0", 4 | "description": "Simple customizable Vue.js timepicker component", 5 | "author": "Alexis Colin ", 6 | "repository": "git+https://github.com/alexiscolin/vue-timeselector.git", 7 | "license": "MIT", 8 | "main": "dist/VueTimeSelector.js", 9 | "private": false, 10 | "keywords": [ 11 | "vue", 12 | "timepicker", 13 | "timeselector", 14 | "date", 15 | "time", 16 | "picker", 17 | "custom", 18 | "vuejs2" 19 | ], 20 | "engines": { 21 | "node": ">= 8.10.0" 22 | }, 23 | "bugs": { 24 | "url": "https://github.com/alexiscolin/vue-timeselector/issues" 25 | }, 26 | "scripts": { 27 | "start": "webpack-dev-server --hot --mode development --config demos/demos.config.js", 28 | "build": "webpack --mode production --config webpack.config.js", 29 | "test": "jest", 30 | "styleguide": "vue-styleguidist server", 31 | "styleguide:build": "vue-styleguidist build" 32 | }, 33 | "dependencies": { 34 | "acorn": "^6.3.0", 35 | "vue": "^2.6.10" 36 | }, 37 | "devDependencies": { 38 | "@babel/cli": "^7.0.0", 39 | "@babel/core": "^7.6.2", 40 | "@babel/polyfill": "^7.6.0", 41 | "@babel/preset-env": "^7.6.2", 42 | "@vue/test-utils": "^1.0.0-beta.27", 43 | "babel-core": "^7.0.0-bridge.0", 44 | "babel-jest": "^25.0.0", 45 | "babel-loader": "^8.0.6", 46 | "babel-preset-env": "^1.7.0", 47 | "css-loader": "^2.0.1", 48 | "html-loader": "^0.5.5", 49 | "html-webpack-plugin": "^3.2.0", 50 | "jest": "^25.0.0", 51 | "regenerator-runtime": "^0.13.3", 52 | "vue-jest": "^3.0.5", 53 | "vue-loader": "^15.7.1", 54 | "vue-style-loader": "^4.1.2", 55 | "vue-styleguidist": "^3.24.2", 56 | "vue-template-compiler": "^2.6.10", 57 | "webpack": "^4.41.0", 58 | "webpack-cli": "^3.3.9", 59 | "webpack-dev-server": "^3.8.1", 60 | "webpack-merge": "^4.2.2" 61 | }, 62 | "jest": { 63 | "moduleNameMapper": { 64 | "^vue$": "vue/dist/vue.common.js" 65 | }, 66 | "moduleFileExtensions": [ 67 | "js", 68 | "json", 69 | "vue" 70 | ], 71 | "transform": { 72 | "^.+\\.js$": "/node_modules/babel-jest", 73 | ".*\\.(vue)$": "/node_modules/vue-jest" 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/VueTimeSelector.test.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils'; 2 | import Vue from 'vue'; 3 | import VueTimeSelector from '../src/VueTimeSelector.vue'; 4 | 5 | const factory = (values = {}) => { 6 | return mount(VueTimeSelector, {...values}) 7 | } 8 | 9 | describe('VueTimeSelector', () => { 10 | /** 11 | * First load instance test 12 | */ 13 | it('is a Vue instance', () => { 14 | const wrapper = factory(); 15 | expect(wrapper.isVueInstance()).toBeTruthy() 16 | }); 17 | 18 | /** 19 | * Test placeholder 20 | */ 21 | it('Can display a placeholder', () => { 22 | [new Date(), null].forEach(type => { 23 | const wrapper = factory({ 24 | propsData: { 25 | value: type, 26 | placeholder: 'This is a placeholder' 27 | } 28 | }); 29 | 30 | expect(wrapper.find('.vtimeselector__input').element.value).toBe('This is a placeholder'); 31 | }) 32 | }); 33 | 34 | /** 35 | * Test name attribute 36 | */ 37 | it('May have a name attribute', () => { 38 | [new Date(), null].forEach(type => { 39 | const wrapper = factory({ 40 | propsData: { 41 | value: type, 42 | name: 'This is a name' 43 | } 44 | }); 45 | 46 | expect(wrapper.find('.vtimeselector__input').attributes().name).toBe('This is a name'); 47 | }); 48 | }); 49 | 50 | /** 51 | * Test id attribute 52 | */ 53 | it('May have an id attribute', () => { 54 | [new Date(), null].forEach(type => { 55 | const wrapper = factory({ 56 | propsData: { 57 | value: type, 58 | id: 'myId' 59 | } 60 | }); 61 | 62 | expect(wrapper.find('.vtimeselector__input').attributes().id).toBe('myId'); 63 | }); 64 | }); 65 | 66 | /** 67 | * Test required attribute 68 | */ 69 | it('May have a required attribute', () => { 70 | [new Date(), null].forEach(type => { 71 | const wrapper = factory({ 72 | propsData: { 73 | value: type, 74 | required: true 75 | } 76 | }); 77 | 78 | expect(wrapper.find('.vtimeselector__input').attributes().required).toBeTruthy(); 79 | }); 80 | }); 81 | 82 | /** 83 | * Test disabled attribute 84 | */ 85 | it('May have a disabled attribute', () => { 86 | [new Date(), null].forEach(type => { 87 | const wrapper = factory({ 88 | propsData: { 89 | value: type, 90 | disabled: true 91 | } 92 | }); 93 | 94 | expect(wrapper.find('.vtimeselector__input').attributes().disabled).toBeTruthy(); 95 | }); 96 | }); 97 | 98 | /** 99 | * Test initial view picker open 100 | */ 101 | it('Can be opened when created', () => { 102 | [new Date(), null].forEach(type => { 103 | const wrapper = factory({ 104 | propsData: { 105 | value: type, 106 | initialView: true 107 | } 108 | }); 109 | 110 | expect(wrapper.find('.vtimeselector__box').classes('vtimeselector__box--is-closed')).toBeFalsy(); 111 | }); 112 | }); 113 | 114 | /** 115 | * Test falsy h24 set hours 12 times (and no more) and AMPM picker 116 | */ 117 | it('Display 12h in picker when h24 is disabled', () => { 118 | [new Date(), null].forEach(type => { 119 | const wrapper = factory({ 120 | propsData: { 121 | value: type, 122 | h24: false 123 | } 124 | }); 125 | 126 | expect(wrapper.findAll('.vtimeselector__box__item--hours').length).toEqual(12); 127 | expect(wrapper.findAll('.vtimeselector__box__list--ampm').exists()).toBe(true); 128 | }); 129 | }); 130 | 131 | /** 132 | * Test if display hour prop can display or not the hour picker 133 | */ 134 | it('Achieve to display or not the hour picker', () => { 135 | [new Date(), null].forEach(type => { 136 | [false,true].forEach(state => { 137 | const wrapper = factory({ 138 | propsData: { 139 | value: type, 140 | displayHours: state 141 | } 142 | }); 143 | 144 | expect(wrapper.findAll('.vtimeselector__box__list--hours').exists()).toBe(state); 145 | }) 146 | }); 147 | }); 148 | 149 | /** 150 | * Test if display minute prop can display or not the minute picker 151 | */ 152 | it('Achieve to display or not the minute picker', () => { 153 | [new Date(), null].forEach(type => { 154 | [false,true].forEach(state => { 155 | const wrapper = factory({ 156 | propsData: { 157 | value: type, 158 | displayMinutes: state 159 | } 160 | }); 161 | 162 | expect(wrapper.findAll('.vtimeselector__box__list--minutes').exists()).toBe(state); 163 | }) 164 | }); 165 | }); 166 | 167 | /** 168 | * Test if display second prop can display or not the second picker 169 | */ 170 | it('Achieve to display or not the second picker', () => { 171 | [new Date(), null].forEach(type => { 172 | [false,true].forEach(state => { 173 | const wrapper = factory({ 174 | propsData: { 175 | value: type, 176 | displaySeconds: state 177 | } 178 | }); 179 | 180 | expect(wrapper.findAll('.vtimeselector__box__list--seconds').exists()).toBe(state); 181 | }) 182 | }); 183 | }); 184 | 185 | /** 186 | * Test if returnedFormat return a String 187 | */ 188 | it('Return a string when returnFormat is set', () => { 189 | const wrapper = factory({ 190 | propsData: { 191 | value: null, 192 | initialView: true, 193 | interval: {h:1, m:1, s:1}, 194 | returnFormat: 'H[h]m' 195 | } 196 | }); 197 | 198 | const hours = Math.floor(Math.random() * 23); 199 | const minutes = Math.floor(Math.random() * 59); 200 | 201 | wrapper.findAll('.vtimeselector__box__item--hours').at(hours).trigger('click'); 202 | expect(wrapper.emitted().formatedTime[0][0]).toBe(`${hours}h0`); 203 | 204 | wrapper.findAll('.vtimeselector__box__item--minutes').at(minutes).trigger('click'); 205 | expect(wrapper.emitted().formatedTime[1][0]).toBe(`${hours}h${minutes}`); 206 | 207 | console.log(wrapper.emitted().formatedTime) 208 | }); 209 | 210 | /** 211 | * Test if click on input open and close the picker 212 | */ 213 | it('Can open and close the picker with emitted event', () => { 214 | const wrapper = factory({ 215 | propsData: { 216 | value: null, 217 | } 218 | }); 219 | 220 | const input = wrapper.find('.vtimeselector__input'); 221 | input.trigger('click'); 222 | expect(wrapper.find('.vtimeselector__box').classes('vtimeselector__box--is-closed')).toBeFalsy(); 223 | expect(wrapper.emitted().opened[0][0]).toBeTruthy(); 224 | 225 | input.trigger('click'); 226 | expect(wrapper.find('.vtimeselector__box').classes('vtimeselector__box--is-closed')).toBeTruthy(); 227 | expect(wrapper.emitted().closed[0][0]).toBeTruthy(); 228 | }); 229 | }) 230 | -------------------------------------------------------------------------------- /dist/VueTimeSelector.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("vue-timeselector",[],t):"object"==typeof exports?exports["vue-timeselector"]=t():e["vue-timeselector"]=t()}(window,(function(){return function(e){var t={};function i(r){if(t[r])return t[r].exports;var s=t[r]={i:r,l:!1,exports:{}};return e[r].call(s.exports,s,s.exports,i),s.l=!0,s.exports}return i.m=e,i.c=t,i.d=function(e,t,r){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(i.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var s in e)i.d(r,s,function(t){return e[t]}.bind(null,s));return r},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i(i.s=4)}([function(e,t,i){var r=i(2);"string"==typeof r&&(r=[[e.i,r,""]]),r.locals&&(e.exports=r.locals);(0,i(5).default)("72252727",r,!1,{})},function(e,t,i){"use strict";var r=i(0);i.n(r).a},function(e,t,i){(e.exports=i(3)(!1)).push([e.i,"\n.vtimeselector {\n position: relative;\n font-family: sans-serif;\n}\n.vtimeselector__input {\n width: 100%;\n box-sizing: border-box;\n}\n.vtimeselector__clear {\n position: absolute;\n display: flex;\n justify-content: center;\n align-items: center;\n top: 0;\n right: 0;\n width: 20px;\n height: 100%;\n cursor: pointer;\n}\n.vtimeselector__clear__ico {\n display: inline-block;\n width: 100%;\n text-align: center;\n vertical-align: middle;\n color: #a5a5a5;\n}\n.vtimeselector__clear:hover .vtimeselector__clear__ico{\n color: black;\n}\n.vtimeselector__box {\n position: absolute;\n display: flex;\n left: 0;\n top: 100%;\n width: 100%;\n height: 10em;\n background: white;\n z-index: 999;\n}\n.vtimeselector__box--is-closed { display: none;\n}\n.vtimeselector__box__item { cursor: pointer;\n}\n.vtimeselector__box__list {\n list-style: none;\n padding: 0;\n margin: 0;\n flex: 1;\n text-align: center;\n overflow-x: hidden;\n overflow-y: auto;\n}\n.vtimeselector__box__list + .vtimeselector__box__list {\n border-left: 1px solid #ffffff;\n}\n.vtimeselector__box__head {\n color: #a5a5a5;\n font-size: .8em;\n padding: .8em 0 .4em;\n}\n.vtimeselector__box__item {\n padding: .4em 0;\n font-size: 1em;\n}\n.vtimeselector__box__item:not(.timeselector__box__item--is-disabled):not(.timeselector__box__item--is-selected):hover {\n background: #d3d3d3;\n}\n.timeselector__box__item--is-highlighted { background: #5b64f7;\n}\n.timeselector__box__item--is-selected {\n background: #05cfb5;\n color: #ffffff;\n}\n.timeselector__box__item--is-disabled {\n cursor: auto;\n background: #f5f5f5;\n color: #a5a5a5;\n}\n",""])},function(e,t,i){"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var i=function(e,t){var i=e[1]||"",r=e[3];if(!r)return i;if(t&&"function"==typeof btoa){var s=(o=r,"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(o))))+" */"),n=r.sources.map((function(e){return"/*# sourceURL="+r.sourceRoot+e+" */"}));return[i].concat(n).concat([s]).join("\n")}var o;return[i].join("\n")}(t,e);return t[2]?"@media "+t[2]+"{"+i+"}":i})).join("")},t.i=function(e,i){"string"==typeof e&&(e=[[null,e,""]]);for(var r={},s=0;s12?"PM":"AM":null}},hoursLength:function(){return this.h24?this.longHourCount:this.shortHourCount},displayRules:function(){var e=this;return{HH:function(){return e.pad(e.picker.hour,!0)},H:function(){return e.picker.hour},kk:function(){return e.pad(0===e.picker.hour?e.picker.hour+1:e.picker.hour,!0)},k:function(){return 0===e.picker.hour?e.picker.hour+1:e.picker.hour},hh:function(){return e.h24?e.pad(e.picker.hour>12?12-(0===e.picker.hour?e.picker.hour+1:e.picker.hour):0===e.picker.hour?e.picker.hour+1:e.picker.hour,!0):e.pad(0===e.picker.hour?e.picker.hour+1:e.picker.hour,!0)},h:function(){return e.h24&&e.picker.hour>12?12-(0===e.picker.hour?e.picker.hour+1:e.picker.hour):0===e.picker.hour?e.picker.hour+1:e.picker.hour},mm:function(){return e.pad(e.picker.minute,!0)},m:function(){return e.picker.minute.toString()},ss:function(){return e.pad(e.picker.second,!0)},s:function(){return e.picker.second.toString()},a:function(){return e.picker.hour>=12?"PM":"AM"}}},time:function(){if(!this.h24&&null!==this.picker.ampm){var e=parseInt(this.picker.hour,10);switch(this.picker.ampm){case"AM":this.picker.hour=e>=12?e-12:e;break;case"PM":this.picker.hour=e<=12?e+12:e}}if(-1===this.picker.hour&&-1===this.picker.minute)return"";this.pickerState.time.setHours(this.picker.hour,this.picker.minute,this.picker.second);var t=this.value?this.value:new Date;return this.utc?new Date(Date.UTC(t.getFullYear(),t.getMonth(),t.getDate(),this.picker.hour,this.picker.minute,this.picker.second)):new Date(this.pickerState.time)}},methods:{pad:function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];return this.padTime||t?e.toString().padStart(2,"0"):e},timeFormated:function(e){var t=this;if(this.placeholder&&this.pickerState.isPristine)return this.placeholder;if("displayFormat"===e&&!this.displayFormat)return(this.displayHours?this.pad(this.picker.hour):"")+(this.displayMinutes&&this.displayHours?this.separator+this.pad(this.picker.minute):this.displayMinutes?this.pad(this.picker.minute):"")+(this.displaySeconds?this.separator+this.pad(this.picker.second):"")+(this.h24?"":this.picker.hour>=12?" AM":" PM");var i=this[e];return this.formater.forEach((function(e){void 0!==t.displayRules[e]&&(i=(i=i.replace(new RegExp("".concat(e,"(?!])"),"g"),t.displayRules[e]())).replace(new RegExp("\\[".concat(e,"\\]"),"g"),e))})),i},timeCount:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1,i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:60;return Array.apply(null,{length:i}).map(Number.call,(function(t){return i<60?e.h24?t:t+1:t})).filter((function(e){return e%t==0})).map((function(t){return e.pad(t)}))},selectTime:function(e,t,i){this.disabled||this.getState(e,"disable",t)?this.$emit("selectedDisabled"):(this.pickerState.isPristine=!1,this.picker[e]=i.target.textContent,this.pickerState.selected[e]=t,this.$emit("selected".concat(e.charAt(0).toUpperCase()+e.slice(1)),this.picker[e]),this.returnFormat&&this.$emit("formatedTime",this.timeFormated("returnFormat")),this.$emit("input",this.time))},clearTime:function(){this.pickerState.isPristine=!0,this.pickerState.selected.hour=-1,this.pickerState.selected.minute=-1,this.pickerState.selected.second=-1,this.pickerState.selected.ampm=null,this.picker.hour=0,this.picker.minute=0,this.picker.second=0,this.picker.ampm="AM",this.close(),this.$emit("input",""),this.$emit("cleared")},togglePicker:function(){this.disabled||(this.pickerState.isClosed=!this.pickerState.isClosed,this.pickerState.isClosed?this.$emit("closed",this.$el):this.$emit("opened",this.$el))},close:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;null===e?!this.pickerState.isClosed&&this.togglePicker():this.$el.contains(e.target)||this.$el===e.target||this.pickerState.isClosed||this.togglePicker()},getState:function(e,t,i){var r=this;if(this[t][e.charAt(0)]){var s=parseInt(i,10);return this[t][e.charAt(0)].map((function(t){t instanceof Date&&"[object Date]"===Object.prototype.toString.call(t)&&(t="hour"===e?t.getHours():"minute"===e?t.getMinutes():t.getSeconds());var i=isNaN(s)?t:parseInt(t,10);return!1===r.h24&&i>12?i-12:i})).indexOf(s)>=0}}},watch:{value:function(){this.picker.hour=this.value?this.utc?this.value.getUTCHours():this.value.getHours():0,this.picker.minute=this.value?this.utc?this.value.getUTCMinutes():this.value.getMinutes():0,this.picker.second=this.value?this.utc?this.value.getUTCSeconds():this.value.getSeconds():0,this.pickerState.isPristine||(this.pickerState.selected.hour=this.h24?this.picker.hour:this.picker.hour>12?this.picker.hour-12:this.picker.hour,this.pickerState.selected.minute=this.picker.minute,this.pickerState.selected.second=this.picker.second,this.pickerState.selected.ampm=this.h24?null:this.picker.hour>12?"PM":"AM",this.pickerState.time=this.value)}},created:function(){var e,t=this;if(!this.h24&&this.value){var i=e=this.utc?this.value.getUTCHours():this.value.getHours();this.pickerState.selected.ampm=i<=12?"AM":"PM",this.picker.ampm=i<=12?"AM":"PM",this.picker.hour=i>=12?i-12:i}null!==this.value&&["hour","minute","second"].forEach((function(e){return t.pickerState.selected[e]=t.picker[e]&&t.pad(t.picker[e])})),!this.h24&&this.value&&(this.picker.hour=e),(!this.h24&&this.highlight.h&&this.highlight.h.length>0||this.disable.h&&this.disable.h.length>0)&&console.warn('You shouldn\'t use h24="false" with highlight or disable hour props. It may cause AM-PM confusion or limitation due to multiple hours selected.'),this.initialView&&this.togglePicker(),window.addEventListener("click",this.close)},beforeDestroy:function(){window.removeEventListener("click",this.close)}};i(1);var n=function(e,t,i,r,s,n,o,c){var l,a="function"==typeof e?e.options:e;if(t&&(a.render=t,a.staticRenderFns=i,a._compiled=!0),r&&(a.functional=!0),n&&(a._scopeId="data-v-"+n),o?(l=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),s&&s.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(o)},a._ssrRegister=l):s&&(l=c?function(){s.call(this,this.$root.$options.shadowRoot)}:s),l)if(a.functional){a._injectStyles=l;var u=a.render;a.render=function(e,t){return l.call(t),u(e,t)}}else{var h=a.beforeCreate;a.beforeCreate=h?[].concat(h,l):[l]}return{exports:e,options:a}}(s,r,[],!1,null,null,null);n.options.__file="src/VueTimeSelector.vue";t.default=n.exports},function(e,t,i){"use strict";function r(e,t){for(var i=[],r={},s=0;si.parts.length&&(r.parts.length=i.parts.length)}else{var o=[];for(s=0;s12?"PM":"AM":null}},hoursLength:function(){return this.h24?this.longHourCount:this.shortHourCount},displayRules:function(){var e=this;return{HH:function(){return e.pad(e.picker.hour,!0)},H:function(){return e.picker.hour},kk:function(){return e.pad(0===e.picker.hour?e.picker.hour+1:e.picker.hour,!0)},k:function(){return 0===e.picker.hour?e.picker.hour+1:e.picker.hour},hh:function(){return e.h24?e.pad(e.picker.hour>12?12-(0===e.picker.hour?e.picker.hour+1:e.picker.hour):0===e.picker.hour?e.picker.hour+1:e.picker.hour,!0):e.pad(0===e.picker.hour?e.picker.hour+1:e.picker.hour,!0)},h:function(){return e.h24&&e.picker.hour>12?12-(0===e.picker.hour?e.picker.hour+1:e.picker.hour):0===e.picker.hour?e.picker.hour+1:e.picker.hour},mm:function(){return e.pad(e.picker.minute,!0)},m:function(){return e.picker.minute.toString()},ss:function(){return e.pad(e.picker.second,!0)},s:function(){return e.picker.second.toString()},a:function(){return e.picker.hour>=12?"PM":"AM"}}},time:function(){if(!this.h24&&null!==this.picker.ampm){var e=parseInt(this.picker.hour,10);switch(this.picker.ampm){case"AM":this.picker.hour=e>=12?e-12:e;break;case"PM":this.picker.hour=e<=12?e+12:e}}if(-1===this.picker.hour&&-1===this.picker.minute)return"";this.pickerState.time.setHours(this.picker.hour,this.picker.minute,this.picker.second);var t=this.value?this.value:new Date;return this.utc?new Date(Date.UTC(t.getFullYear(),t.getMonth(),t.getDate(),this.picker.hour,this.picker.minute,this.picker.second)):new Date(this.pickerState.time)}},methods:{pad:function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];return this.padTime||t?e.toString().padStart(2,"0"):e},timeFormated:function(e){var t=this;if(this.placeholder&&this.pickerState.isPristine)return this.placeholder;if("displayFormat"===e&&!this.displayFormat)return(this.displayHours?this.pad(this.picker.hour):"")+(this.displayMinutes&&this.displayHours?this.separator+this.pad(this.picker.minute):this.displayMinutes?this.pad(this.picker.minute):"")+(this.displaySeconds?this.separator+this.pad(this.picker.second):"")+(this.h24?"":this.picker.hour>=12?" AM":" PM");var i=this[e];return this.formater.forEach((function(e){void 0!==t.displayRules[e]&&(i=(i=i.replace(new RegExp("".concat(e,"(?!])"),"g"),t.displayRules[e]())).replace(new RegExp("\\[".concat(e,"\\]"),"g"),e))})),i},timeCount:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1,i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:60;return Array.apply(null,{length:i}).map(Number.call,(function(t){return i<60?e.h24?t:t+1:t})).filter((function(e){return e%t==0})).map((function(t){return e.pad(t)}))},selectTime:function(e,t,i){this.disabled||this.getState(e,"disable",t)?this.$emit("selectedDisabled"):(this.pickerState.isPristine=!1,this.picker[e]=i.target.textContent,this.pickerState.selected[e]=t,this.$emit("selected".concat(e.charAt(0).toUpperCase()+e.slice(1)),this.picker[e]),this.returnFormat&&this.$emit("formatedTime",this.timeFormated("returnFormat")),this.$emit("input",this.time))},clearTime:function(){this.pickerState.isPristine=!0,this.pickerState.selected.hour=-1,this.pickerState.selected.minute=-1,this.pickerState.selected.second=-1,this.pickerState.selected.ampm=null,this.picker.hour=0,this.picker.minute=0,this.picker.second=0,this.picker.ampm="AM",this.close(),this.$emit("input",""),this.$emit("cleared")},togglePicker:function(){this.disabled||(this.pickerState.isClosed=!this.pickerState.isClosed,this.pickerState.isClosed?this.$emit("closed",this.$el):this.$emit("opened",this.$el))},close:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;null===e?!this.pickerState.isClosed&&this.togglePicker():this.$el.contains(e.target)||this.$el===e.target||this.pickerState.isClosed||this.togglePicker()},getState:function(e,t,i){var r=this;if(this[t][e.charAt(0)]){var n=parseInt(i,10);return this[t][e.charAt(0)].map((function(t){t instanceof Date&&"[object Date]"===Object.prototype.toString.call(t)&&(t="hour"===e?t.getHours():"minute"===e?t.getMinutes():t.getSeconds());var i=isNaN(n)?t:parseInt(t,10);return!1===r.h24&&i>12?i-12:i})).indexOf(n)>=0}}},watch:{value:function(){this.picker.hour=this.value?this.utc?this.value.getUTCHours():this.value.getHours():0,this.picker.minute=this.value?this.utc?this.value.getUTCMinutes():this.value.getMinutes():0,this.picker.second=this.value?this.utc?this.value.getUTCSeconds():this.value.getSeconds():0,this.pickerState.isPristine||(this.pickerState.selected.hour=this.h24?this.picker.hour:this.picker.hour>12?this.picker.hour-12:this.picker.hour,this.pickerState.selected.minute=this.picker.minute,this.pickerState.selected.second=this.picker.second,this.pickerState.selected.ampm=this.h24?null:this.picker.hour>12?"PM":"AM",this.pickerState.time=this.value)}},created:function(){var e,t=this;if(!this.h24&&this.value){var i=e=this.utc?this.value.getUTCHours():this.value.getHours();this.pickerState.selected.ampm=i<=12?"AM":"PM",this.picker.ampm=i<=12?"AM":"PM",this.picker.hour=i>=12?i-12:i}null!==this.value&&["hour","minute","second"].forEach((function(e){return t.pickerState.selected[e]=t.picker[e]&&t.pad(t.picker[e])})),!this.h24&&this.value&&(this.picker.hour=e),(!this.h24&&this.highlight.h&&this.highlight.h.length>0||this.disable.h&&this.disable.h.length>0)&&console.warn('You shouldn\'t use h24="false" with highlight or disable hour props. It may cause AM-PM confusion or limitation due to multiple hours selected.'),this.initialView&&this.togglePicker(),window.addEventListener("click",this.close)},beforeDestroy:function(){window.removeEventListener("click",this.close)}};i(4);var s=function(e,t,i,r,n,s,o,c){var l,a="function"==typeof e?e.options:e;if(t&&(a.render=t,a.staticRenderFns=i,a._compiled=!0),r&&(a.functional=!0),s&&(a._scopeId="data-v-"+s),o?(l=function(e){(e=e||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(e=__VUE_SSR_CONTEXT__),n&&n.call(this,e),e&&e._registeredComponents&&e._registeredComponents.add(o)},a._ssrRegister=l):n&&(l=c?function(){n.call(this,this.$root.$options.shadowRoot)}:n),l)if(a.functional){a._injectStyles=l;var u=a.render;a.render=function(e,t){return l.call(t),u(e,t)}}else{var h=a.beforeCreate;a.beforeCreate=h?[].concat(h,l):[l]}return{exports:e,options:a}}(n,r,[],!1,null,null,null);s.options.__file="src/VueTimeSelector.vue";t.a=s.exports},function(e,t,i){"use strict";i.r(t),function(e){var t=i(1);e.exports={install:function(e,i){e.component("timeselector",t.a)}}}.call(this,i(3)(e))},function(e,t){e.exports=function(e){if(!e.webpackPolyfill){var t=Object.create(e);t.children||(t.children=[]),Object.defineProperty(t,"loaded",{enumerable:!0,get:function(){return t.l}}),Object.defineProperty(t,"id",{enumerable:!0,get:function(){return t.i}}),Object.defineProperty(t,"exports",{enumerable:!0}),t.webpackPolyfill=1}return t}},function(e,t,i){"use strict";var r=i(0);i.n(r).a},function(e,t,i){(e.exports=i(6)(!1)).push([e.i,"\n.vtimeselector {\n position: relative;\n font-family: sans-serif;\n}\n.vtimeselector__input {\n width: 100%;\n box-sizing: border-box;\n}\n.vtimeselector__clear {\n position: absolute;\n display: flex;\n justify-content: center;\n align-items: center;\n top: 0;\n right: 0;\n width: 20px;\n height: 100%;\n cursor: pointer;\n}\n.vtimeselector__clear__ico {\n display: inline-block;\n width: 100%;\n text-align: center;\n vertical-align: middle;\n color: #a5a5a5;\n}\n.vtimeselector__clear:hover .vtimeselector__clear__ico{\n color: black;\n}\n.vtimeselector__box {\n position: absolute;\n display: flex;\n left: 0;\n top: 100%;\n width: 100%;\n height: 10em;\n background: white;\n z-index: 999;\n}\n.vtimeselector__box--is-closed { display: none;\n}\n.vtimeselector__box__item { cursor: pointer;\n}\n.vtimeselector__box__list {\n list-style: none;\n padding: 0;\n margin: 0;\n flex: 1;\n text-align: center;\n overflow-x: hidden;\n overflow-y: auto;\n}\n.vtimeselector__box__list + .vtimeselector__box__list {\n border-left: 1px solid #ffffff;\n}\n.vtimeselector__box__head {\n color: #a5a5a5;\n font-size: .8em;\n padding: .8em 0 .4em;\n}\n.vtimeselector__box__item {\n padding: .4em 0;\n font-size: 1em;\n}\n.vtimeselector__box__item:not(.timeselector__box__item--is-disabled):not(.timeselector__box__item--is-selected):hover {\n background: #d3d3d3;\n}\n.timeselector__box__item--is-highlighted { background: #5b64f7;\n}\n.timeselector__box__item--is-selected {\n background: #05cfb5;\n color: #ffffff;\n}\n.timeselector__box__item--is-disabled {\n cursor: auto;\n background: #f5f5f5;\n color: #a5a5a5;\n}\n",""])},function(e,t,i){"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var i=function(e,t){var i=e[1]||"",r=e[3];if(!r)return i;if(t&&"function"==typeof btoa){var n=(o=r,"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(o))))+" */"),s=r.sources.map((function(e){return"/*# sourceURL="+r.sourceRoot+e+" */"}));return[i].concat(s).concat([n]).join("\n")}var o;return[i].join("\n")}(t,e);return t[2]?"@media "+t[2]+"{"+i+"}":i})).join("")},t.i=function(e,i){"string"==typeof e&&(e=[[null,e,""]]);for(var r={},n=0;ni.parts.length&&(r.parts.length=i.parts.length)}else{var o=[];for(n=0;n
9 | 10 | vue-timeselector is a **Vue (2.x)** component that gives you ability to select a time depending on multiple options. This component has been created in order to be as fully and simply customizable as powerful thanks to props (format, UTC, 12-24h, optional pickers, highlight, interval, native HTML attributes and many more...), events (opened picker, closed picker, cleared input...) and slots (icon, headers...). 11 |

12 |

13 |
14 | 15 | ## Install 16 | 17 | ``` bash 18 | npm install vue-timeselector --save 19 | ``` 20 | or 21 | ``` bash 22 | yarn add vue-timeselector 23 | ``` 24 | 25 | Node bundle installation: 26 | ``` javascript 27 | import Timeselector from 'vue-timeselector'; 28 | 29 | export default { 30 | // ... 31 | components: { 32 | Timeselector 33 | } 34 | // ... 35 | } 36 | ``` 37 | 38 | **OR** 39 | 40 | Browser bundle installation: 41 | ``` html 42 | 43 |
44 | 45 |
46 | 47 | 50 | 51 | ``` 52 | 53 | 54 | ## Usage 55 | ### Basic Usage 56 | 57 | ``` html 58 | 59 | 60 | ``` 61 | 62 | Value prop if passed should be a `Date` object in order to inject a preconfigured time or `null` if you want to set the picker default time as `0:0`. 63 | 64 | ``` html 65 | 66 | 69 | 70 | 83 | 84 | ``` 85 | 86 | Using `v-modal` lets you benefit of the "two-way-binding" thanks to the `input` emitted event included in the prop. But you can also use `:value` prop in order to inject data in vue-timeselector component and listen the `input` event manualy: 87 | ``` html 88 | 89 | ``` 90 | 91 | Supports name attribute for normal HTML form submission 92 | ``` html 93 | 94 | ``` 95 | 96 | Supports id attribute as well 97 | ``` html 98 | 99 | ``` 100 | 101 | Make a use of state attributes like disabled or required 102 | ``` html 103 | 104 | ``` 105 | 106 | Choose a placeholder as default views 107 | ``` html 108 | 109 | ``` 110 | 111 | Emits events 112 | ``` html 113 | 114 | ``` 115 | 116 | **All [props](#available-props) are listed in the props array below** 117 | 118 | **All [events](#events) are listed in the event array below** 119 | 120 | ### Custom modal box 121 | 122 | Vue-timeselector component lets you choose what kind of information you want to display in the modal box (aka the picker). You can choose to give your users access to **hours**, **minutes**, **seconds**. Furthermore, you can disable any of them by using the following props: 123 | 124 | * `:displayHours="false"` - {Boolean} *optionnal* - default: `true` 125 | * `:displayMinutes="false"` - {Boolean} *optionnal* - default: `true` 126 | * `:displaySeconds="false"` - {Boolean} *optionnal* - default: `false` 127 | 128 | Displays options doesn't act on the time format you see in the input field. You need to use custom time formatting props to change it. 129 | 130 | Also, keep in mind that *AM-PM options* appears automatically in the modal box by passing the prop `h24` to `false` (`:h24="false"`) - see [here](#12-hours-in-modal) to learn more about it. 131 | 132 | ### Customized Time Format 133 | 134 | Timeselector give the opportunity to customize time displayed and returned format. 135 | 136 | By default, timeselector displays time as `H:m` (eg, *16:5*) following UTC datetime and 24h format. Time type displayed depends on modals you have chosen in the modalbox props (`:displayHours`, `:displayMinutes`...). 137 | 138 | You can change the separator by setting it in the *separator* props : `:separator="':'"`. Default separator is `:` symbol. 139 | 140 | The best option to fully custom time displayed in the input is to use the *displayFormat* props : `:displayFormat="'HH[h]mm : ss'"`. 141 | 142 | It's possible to escape a letter used for formatting ("h", "H", "m" ...) by surrounding it with brackets, eg. `HH[h]mm` could render *01h35*. 143 | Time may be set to UTC or not in order to display and return UTC time. 144 | 145 | Finally, the component returns a `Date` object and is complient with other format thanks to `returnFormat` props. In combination with `formatedTime` event, this props let you listen for a returned date format that should be configured in the same way as the `displayFormat` props. 146 | 147 | Please, keep in mind that prop makes the component return a String (and not a date anymore). So `UTC` formatting doesn't affect the returned string that is now the absolute number on which user has clicked. 148 | Also, note that the `:value` returned by the component is still a `Date` object. You need `formatedTime` event to listen the formated date. 149 | 150 | ⚠️ (Since 0.1.4, `returnDate` is accessible from `formatedTime` event and not directly from the `:value` anymore) 151 | 152 | ``` html 153 | 154 | 155 | ``` 156 | 157 | #### String formatter 158 | 159 | | Token | Desc | Example | 160 | |-------|-----------------------------------------|-----------------| 161 | | H | hour from 0 to 23 (non-zero padded) | 0 1 ... 22 23 | 162 | | HH | hour from 0 to 23 (zero padded) | 00 01 ... 22 23 | 163 | | h | hour from 1 to 12 (non-zero padded) | 1 2 ... 11 12 | 164 | | hh | hour from 1 to 12 (zero padded) | 01 02 ... 11 12 | 165 | | k | hour from 1 to 24 (non-zero padded) | 1 2 ... 23 24 | 166 | | kk | hour from 1 to 24 (zero padded) | 01 02 ... 23 24 | 167 | | m | one digit minutes | 0 1 ... 58 59 | 168 | | mm | two digits minutes | 00 01 ... 58 59 | 169 | | s | one digit seconds | 0 1 ... 58 59 | 170 | | ss | two digits seconds | 00 01 ... 58 59 | 171 | | a | AM-PM Period code | AM/PM | 172 | 173 | 174 | ### 12 hours in modal 175 | 176 | It's easy to set 12h - 24h time mode on vue-timeselector. Just feed the `:h24` prop with a Boolean. If true, the modalbox will display time until 23h, if false, the modalbox will display time until 12h and a AM-PM option as well. 177 | 178 | Don't forget that h24 only affect the modalbox, so you may wish to set `:format` props in a special way in order to display input time in a 12h format (see above). 179 | 180 | ### Interval in modal 181 | 182 | Vue-timeselector allows you to choose the time interval you want to set for each unit of time in the modalbox. You may want to display only hours that are multiples of two, every minute, and the seconds of the group by ten. To achieve this goal, you only have to fill an object with hours `h`, minutes `m`and seconds `s` keys, that you will set in the `interval` prop. 183 | 184 | ``` html 185 | 186 | ``` 187 | 188 | Interval prop default value is `{h:1, m:10, s:10}`: 189 | 190 | * **hours: 1**: each hours - eache one unit (0, 1, 2, ...) 191 | * **minutes: 10**: each 10 minutes - eache 10 unit (0, 10, 20, ...) 192 | * **seconds: 10**: each 10 seconds - eache 10 unit (0, 10, 20, ...) 193 | 194 | 195 | ### Highligth time 196 | 197 | Just like interval prop, vue-timeselector allows you to choose an highlight list of times you may want to set for each unit of time in the modalbox. You may want to highlight a special hour, minute or second setting in the modalbox. The `highlight` prop give you the opportunity to do that. And because you may also want to highlight multiple times in the same kind of unit (multiple hours and minutes for exemple), vue-timeselector let you emphasis many of them. To achieve this goal, you only have to fill an object with hours `h`, minutes `m`and seconds `s` keys, and feed them with arrays which contain a list of times you wish your users focus on. 198 | 199 | ``` html 200 | 201 | 202 | ``` 203 | 204 | You may fill arrays with specific time number or even with DateTime expression eg `:highlight="{h:[new Date], m: null, s: null}">`. Also you should avoid use disable hour with `h24` prop set to `false` in order to avoid AM-PM time confusion. 205 | 206 | Note that list of numbers are not interval but lists of specific times. 207 | 208 | ### Disable time 209 | 210 | Just like highlight prop, vue-timeselector allows you to choose a disabled list of times you may want to set for each unit of time in the modalbox. You may want to disable a special hour, minute or second setting in the modalbox. The `disable` prop give you the opportunity to do that. And because you may also want to also disable multiple times in the same kind of unit (multiple hours and minutes for exemple), vue-timeselector let you disable many of them. To achieve this goal, you only have to fill an object with hours `h`, minutes `m`and seconds `s` keys, and feed them with arrays which contain a list of times you wish your users focus on. 211 | 212 | ``` html 213 | 214 | 215 | ``` 216 | 217 | You may fill arrays with specific time number or even with DateTime expression eg `:highlight="{h:[new Date], m: null, s: null}">`. Also you should avoid use disable hour with `h24` prop set to `false` in order to avoid AM-PM time confusion. 218 | 219 | Note that list of numbers are not interval but lists of specific times. 220 | 221 | ### Slots 222 | 223 | Slots will help you to introduce some code or text inside the picker. 224 | 225 | Slots list: 226 | 227 | - **`hours`**: in order to insert code/text above hours selectbox (default: `HH`) 228 | - **`minutes`**: in order to insert code/text above minutes selectbox (default: `mm`) 229 | - **`seconds`**: in order to insert code/text above seconds selectbox (default: `ss`) 230 | - **`ampm`**: in order to insert code/text above ampm selectbox (default: `AM / PM`) 231 | - **`clear-ico`**: in order to insert another icon into the clear button ad symbol (default: `x`) 232 | 233 | ``` html 234 | 235 | 238 | 239 | ``` 240 | 241 | ### Style selector (TODO) 242 | 243 | ... 244 | 245 | ### Use classes to curstomize elements 246 | #### Classes structure 247 | 248 | vue-timeselector is built following [BEM](http://getbem.com/) guidelines so it's easy for everyone to overrides the component's styles for each elements and their modifiers. As exemple, you may want to hide the clear button by setting a `display: none` on the `.vtimeselector__clear` element. 249 | 250 | Here is the classes structure: 251 | 252 | ##### Block - Elements 253 | 254 | ``` 255 | | .vtimeselector 256 | | 257 | |----- .vtimeselector__input 258 | |----- .vtimeselector__clear 259 | |----- .vtimeselector__box 260 | | | 261 | | | ----- .vtimeselector__box__list .vtimeselector__box__list--hours 262 | | | | 263 | | | | ----- vtimeselector__box__item .vtimeselector__box__item--hours 264 | | | | ----- vtimeselector__box__item .vtimeselector__box__item--hours 265 | | | | ----- ... 266 | | | 267 | | | ----- .vtimeselector__box__list .vtimeselector__box__list--minutes 268 | | | | 269 | | | | ----- vtimeselector__box__item .vtimeselector__box__item--minutes 270 | | | | ----- vtimeselector__box__item .vtimeselector__box__item--minutes 271 | | | | ----- ... 272 | | | 273 | | | ----- .vtimeselector__box__list .vtimeselector__box__list--seconds 274 | | | | 275 | | | | ----- vtimeselector__box__item .vtimeselector__box__item--seconds 276 | | | | ----- vtimeselector__box__item .vtimeselector__box__item--seconds 277 | | | | ----- .. 278 | | | 279 | | | ----- .vtimeselector__box__list .vtimeselector__box__list--ampm 280 | | | | 281 | | | | ----- vtimeselector__box__item .vtimeselector__box__item--ampm 282 | | | | ----- vtimeselector__box__item .vtimeselector__box__item--ampm 283 | | | | ----- ... 284 | ``` 285 | 286 | ##### Modifiers 287 | 288 | - **`.vtimeselector__input--is-open`**: Modifier displayed on `.vtimeselector__input` element when the modal is opened 289 | 290 | - **`.vtimeselector__box--is-closed`**: Modifier displayed on `.vtimeselector__box` element when the modal is closed 291 | 292 | - **`.timeselector__box__item--is-highlighted`**: Modifier displayed on `.timeselector__box__item` element when the item is highlighted 293 | 294 | - **`.timeselector__box__item--is-selected`**: Modifier displayed on `.timeselector__box__item` element when the item is selected 295 | 296 | - **`.timeselector__box__item--is-disabled`**: Modifier displayed on `.timeselector__box__item` element when the item is disabled 297 | 298 | 299 | ## Available props 300 | 301 | | Prop | Type | Default | Description | 302 | |-------------------------------|------------------|---------------------|----------------------------------------------------------| 303 | | value | Date / Null | | Date value of the timepicker | 304 | | name | String | | Input name property | 305 | | id | String | | Input id | 306 | | placeholder | String | | Input placeholder text | 307 | | required | Boolean | false | Sets html required attribute on input | 308 | | disabled | Boolean | false | If true, disable timepicker on screen | 309 | | displayHours | Boolean | true | Display hours to the input | 310 | | displayMinutes | Boolean | true | Display minutes to the input | 311 | | displaySeconds | Boolean | false | Display seconds to the input | 312 | | separator | String | ":" | Separator symbol used if no displayFormat | 313 | | padTime | Boolean | true | Pads number with a zero (both input and modal) | 314 | | displayFormat | String | | Time formatting string displayed | 315 | | returnFormat | String | | Time formatting string returned | 316 | | h24 | Boolean | false | Display 24 hours format | 317 | | utc | Boolean | true | Return UTC date format | 318 | | initialView | Boolean | false | Open on the first | 319 | | interval | Object | {h:1, m:10, s:10} | Define hours, minutes and seconds interval to the picker | 320 | | highlight | Object | | Hightligth defined time on hours, minutes and seconds | 321 | | disable | Object | | Disable specific time on hours, minutes and seconds | 322 | | pickerStyle | String | `TODO` | Set the timepicker style | 323 | 324 | 325 | ## Events 326 | 327 | These events are emitted on actions in the timepicker 328 | 329 | | Event | Output | Description | 330 | |-------------------|------------|--------------------------------------| 331 | | opened | Node | The picker is opened | 332 | | closed | Node | The picker is closed | 333 | | selectedHour | Date | An hour has been selected | 334 | | selectedMinute | Date | A minute has been selected | 335 | | selectedSecond | Date | A second has been selected | 336 | | selectedAmpm | String | A ampm field has been selected | 337 | | selectedDisabled | | A disabled time has been selected | 338 | | formatedTime | String | Time formatting string emited | 339 | | input | Date | Input value has been modified | 340 | | cleared | | Selected time has been cleared | 341 | 342 | ## Contributing 343 | ### Tests 344 | 345 | Component tests are made using [Jest](https://jestjs.io/) and are written inside the `tests` folder. You can start a test session by running the following commands: 346 | 347 | ``` bash 348 | 349 | npm test 350 | yarn test 351 | 352 | ``` 353 | 354 | ### Demos server 355 | 356 | Also you can start a webpack webdev server **on the demo file** by running the belowing command. It will open a new window at the `9900` port of your local host. 357 | 358 | ``` bash 359 | 360 | npm start 361 | yarn start 362 | 363 | ``` 364 | 365 | ### Documentation 366 | 367 | vue-timeselector make a use of [vue-styleguidist](https://vue-styleguidist.github.io/) to generate auto documentation. In order to regenerate it, run the following commands: 368 | 369 | ``` bash 370 | # to start style a guide dev server 371 | npm run styleguide 372 | 373 | # to build a static version 374 | npm run styleguide:build 375 | ``` 376 | 377 | **Component's documentation is available [here](https://alexiscolin.github.io/vue-timeselector/)** 378 | 379 | ### Changelog 380 | [See the changelog](https://github.com/alexiscolin/vue-timeselector/blob/master/CHANGELOG.md) 381 | 382 | ### TODO 383 | 384 | * Picker defined style 385 | * Merge returnFormat and displayFormat props 386 | * More tests 387 | 388 | ## License 389 | 390 | [MIT](http://opensource.org/licenses/MIT) 391 | -------------------------------------------------------------------------------- /src/VueTimeSelector.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 535 | 536 | 626 | --------------------------------------------------------------------------------