├── src ├── df.js ├── period.js ├── index.js ├── isEq.js ├── findDataAttr.js ├── calendarHeader.vue ├── calendarRange.vue ├── mixin.js ├── pointerEventDirective.js ├── calendarEvents.vue ├── calendar.vue └── calendarView.vue ├── package.json ├── .gitignore ├── .npmignore ├── LICENSE └── README.md /src/df.js: -------------------------------------------------------------------------------- 1 | import * as df from 'date-fns'; 2 | export default df -------------------------------------------------------------------------------- /src/period.js: -------------------------------------------------------------------------------- 1 | export default { 2 | MINUTE: 2, 3 | HOUR: 3, 4 | DAY: 4, 5 | WEEK: 5, 6 | MONTH: 6, 7 | YEAR: 7, 8 | DECADE: 8, 9 | 10 | 2: 'minute', 11 | 3: 'hour', 12 | 4: 'day', 13 | 5: 'week', 14 | 6: 'month', 15 | 7: 'year', 16 | 8: 'decade', 17 | } 18 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import calendarView from './calendarView.vue'; 2 | import calendar from './calendar.vue'; 3 | import calendarRange from './calendarRange.vue'; 4 | import calendarEvents from './calendarEvents.vue'; 5 | 6 | export { 7 | calendarView, 8 | calendar, 9 | calendarRange, 10 | calendarEvents, 11 | } 12 | -------------------------------------------------------------------------------- /src/isEq.js: -------------------------------------------------------------------------------- 1 | export default function isEq(val1, val2) { 2 | 3 | if ( val1 === val2 ) 4 | return true; 5 | 6 | var type1 = typeof(val1); 7 | if ( type1 !== typeof(val2) ) 8 | return false; 9 | 10 | if ( type1 === 'object' && val1 !== null && val2 !== null ) { 11 | 12 | var k1 = Object.keys(val1); 13 | var k1len = k1.length; 14 | if ( Object.keys(val2).length !== k1len ) 15 | return false; 16 | 17 | for ( var i = 0; i < k1len; ++i ) 18 | if ( !isEq(val1[k1[i]], val2[k1[i]]) ) 19 | return false; 20 | return true; 21 | } 22 | return false; 23 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-calendar-picker", 3 | "version": "2.0.3", 4 | "description": "calendar component", 5 | "main": "src/index.js", 6 | "scripts": {}, 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/FranckFreiburger/vue-calendar-picker.git" 10 | }, 11 | "keywords": [ 12 | "vue2", 13 | "calendar", 14 | "datetime" 15 | ], 16 | "author": "Franck FREIBURGER", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/FranckFreiburger/vue-calendar-picker/issues" 20 | }, 21 | "homepage": "https://github.com/FranckFreiburger/vue-calendar-picker#readme", 22 | "dependencies": { 23 | "date-fns": "^1.29.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /src/findDataAttr.js: -------------------------------------------------------------------------------- 1 | function camelize(str) { 2 | 3 | return str.replace(/-./g, function(str) { return str.charAt(1).toUpperCase() }); 4 | } 5 | 6 | var dataset = function(elt) { 7 | 8 | if ( 'dataset' in HTMLElement.prototype ) { 9 | 10 | dataset = function(elt) { 11 | 12 | return elt.dataset; 13 | } 14 | } else { 15 | 16 | dataset = function(elt) { 17 | 18 | var data = {}; 19 | for ( var i = 0; i < elt.attributes.length; ++i ) { 20 | 21 | var attribute = elt.attributes[i]; 22 | if ( attribute.name.substr(0, 5) === 'data-' ) 23 | data[camelize(attribute.name.substr(5))] = attribute.value; 24 | } 25 | return data; 26 | } 27 | } 28 | return dataset(elt); 29 | } 30 | 31 | export default function(elt, rootElt) { 32 | 33 | var dataAttrMap = {}; 34 | for ( ; elt !== rootElt && elt !== null; elt = elt.parentNode ) 35 | if ( elt.nodeType === 1 ) { 36 | 37 | var data = dataset(elt); 38 | for ( var propName in data ) 39 | dataAttrMap[propName] = data[propName]; 40 | } 41 | return dataAttrMap; 42 | } 43 | -------------------------------------------------------------------------------- /src/calendarHeader.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Franck Freiburger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/calendarRange.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 97 | -------------------------------------------------------------------------------- /src/mixin.js: -------------------------------------------------------------------------------- 1 | import isEq from './isEq.js'; 2 | 3 | import { format as df_format, addMinutes as df_addMinutes, addHours as df_addHours, addDays as df_addDays, addWeeks as df_addWeeks, addMonths as df_addMonths, addYears as df_addYears, startOfMinute as df_startOfMinute, startOfHour as df_startOfHour, startOfDay as df_startOfDay, startOfWeek as df_startOfWeek, startOfMonth as df_startOfMonth, startOfYear as df_startOfYear, getYear as df_getYear } from 'date-fns'; 4 | import PERIOD from './period.js'; 5 | import pointerEventDirective from './pointerEventDirective.js'; 6 | 7 | 8 | function data(el, binding) { 9 | 10 | if ( isEq(binding.value, binding.oldValue) ) 11 | return; 12 | // use setAttribute instead of dataset because IE[9, 10] does not reflect dataset into dom attributes. 13 | el.setAttribute('data-'+binding.arg, JSON.stringify(binding.value)); 14 | } 15 | 16 | function normalizeLocale(locale) { 17 | 18 | if ( locale.length === 2 ) 19 | return locale+'-'+locale; 20 | return locale; 21 | } 22 | 23 | export default { 24 | directives: { 25 | data: data, 26 | onpointer: pointerEventDirective(), 27 | }, 28 | 29 | props: { 30 | locale: { 31 | type: String, 32 | default: process.env.VUE_ENV === 'server' ? '' : normalizeLocale(navigator.language || navigator.userLanguage), 33 | // validator: function(value) { 34 | // 35 | // return value === value.toUpperCase(); 36 | // }, 37 | required: process.env.VUE_ENV === 'server' 38 | }, 39 | compact: { 40 | type: Boolean, 41 | default: false, 42 | }, 43 | 44 | itemClass: { 45 | type: Function, 46 | default: function() {}, 47 | }, 48 | 49 | }, 50 | computed: { 51 | dateFnsLocale: function() { 52 | 53 | const lang = this.locale.substr(0,2).toLowerCase(); 54 | return require('date-fns/locale/'+lang+'/index.js'); 55 | }, 56 | 57 | firstDayOfTheWeek: function() { 58 | 59 | const country = this.locale.substr(3,2).toUpperCase(); 60 | 61 | if (' GB AG AR AS AU BR BS BT BW BZ CA CN CO DM DO ET GT GU HK HN ID IE IL IN JM JP KE KH KR LA MH MM MO MT MX MZ NI NP NZ PA PE PH PK PR PY SA SG SV TH TN TT TW UM US VE VI WS YE ZA ZW '.indexOf(' '+country+' ') !== -1 ) 62 | return 0; // sun 63 | if (' AE AF BH DJ DZ EG IQ IR JO KW LY MA OM QA SD SY '.indexOf(' '+country+' ') !== -1 ) 64 | return 6; // sat 65 | if (' BD MV '.indexOf(' '+country+' ') !== -1 ) 66 | return 5; // fri 67 | return 1; // mon 68 | }, 69 | 70 | dfOptions: function() { 71 | return { weekStartsOn: this.firstDayOfTheWeek }; 72 | }, 73 | 74 | }, 75 | methods: { 76 | format: function(date, format) { 77 | 78 | return df_format(date, format, { locale: this.dateFnsLocale }); 79 | }, 80 | 81 | dateAdd: function(date, type, count) { 82 | 83 | switch ( type ) { 84 | case PERIOD.MINUTE: return df_addMinutes(date, count); 85 | case PERIOD.HOUR: return df_addHours(date, count); 86 | case PERIOD.DAY: return df_addDays(date, count); 87 | case PERIOD.WEEK: return df_addWeeks(date, count); 88 | case PERIOD.MONTH: return df_addMonths(date, count); 89 | case PERIOD.YEAR: return df_addYears(date, count); 90 | case PERIOD.DECADE: return df_addYears(date, 16 * count); 91 | } 92 | }, 93 | 94 | getItemRange: function(date, type) { 95 | 96 | switch ( type ) { 97 | case PERIOD.MINUTE: return { start: df_startOfMinute(date), end: df_startOfMinute(df_addMinutes(date, 1)) }; 98 | case PERIOD.HOUR: return { start: df_startOfHour(date), end: df_startOfHour(df_addHours(date, 1)) }; 99 | case PERIOD.DAY: return { start: df_startOfDay(date), end: df_startOfDay(df_addDays(date, 1)) }; 100 | case PERIOD.WEEK: return { start: df_startOfWeek(date, this.dfOptions), end: df_startOfWeek(df_addWeeks(date, 1), this.dfOptions) }; 101 | case PERIOD.MONTH: return { start: df_startOfMonth(date), end: df_startOfMonth(df_addMonths(date, 1)) }; 102 | case PERIOD.YEAR: return { start: df_startOfYear(date), end: df_startOfYear(df_addYears(date, 1)) }; 103 | case PERIOD.DECADE: return { start: df_getYear(date)-9 , end:df_getYear(date)+6 }; 104 | } 105 | }, 106 | 107 | }, 108 | 109 | created: function() { 110 | 111 | this.PERIOD = PERIOD; 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/pointerEventDirective.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | 3 | if ( process.env.VUE_ENV === 'server' ) { 4 | 5 | return {} 6 | } 7 | 8 | var docComputedStyles = window.getComputedStyle(window.document.documentElement); 9 | var hasUserSelect = 'userSelect' in docComputedStyles || 'MozUserSelect' in docComputedStyles || 'OUserSelect' in docComputedStyles || 'msUserSelect' in docComputedStyles || 'WebkitUserSelect' in docComputedStyles; 10 | var hasTouchScreen = 'ontouchstart' in window || navigator.maxTouchPoints; 11 | var hasMouse = true; 12 | 13 | var uaName = navigator.appVersion; 14 | var pos = uaName.indexOf('MSIE '); 15 | var ieVer = pos !== -1 ? parseFloat(uaName.substr(pos+5, uaName.indexOf(';', pos))) : 0; 16 | 17 | function hasKeyActive(cx, ev) { 18 | 19 | return ev.shiftKey || ev.ctrlKey || ev.altKey || ev.metaKey; 20 | } 21 | 22 | function hasPointerActive(cx, ev) { 23 | 24 | return 'buttonState' in cx ? cx.buttonState : ev.buttons !== 0; 25 | } 26 | 27 | 28 | function touchStartHandler(cx, ev) { 29 | 30 | cx.callback({ eventType:'down', eventTarget:ev.target, pointerActive:true, keyActive:false}); 31 | 32 | cx.pressTimeout = setTimeout(function() { 33 | 34 | cx.callback({ eventType:'press', eventTarget:ev.target, pointerActive:true, keyActive:false}); 35 | }, 1000); 36 | } 37 | 38 | function touchEndHandler(cx, ev) { 39 | 40 | if ( cx.pressTimeout !== undefined ) { 41 | 42 | clearTimeout(cx.pressTimeout); 43 | cx.pressTimeout = undefined; 44 | } 45 | 46 | cx.callback({ eventType:'up', eventTarget:ev.target, pointerActive:false, keyActive:false}); 47 | } 48 | 49 | function touchMoveHandler(cx, ev) { 50 | 51 | if ( cx.pressTimeout !== undefined ) { 52 | 53 | clearTimeout(cx.pressTimeout); 54 | cx.pressTimeout = undefined; 55 | } 56 | 57 | ev.preventDefault(); 58 | 59 | var eventTarget = document.elementFromPoint(ev.changedTouches[0].clientX, ev.changedTouches[0].clientY); 60 | cx.callback({ eventType:'over', eventTarget:eventTarget, pointerActive:true, keyActive:false}); 61 | } 62 | 63 | function clickHandler(cx, ev) { 64 | 65 | cx.callback({ eventType:'tap', eventTarget:ev.target, pointerActive:false, keyActive:hasKeyActive(cx, ev)}); 66 | } 67 | 68 | function dblclickHandler(cx, ev) { 69 | 70 | cx.callback({ eventType:'press', eventTarget:ev.target, pointerActive:false, keyActive:hasKeyActive(cx, ev)}); 71 | } 72 | 73 | function mouseOverHandler(cx, ev) { 74 | 75 | cx.callback({ eventType:'over', eventTarget:ev.target, pointerActive:hasPointerActive(cx, ev), keyActive:hasKeyActive(cx, ev)}); 76 | } 77 | 78 | function mouseDownHandler(cx, ev) { 79 | 80 | cx.callback({ eventType:'down', eventTarget:ev.target, pointerActive:true, keyActive:hasKeyActive(cx, ev)}); 81 | } 82 | 83 | function mouseUpHandler(cx, ev) { 84 | 85 | cx.callback({ eventType:'up', eventTarget:ev.target, pointerActive:false, keyActive:hasKeyActive(cx, ev)}); 86 | } 87 | 88 | 89 | function eventListener(el, eventName, handler) { 90 | 91 | el.addEventListener(eventName, handler); 92 | return el.removeEventListener.bind(el, eventName, handler); 93 | } 94 | 95 | return { 96 | bind: function(el, binding, vnode, oldVnode) { 97 | 98 | var cx = { 99 | el: el, 100 | callback: binding.value, 101 | offEvent: [] 102 | } 103 | 104 | cx.offEvent.push( eventListener(el, 'click', clickHandler.bind(this, cx)) ); 105 | cx.offEvent.push( eventListener(el, 'dblclick', dblclickHandler.bind(this, cx)) ); 106 | 107 | // touch screen 108 | if ( hasTouchScreen ) { 109 | 110 | cx.offEvent.push( eventListener(el, 'touchstart', touchStartHandler.bind(this, cx)) ); 111 | cx.offEvent.push( eventListener(el, 'touchmove', touchMoveHandler.bind(this, cx)) ); 112 | cx.offEvent.push( eventListener(el, 'touchend', touchEndHandler.bind(this, cx)) ); 113 | } 114 | 115 | if ( hasMouse ) { 116 | 117 | cx.offEvent.push( eventListener(el, 'mouseover', mouseOverHandler.bind(this, cx)) ); 118 | cx.offEvent.push( eventListener(el, 'mousedown', mouseDownHandler.bind(this, cx)) ); 119 | cx.offEvent.push( eventListener(el, 'mouseup', mouseUpHandler.bind(this, cx)) ); 120 | 121 | if ( ieVer && ieVer === 9 ) { 122 | 123 | // IE9 does not update ev.buttons on mouseover event 124 | cx.buttonState = undefined; 125 | cx.offEvent.push( eventListener(document, 'mousedown', function(ev) { cx.buttonState = true } ) ); 126 | cx.offEvent.push( eventListener(document, 'mouseup', function(ev) { cx.buttonState = false } ) ); 127 | } 128 | } 129 | 130 | if ( !hasUserSelect ) 131 | cx.offEvent.push( eventListener(el, 'selectstart', function(ev) { ev.preventDefault() } ) ); 132 | 133 | el._onpointerCx = cx; 134 | }, 135 | unbind: function(el, binding, vnode, oldVnode) { 136 | 137 | var cx = el._onpointerCx; 138 | while ( cx.offEvent.length !== 0 ) 139 | cx.offEvent.pop()(); 140 | el._onpointerCx = null; 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/calendarEvents.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 130 | 131 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-calendar-picker 2 | Calendar component 3 | 4 | ![vue-calendar-picker screenshot](https://cloud.githubusercontent.com/assets/25509586/26058700/40d539be-397f-11e7-86ad-d0af0f21e64d.png) 5 | 6 | [vue-calendar-picker demo on jsfiddle](https://jsfiddle.net/b8z8wh1j/101/) 7 | 8 | 9 | ## Example - basic 10 | ```vue 11 | 14 | 15 | 26 | ``` 27 | 28 | 29 | ## Install 30 | ``` 31 | npm install --save vue-calendar-picker 32 | ``` 33 | 34 | 35 | ## Features 36 | * zoom from decade view to hour view (decade, year, month, week, day, hour) 37 | * localized (see [supported locale list](https://github.com/date-fns/date-fns/tree/master/src/locale)) 38 | * autodetect the first day of the week 39 | * animated (zoom & slide) 40 | * multiple calendar views (side-by-side) 41 | * display one-time events and date/time period 42 | * date/time period selection 43 | * touch screen support 44 | * only one dependancy: [date-fns](https://date-fns.org/), a Modern JavaScript date utility library 45 | 46 | `vue-calendar-picker` has 3 available components: 47 | * `calendar.vue`: simple calendar. 48 | * `calendarEvents.vue`: `calendar.vue` + one-time events and date/time periods display. 49 | * `calendarRange.vue`: `calendarEvents.vue` + range selection. 50 | 51 | 52 | ## Conventions 53 | 54 | * date ranges are inclusive, exclusive ([start, end[) 55 | 56 | 57 | ## API - `calendar.vue` 58 | 59 | 60 | ### Properties 61 | 62 | #### :locale string, default: navigator.language 63 | The locale of the calendar. Impacts the days names, month names and first day ofthe week. [supported locale list](https://github.com/date-fns/date-fns/tree/master/src/locale). 64 | Locale name must be uppercase. 65 | 66 | #### :compact boolean, default: false 67 | Enable compact mode. Use less UI space. 68 | 69 | #### :initialView number, default: 6 (month view) 70 | Initial view zoom. 71 | 72 | #### :initialCurrent Date, default: new Date 73 | Initial view date. 74 | 75 | #### :itemClass function(range), default: *empty function* 76 | Called for each calendar cell. The retun valus is used as className of the cell. 77 | 78 | #### :viewCount number, default: 1 79 | Allow to display multiple calendar views side-by-side. 80 | 81 | #### :showOverlappingDays boolean, default: viewCount === 1 82 | In the month view, show days belonging to the preceding and following month. 83 | 84 | 85 | ### Events 86 | 87 | #### @action (eventObject) 88 | 89 | `eventObject` has the following properties: 90 | 91 | ##### eventType string 92 | * `'down'`: mousedown or touchstart 93 | * `'up'`: mouseup or touchend 94 | * `'tap'`: click or tap 95 | * `'press'`: dblclick or longtap 96 | * `'over'`: mouseover or touchmove 97 | 98 | ##### eventActive boolean 99 | Indicates that the pointer is active: a mouse button is down or the finger touches the screen. 100 | 101 | ##### keyActive boolean 102 | Indicates that the shift or ctrl or alt or meta key is down. Always `false` on touch-screen devices. 103 | 104 | ##### range `{ start: Date, end: Date }` 105 | The date range of the item 106 | 107 | ##### rangeType string 108 | The range name: `'minute'`, `'hour'`, `'day'`, `'week'`, `'month'`, `'year'`. 109 | 110 | 111 | ### Slots 112 | 113 | #### *default slot* scope: itemRange, layout / default: *empty* 114 | The content of calendar cells. 115 | 116 | ##### `itemRange` `{ start: Date, end: Date }` 117 | The time range of the the cell. 118 | 119 | ##### `layout` string 120 | The layout of the content, either `'horizontal'` or `'vertical'`. 121 | 122 | 123 | ### Styling 124 | vue-calendar-picker can by styled easily, all css selectors are prefixed with `.calendar`. 125 | 126 | ###### example 127 | ```css 128 | .calendar { 129 | border: 2px solid #000; 130 | border-radius: 0.5em; 131 | padding: 0.5em; 132 | } 133 | ``` 134 | 135 | ### UI details 136 | * click on date part in the calendar header area to modify it (zoom out) 137 | * click or double-click on the cell to zoom in. (from month view, use double-click to zoom in) 138 | 139 | 140 | 141 | ## API - `calendarEvents.vue` 142 | 143 | ### Properties 144 | 145 | #### :locale - see [calendar.vue](#api---calendarvue) API. 146 | #### :compact - see [calendar.vue](#api---calendarvue) API. 147 | #### :initialView - see [calendar.vue](#api---calendarvue) API. 148 | #### :initialCurrent - see [calendar.vue](#api---calendarvue) API. 149 | #### :itemClass - see [calendar.vue](#api---calendarvue) API. 150 | 151 | #### :events Array for `{ color: CSS color, start: Date, end: Date }` 152 | A list of one-time events and date/time periods to display in the calendar. 153 | One-time events has the same `start` and `end` date. 154 | 155 | #### :selection `{ start: Date, end: Date }` 156 | The current calendar selection. For display only. 157 | 158 | ### Events 159 | 160 | #### @action (eventObject) 161 | 162 | `eventObject` has the same properties that [calendar.vue](#api---calendarvue) added: 163 | 164 | ##### calendarEvent object 165 | A reference to a calendar event (see `:events` property) related to the mouse/touch event, otherwise `undefined`. 166 | 167 | 168 | ### UI details 169 | * event range are colored lines 170 | * event point are big dots 171 | 172 | 173 | 174 | ## API - `calendarRange.vue` 175 | 176 | Allow user selection. The `selection` property object is modified according to the user's selection. 177 | 178 | ### Properties 179 | 180 | #### :locale - see [calendar.vue](#api---calendarvue) API. 181 | #### :compact - see [calendar.vue](#api---calendarvue) API. 182 | #### :initialView - see [calendar.vue](#api---calendarvue) API. 183 | #### :initialCurrent - see [calendar.vue](#api---calendarvue) API. 184 | #### :itemClass - see [calendar.vue](#api---calendarvue) API. 185 | 186 | #### :events - see [calendarEvents.vue](#api---calendareventsvue) API. 187 | #### :selection - see [calendarEvents.vue](#api---calendareventsvue) API. 188 | 189 | #### :useTwoCalendars boolean, default: `false` 190 | Display two calendars side-by-side to make selection easier. 191 | 192 | 193 | ### Events 194 | 195 | #### @action (eventObject) 196 | 197 | `eventObject` has the same properties that [calendar.vue](#api---calendareventsvue). 198 | 199 | 200 | ### UI details 201 | * use mousedown + move or tap + move to select a range (also across calendars) 202 | * use ctrl + click to update the selection from the nearest end point (disbled on touch screens) 203 | 204 | 205 | ## Browser support 206 | Same browser support as [Vue.js 2](https://github.com/vuejs/vue/blob/dev/README.md) 207 | 208 | 209 | ## Example - advanced 210 | ```vue 211 | 214 | 215 | 241 | ``` 242 | 243 | 244 | ## Demo 245 | ![vue-calendar-picker demo](https://cloud.githubusercontent.com/assets/25509586/26059550/7863fa02-3982-11e7-8a20-83f77dbfe4de.gif) 246 | 247 | 248 | ## Credits 249 | [ Franck Freiburger](https://www.franck-freiburger.com) 250 | -------------------------------------------------------------------------------- /src/calendar.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 244 | 245 | -------------------------------------------------------------------------------- /src/calendarView.vue: -------------------------------------------------------------------------------- 1 | 120 | 121 | 258 | 259 | 324 | --------------------------------------------------------------------------------