vue 2.x calendar component

Overview

vue2-calendar

vue 2 calendar, datepicker component which supported lunar or date event

Live Demo >>






  • This project is not only a vue component, but also a webpack multi-page project in action.

  • Boostrap style like

  • I18n support

  • Community feedback

Install

$ npm install vue2-slot-calendar

Import using module

// js file
import 'vue2-slot-calendar/lib/calendar.min.css';
import calendar from 'vue2-slot-calendar/lib/calendar';

// vue file
// in ES6 modules
import Calendar from 'vue2-slot-calendar';

// in CommonJS
const Calendar = require('vue2-slot-calendar');

// in Global variable
const VueCalendar = Calendar;

Import using script tag

<link rel="stylesheet" href="../node_modules/vue2-slot-calendar/lib/calendar.min.css" >
<script src="../node_modules/vue2-slot-calendar/lib/calendar.min.js"></script>

Also see the demo file, example/demo.html

I18n support

currently, provide window.VueCalendarLang function hook to change your lang

  translations(lang) {
    lang = lang || "en";
    let text = {
      daysOfWeek: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"],
      limit: "Limit reached ({{limit}} items max).",
      loading: "Loading...",
      minLength: "Min. Length",
      months: [
        "January",
        "February",
        "March",
        "April",
        "May",
        "June",
        "July",
        "August",
        "September",
        "October",
        "November",
        "December"
      ],
      notSelected: "Nothing Selected",
      required: "Required",
      search: "Search"
    };
    return window.VueCalendarLang ? window.VueCalendarLang(lang) : text;
  },

Build Setup

# install dependencies
npm install

# serve with hot reload at localhost:4000
npm run dev

# build for production with minification
npm run build

# run unit tests
npm run unit

# run all tests
npm test

Screenshot

Usage

<calendar
  :value="value"
  :disabled-days-of-week="disabled"
  :format="format"
  :clear-button="clear"
  :placeholder="placeholder"
  :pane="2"
  :has-input="false"
  :on-day-click="onDayClick2"
  :special-days="_dateMap"
></calendar>

Use slot to render async data

<calendar
  class="event-calendar"
  :value="value"
  :disabled-days-of-week="disabled"
  :format="format"
  :clear-button="clear"
  :placeholder="placeholder"
  :pane="2"
  :has-input="false"
  :on-day-click="onDayClick3"
  :change-pane="changePane"
>
  <div v-for="evt in events" :slot="evt.date">
    ${{evt.content}} <i :class="{low : evt.low}" v-if="evt.low"></i>
  </div>
</calendar>

Range Hover Status

<calendar
  :value="value"
  :disabled-days-of-week="disabled"
  :format="format"
  :clear-button="clear"
  :placeholder="placeholder"
  :pane="2"
  :range-bus="getBus"
  :range-status="1"
></calendar>

<calendar
  :value="value"
  :disabled-days-of-week="disabled"
  :format="format"
  :clear-button="clear"
  :placeholder="placeholder"
  :pane="2"
  :range-bus="getBus"
  :range-status="2"
></calendar>

Props

Options/Props

Name Type Default Description
value String '' Value of the input DOM
width String '200px' Width of the input DOM
format String MMMM/dd/yyyy The date format, combination of d, dd, M, MM, MMM, MMMM, yyyy.
disabled-days-of-week Array Days of the week that should be disabled. Values are 0 (Sunday) to 6 (Saturday). Multiple values should be comma-separated.
clear-button Bollean false If true shows an × shaped button to clear the selected date. Usefull in forms where date entry is optional.
placeholder String Placeholder to put on the input field when no date (null or empty) is set
hasInput Boolean true Default is has-input style, if don't have input will show pane directly
pane Number 1 pane count
borderWidth Number 2 This value is for calculating the pane width
onDayClick Function Only for hasInput set false
specialDays Object To repalce the day text
changePane Function For pane change parameter (year, month, pane) month[0,11], demo /src/modules/Docs.vue
rangeBus Function should return new Vue() as sibling component communication events bus
rangeStatus Number 0 Default is disabled range hover effect, currently only support [0,1,2] 1 will communicate with 2
onDrawDate Function DrawDate Function allowSelect to update date cell style
showDateOnly Boolean false show date pane only
transfer Boolean false transfer popup to document.body
elementId String elementId for label tag for attribute
firstDayOfWeek Number 0 first day of the week, default sunday, [0,6]

Events

Name Description
drawdate drawdate Event like onDrawDate
  props: {
    value: {
      type: [String, Date]
    },
    format: {
      default: 'MM/dd/yyyy'
    },
    firstDayOfWeek: {
      // sunday
      default: 0
    },
    disabledDaysOfWeek: {
      type: Array,
      default () {
        return []
      }
    },
    width: {
      type: String,
      default: '200px'
    },
    clearButton: {
      type: Boolean,
      default: false
    },
    inputClasses: {
      type: String,
      default: ''
    },
    lang: {
      type: String,
      default: navigator.language
    },
    placeholder: {
      type: String
    },
    hasInput: {
      type: Boolean,
      default: true
    },
    pane: {
      type: Number,
      default: 1
    },
    borderWidth: {
      type: Number,
      default: 2
    },
    onDayClick: {
      type: Function,
      default () {}
    },
    changePane: {
      type: Function,
      default () {}
    },
    specialDays: {
      type: Object,
      default () {
        return {}
      }
    },
    rangeBus: {
      type: Function,
      default () {
        // return new Vue()
      }
    },
    rangeStatus: {
      type: Number,
      default: 0
    },
    onDrawDate: {
      type: Function,
      default () {}
    },
    maxDate: {
      type: String
    },
    minDate: {
      type: String
    },
    showDateOnly: {
      type: Boolean,
      default: false
    },
    transfer: {
      type: Boolean,
      default: false
    },
    elementId: [String]
  }

Credits

Inspired by vue-strap datepicker component.

For detailed explanation on how things work, checkout the guide and docs for vue-loader.

Comments
  • v-model didn't work

    v-model didn't work

    when I use this component, I can just change the data-inputValue instead of props-value. Then I got data of ''. So could you explain how to get the value? thanks a lot

    enhancement 
    opened by aianzi 5
  • Cannot read property 'root' of undefined

    Cannot read property 'root' of undefined

    import Calendar from 'vue-calendar'
    
    VM7796:27 TypeError: Cannot read property 'root' of undefined
        at Proxy.eval (eval at makeFunction (eval at <anonymous> (app.js:604)), <anonymous>:2:45)
        at VueComponent.Vue._render (eval at <anonymous> (app.js:604), <anonymous>:2252:22)
        at VueComponent.eval (eval at <anonymous> (app.js:604), <anonymous>:1699:21)
        at Watcher.get (eval at <anonymous> (app.js:604), <anonymous>:740:27)
        at new Watcher (eval at <anonymous> (app.js:604), <anonymous>:732:12)
        at VueComponent.Vue._mount (eval at <anonymous> (app.js:604), <anonymous>:1698:19)
        at VueComponent.Vue$3.$mount (eval at <anonymous> (app.js:604), <anonymous>:5718:15)
        at VueComponent.Vue$3.$mount (eval at <anonymous> (app.js:604), <anonymous>:7870:16)
        at init (eval at <anonymous> (app.js:604), <anonymous>:1988:11)
        at createElm (eval at <anonymous> (app.js:604), <anonymous>:3867:56)
    
    opened by SacDin 3
  • Handle offsetParent being null on offset calculation

    Handle offsetParent being null on offset calculation

    There are a few situations where $el.offsetParent will return a null:

    1. Parent having display: none
    2. Parent having position: fixed (at least on Chrome)
    3. document hasn't finished loading yet

    I am experiencing item two and this is my proposed fix. I've tested the PR locally with an application that heavily uses this component.

    And thanks by the way 🙏

    opened by chrisledet 2
  • How to disable this calendar input?

    How to disable this calendar input?

    Hello, thank you for providing this nice and simple component! I had one question regarding programatically enable/disable this datepicker control, is there any approach on doing that? Seems there is no property like ":disable" here...

    Best, kevin

    opened by kevinding0218 2
  • Fixing the days of the week being wrong when the first day of the month is a monday

    Fixing the days of the week being wrong when the first day of the month is a monday

    The calendar is broken when the first day of the month is a Sunday. April 2019 is a good example of this. vue-calendar currently shows it as a Sunday, when it is, in fact, a Monday, but it is being displayed as a Sunday, throwing off all the dates for that month.

    opened by matuszeg 1
  • Issue next month function when date is > 28

    Issue next month function when date is > 28

    I use the calendar with 1 pane only.

    Today is 31 May 2018. If I click the Next button in the calendar it redirect to July 2018.

    The problem is here:

    preNextMonthClick (flag) {
            const year = this.currDate.getFullYear()
            const month = this.currDate.getMonth()
            const date = this.currDate.getDate() // 31
            if (flag === 0) {
              const preMonth = this.getYearMonth(year, month - 1)
              this.currDate = new Date(preMonth.year, preMonth.month, date)
              this.changePane(preMonth.year, preMonth.month, this.pane)
            } else {
              const nextMonth = this.getYearMonth(year, month + 1)
              this.currDate = new Date(nextMonth.year, nextMonth.month, date) // Sun Jul 01 2018 00:00:00 GMT+0300 (EEST)
              this.changePane(nextMonth.year, nextMonth.month, this.pane)
            }
          },
    

    when try to get a new Date using 31 june as parameters. Since it doesn't exist it gives the following day: 1 July. A very specific case. Yet better to avoid it.

    I made a temporary workaround. Just check if the date is more the 28 and set to 10.

    preNextMonthClick (flag) {
        const year = this.currDate.getFullYear()
        const month = this.currDate.getMonth()
        let date = this.currDate.getDate()
        if (date > 28) {
          date = 10
        }
        if (flag === 0) {
          const preMonth = this.getYearMonth(year, month - 1)
          this.currDate = new Date(preMonth.year, preMonth.month, date)
          this.changePane(preMonth.year, preMonth.month, this.pane)
        } else {
          const nextMonth = this.getYearMonth(year, month + 1)
          this.currDate = new Date(nextMonth.year, nextMonth.month, date) // _Sun Jun 10 2018 00:00:00 GMT+0300 (EEST)_ 
          this.changePane(nextMonth.year, nextMonth.month, this.pane)
        }
      },
    
    opened by FrancescoMussi 1
  • when inputValue date is overflow nextMonth date,new Date(nextMonth.year, nextMonth.month, date) is wrong

    when inputValue date is overflow nextMonth date,new Date(nextMonth.year, nextMonth.month, date) is wrong

    in method preNextMonthClick: this.currDate = new Date(nextMonth.year, nextMonth.month, date) when inputValue date is not in nextMonth date for example :inputValue is 2018-05-31 new Date(2018, 5, 31) = Sun Jul 01 2018 00:00:00 so preNextMonthClick('nextMonth') is Jul not June. i set 475: const date = 1. let activeDay = new Date(this.inputValue).getDate() 。before 629 for cycle 659: i === activeDay

    中文: 英语很差,还是加上中文描述一下吧,当inputValue时间是2018-05-31时,由于6月没有31,所以new Date(2018, 5, 31) 会变成7月,于是我把日期设为1,然后判断datepicker-dateRange-item-active这里直接用inputValue的值,为了性能考虑,不必每次for中再重新new Date(this.inputValue).getDate(),所以写在了for循环外。 还有刚打开项目时,由于eslint,第一次运行时是运行不起来的,建议完善一下~

    最后感谢你无私的开源~

    opened by snail3157 1
  • Allow styles in the date input field

    Allow styles in the date input field

    This change will give more liberty to the user when using the input for the date. It allows to apply styles to the input by using the prop input-classes:

    It is compatible with the current version clear-button prop can still be used.

    opened by gorkau 1
  • Remove scss loader dependency

    Remove scss loader dependency

    When trying to import this as a Vue component (import DatePicker from 'vue2-slot-calendar') and error is thrown regarding the missing style loader.

    This dependency was not found:
    
    * !!vue-style-loader!css-loader?{"minimize":false,"sourceMap":false}!../../vue-loader/lib/style-compiler/index?{"vue":true,"id":"data-v-6fe5cf14","scoped":false,"hasInlineConfig":false}!sass-loader?{"sourceMap":false}!../../vue-loader/lib/selector?type=styles&index=0&bustCache!./Calendar.vue in ./~/vue2-slot-calendar/lib/Calendar.vue
    
    To install it, you can run: npm install --save !!vue-style-loader!css-loader?{"minimize":false,"sourceMap":false}!../../vue-loader/lib/style-compiler/index?{"vue":true,"id":"data-v-6fe5cf14","scoped":false,"hasInlineConfig":false}!sass-loader?{"sourceMap":false}!../../vue-loader/lib/selector?type=styles&index=0&bustCache!./Calendar.vue
    
    

    This is due to the fact that the Calendar.vue file is uses scss.

    opened by morficus 1
  • Making the component responsive

    Making the component responsive

    I need to make the component responsive.

    I make a basic working structure:

    @media screen and (min-width: 1200px) {
        
      .event-calendar {
        .datepicker-inner{
          width: 440px;
        }
        .datepicker-body{
          span{
            width: 60px;
          }
        }
      }
    }
    
    
    @media screen and (max-width: 1199px) {
        
      .event-calendar {
        .datepicker-inner{
          width: 340px;
        }
        .datepicker-body{
          span{
            width: 45px;
            font-size: 0.9rem;
          }  
        }
      }
    } 
    

    It needs refinements of course but as basic structure it is working.

    But I have a question:

    What is the purpose of the updatePaneStyle function? It seems to resize on the width of the datepicker.

    Because the only way to make the datepicker properly resize was to actually remove that function.

    By the way it could be awesome if in a future release the component would be responsive by default. Do you think it would be possible?

    Thanks!

    opened by FrancescoMussi 1
  • import moment.js to handle date functions

    import moment.js to handle date functions

    I think there is no need to reinvent the wheel of date handler, and moment.js is useful. I try to modify the source code like this:

    <template>
      <div class="datepicker">
        <template v-if="hasInput">
          <input class="form-control datepicker-input" :class="{'with-reset-button': clearButton}" type="text" :placeholder="placeholder"
              :style="{width:width}"
              @click="inputClick"
              v-model="inputValue"/>
          <button v-if="clearButton && value" type="button" class="close" @click="inputValue = ''">
            <span>&times;</span>
          </button>
        </template>
        <div class="datepicker-popup" :style="paneStyle" @mouseover="handleMouseOver" @mouseout="handleMouseOver" v-show="displayDayView">
          <div class="datepicker-ctrl">
            <span class="datepicker-preBtn fa fa-chevron-left" aria-hidden="true" @click="preNextMonthClick(0)"></span>
            <span class="datepicker-nextBtn fa fa-chevron-right" aria-hidden="true" @click="preNextMonthClick(1)"></span>
          </div>
          <template v-for="(p, pan) in pane" >
            <div class="datepicker-inner">
              <div class="datepicker-body">
                <p class="datepicker-header" @click="switchMonthView">{{stringifyDayHeader(currDate, pan)}}</p>
                <div class="datepicker-weekRange">
                  <span v-for="w in daysOfWeek">{{w}}</span>
                </div>
                <div class="datepicker-dateRange">
                  <span v-for="d in dateRange[pan]" class="day-cell" :class="getItemClasses(d)" :data-date="stringify(d.date)" @click="daySelect(d.date, $event)"><div>
                    <template v-if="d.sclass !== 'datepicker-item-gray'">
                      {{getSpecailDay(d.date) || d.text}}
                    </template>
                    <template v-else>
                        {{d.text}}
                    </template>
                    <div v-if="d.sclass !== 'datepicker-item-gray'"><slot :name="stringify(d.date)"></slot></div></div>
                  </span>
                </div>
              </div>
            </div>
          </template>
        </div>
        <div class="datepicker-popup" :style="paneStyle" v-show="displayMonthView">
          <div class="datepicker-ctrl">
            <span class="datepicker-preBtn fa fa-chevron-left" aria-hidden="true" @click="preNextYearClick(0)"></span>
            <span class="datepicker-nextBtn fa fa-chevron-right" aria-hidden="true" @click="preNextYearClick(1)"></span>
          </div>
          <template v-for="(p, pan) in pane" >
            <div class="datepicker-inner">
              <div class="datepicker-body">
                <p class="datepicker-header" @click="switchDecadeView">{{stringifyYearHeader(currDate, pan)}}</p>
                <div class="datepicker-monthRange">
                  <template v-for="(m, $index) in months">
                    <span :class="{'datepicker-dateRange-item-active':
                        (months[parse(value).month()]  === m) &&
                        currDate.year() + pan === parse(value).year()}"
                        @click="monthSelect(stringifyYearHeader(currDate, pan), $index)"
                      >{{m}}</span>
                  </template>
                </div>
              </div>
            </div>
          </template>
        </div>
        <div class="datepicker-popup" :style="paneStyle" v-show="displayYearView">
          <div class="datepicker-ctrl">
            <span class="datepicker-preBtn fa fa-chevron-left" aria-hidden="true" @click="preNextDecadeClick(0)"></span>
            <span class="datepicker-nextBtn fa fa-chevron-right" aria-hidden="true" @click="preNextDecadeClick(1)"></span>
          </div>
          <template v-for="(p, pan) in pane" >
            <div class="datepicker-inner">
              <div class="datepicker-body">
                <p class="datepicker-header">{{stringifyDecadeHeader(currDate, pan)}}</p>
                <div class="datepicker-monthRange decadeRange">
                  <template v-for="decade in decadeRange[pan]">
                    <span :class="{'datepicker-dateRange-item-active':
                        parse(inputValue).year() === decade.text}"
                        @click.stop="yearSelect(decade.text)"
                      >{{decade.text}}</span>
                  </template>
                </div>
              </div>
            </div>
          </template>
        </div>
      </div>
    </template>
    
    <script>
    import i18n from './i18n'
    import moment from 'moment'
    export default {
      name: 'calendar',
      props: {
        value: {
          type: String
        },
        format: {
          default: 'YYYY-MM-DD'
        },
        disabledDaysOfWeek: {
          type: Array,
          default () {
            return []
          }
        },
        width: {
          type: String,
          default: '200px'
        },
        clearButton: {
          type: Boolean,
          default: false
        },
        lang: {
          type: String,
          default: navigator.language
        },
        placeholder: {
          type: String
        },
        hasInput: {
          type: Boolean,
          default: true
        },
        pane: {
          type: Number,
          default: 1
        },
        borderWidth: {
          type: Number,
          default: 2
        },
        onDayClick: {
          type: Function,
          default () {}
        },
        changePane: {
          type: Function,
          default () {}
        },
        specialDays: {
          type: Object,
          default () {
            return {}
          }
        },
        rangeBus: {
          type: Function,
          default () {
            // return new Vue()
          }
        },
        rangeStatus: {
          type: Number,
          default: 0
        }
      },
      mounted () {
        this._blur = (e) => {
          if (!this.$el.contains(e.target) && this.hasInput) this.close()
        }
        this.$emit('child-created', this)
        // this.inputValue = this.value
        // this.dateFormat = this.format
        this.currDate = this.parse(this.inputValue) || moment()
        const year = this.currDate.year()
        const month = this.currDate.month()
        this.changePane(year, month, this.pane)
        if (!this.hasInput) {
          this.displayDayView = true
          this.updatePaneStyle()
        }
        if (this.rangeStatus) {
          this.eventbus = this.rangeBus()
          if (typeof this.eventbus === 'object' && !this.eventbus.$on) {
            console.warn('Calendar rangeBus doesn\'t exist')
            this.rangeStatus = 0
          }
        }
        if (this.rangeStatus === 2) {
          this._updateRangeStart = (date) => {
            this.rangeStart = date
            this.currDate = date
            this.inputValue = this.stringify(this.currDate)
          }
          this.eventbus.$on('calendar-rangestart', this._updateRangeStart)
        }
        document.addEventListener('click', this._blur)
      },
      beforeDestroy () {
        document.removeEventListener('click', this._blur)
        if (this.rangeStatus === 2) {
          this.eventbus.$off('calendar-rangestart', this._updateRangeStart)
        }
      },
      data () {
        return {
          inputValue: this.value,
          dateFormat: this.format,
          months: (function () {
            let months = []
            for (let i = 0; i < 12; i++) {
              months.push(moment({month: i}).format('MMM'))
            }
            return months
          })(),
          daysOfWeek: (function () {
            let daysOfWeek = []
            for (let i = 0; i < 7; i++) {
              daysOfWeek.push(moment().weekday(i).format('ddd'))
            }
            return daysOfWeek
          })(),
          currDate: moment(),
          dateRange: [],
          decadeRange: [],
          paneStyle: {
            width: ''
          },
          displayDayView: false,
          displayMonthView: false,
          displayYearView: false,
          rangeStart: false,
          rangeEnd: false
        }
      },
      watch: {
        currDate () {
          this.getDateRange()
        }
      },
      computed: {
        text () {
          return this.translations(this.lang)
        }
      },
      methods: {
        handleMouseOver (event) {
          let target = event.target
          // this.rangeEnd = false
          if (!this.rangeStart) {
            return true
          }
          if (event.type === 'mouseout') {
            return true
          }
          while (this.$el.contains(target) && !~target.className.indexOf('day-cell')) {
            target = target.parentNode
          }
          if (~target.className.indexOf('day-cell') && !~target.className.indexOf('datepicker-item-gray')) {
            const rangeEnd = target.getAttribute('data-date')
            if (this.rangeStart < this.parse(rangeEnd)) {
              this.rangeEnd = this.parse(rangeEnd)
            }
          }
        },
        getItemClasses (d) {
          const clazz = []
          clazz.push(d.sclass)
          if (this.rangeStart && this.rangeEnd && d.sclass !== 'datepicker-item-gray') {
            if (d.date > this.rangeStart && d.date < this.rangeEnd) {
              clazz.push('daytoday-range')
            }
            /* eslint-disable eqeqeq */
            if (this.stringify(d.date) == this.stringify(this.rangeStart)) {
              clazz.push('daytoday-start')
            }
            /* eslint-disable eqeqeq */
            if (this.stringify(d.date) == this.stringify(this.rangeEnd)) {
              clazz.push('daytoday-end')
            }
          }
          return clazz.join(' ')
        },
        translations (lang) {
          lang = lang || 'en'
          return i18n[lang]
        },
        close () {
          this.displayDayView = this.displayMonthView = this.displayYearView = false
        },
        inputClick () {
          this.currDate = this.parse(this.inputValue) || moment()
          if (this.displayMonthView || this.displayYearView) {
            this.displayDayView = false
          } else {
            this.displayDayView = !this.displayDayView
          }
          this.updatePaneStyle()
        },
        updatePaneStyle () {
          if (!(this.displayMonthView || this.displayYearView)) {
            this.$nextTick(function () {
              let offsetLeft = this.$el.offsetLeft
              let offsetWidth = this.$el.querySelector('.datepicker-inner').offsetWidth
              let popWidth = this.pane * offsetWidth + this.borderWidth // add border
              this.paneStyle.width = popWidth + 'px'
              if (this.hasInput) {
                if (popWidth + offsetLeft > document.documentElement.clientWidth) {
                  this.paneStyle.right = '0px'
                }
              } else {
                this.paneStyle.position = 'initial'
              }
              this.$forceUpdate()
            })
          }
        },
        preNextDecadeClick (flag) {
          if (flag === 0) {
            this.currDate = this.currDate.clone().subtract(10, 'years')
          } else {
            this.currDate = this.currDate.clone().add(10, 'years')
          }
        },
        preNextMonthClick (flag) {
          if (flag === 0) {
            this.currDate = this.currDate.clone().subtract(1, 'months')
            this.changePane(this.currDate.year(), this.currDate.month(), this.pane)
          } else {
            this.currDate = this.currDate.clone().add(1, 'months')
            this.changePane(this.currDate.year(), this.currDate.month(), this.pane)
          }
        },
        preNextYearClick (flag) {
          if (flag === 0) {
            this.currDate = this.currDate.clone().subtract(1, 'years')
          } else {
            this.currDate = this.currDate.clone().add(1, 'years')
          }
        },
        yearSelect (year) {
          this.displayYearView = false
          this.displayMonthView = true
          this.currDate = this.currDate.clone().year(year)
        },
        daySelect (date, event) {
          let el = event.target
          if (el.classList[0] === 'datepicker-item-disable') {
            return false
          } else {
            if (this.hasInput) {
              this.currDate = date
              this.inputValue = this.stringify(this.currDate)
              this.displayDayView = false
              if (this.rangeStatus === 1) {
                this.eventbus.$emit('calendar-rangestart', this.currDate)
              }
            } else {
              this.onDayClick(date, this.stringify(date))
            }
          }
        },
        switchMonthView () {
          this.displayDayView = false
          this.displayMonthView = true
        },
        switchDecadeView () {
          this.displayMonthView = false
          this.displayYearView = true
        },
        monthSelect (year, month) {
          this.displayMonthView = false
          this.displayDayView = true
          this.currDate = this.currDate.clone().set({
            year: year,
            month: month
          })
          this.changePane(year, month, this.pane)
        },
        getYearMonth (year, month) {
          if (month > 11) {
            year++
            month = 0
          } else if (month < 0) {
            year--
            month = 11
          }
          return {year: year, month: month}
        },
        getSpecailDay (v) {
          return this.specialDays[this.stringify(v)]
        },
        stringifyDecadeHeader (date, pan) {
          const yearStr = date.year().toString()
          const firstYearOfDecade = parseInt(yearStr.substring(0, yearStr.length - 1) + 0, 10) + (pan * 10)
          const lastYearOfDecade = parseInt(firstYearOfDecade, 10) + 10
          return firstYearOfDecade + '-' + lastYearOfDecade
        },
        siblingsMonth (date, offset) {
          return date.clone().add({month: offset})
        },
        stringifyDayHeader (date, month = 0) {
          const d = this.siblingsMonth(date, month)
          return d.format('YYYY MMMM')
        },
        parseMonth (date) {
          return date.format('MMMM')
        },
        stringifyYearHeader (date, year = 0) {
          return date.year() + year
        },
        stringify (date, format = this.dateFormat) {
          if (!date) date = this.parse()
          if (!date) return ''
          return date.format(format)
        },
        parse (str = this.inputValue) {
          return moment(str, this.dateFormat)
        },
        getDayCount (year, month) {
          return moment({year: year, month: month}).endOf('month').date()
        },
        getDateRange () {
          this.dateRange = []
          this.decadeRange = []
          for (let p = 0; p < this.pane; p++) {
            let currMonth = this.siblingsMonth(this.currDate, p)
            let time = {
              year: currMonth.year(),
              month: currMonth.month()
            }
            let yearStr = time.year.toString()
            this.decadeRange[p] = []
            let firstYearOfDecade = (yearStr.substring(0, yearStr.length - 1) + 0) - 1
            for (let i = 0; i < 12; i++) {
              this.decadeRange[p].push({
                text: firstYearOfDecade + i + p * 10
              })
            }
            this.dateRange[p] = []
            const currMonthFirstDay = moment({year: time.year, month: time.month, day: 1})
            let firstDayWeek = currMonthFirstDay.weekday()
            const dayCount = this.getDayCount(time.year, time.month)
            if (firstDayWeek > 0) {
              const preMonth = this.getYearMonth(time.year, time.month - 1)
              const prevMonthDayCount = this.getDayCount(preMonth.year, preMonth.month)
              for (let i = 0; i < firstDayWeek; i++) {
                const dayText = prevMonthDayCount - firstDayWeek + i + 1
                this.dateRange[p].push({
                  text: dayText,
                  date: moment({year: preMonth.year, month: preMonth.month, day: dayText}),
                  sclass: 'datepicker-item-gray'
                })
              }
            }
            for (let i = 1; i <= dayCount; i++) {
              const date = moment({year: time.year, month: time.month, day: i})
              const week = date.weekday()
              let sclass = ''
              this.disabledDaysOfWeek.forEach((el) => {
                if (week === parseInt(el, 10)) sclass = 'datepicker-item-disable'
              })
              if (i === this.currDate.date()) {
                if (this.inputValue) {
                  const valueDate = this.parse(this.inputValue)
                  if (valueDate) {
                    if (valueDate.year() === time.year && valueDate.month() === time.month) {
                      sclass = 'datepicker-dateRange-item-active'
                    }
                  }
                }
              }
              this.dateRange[p].push({
                text: i,
                date: date,
                sclass: sclass
              })
            }
            if (this.dateRange[p].length < 42) {
              const nextMonthNeed = 42 - this.dateRange[p].length
              const nextMonth = this.getYearMonth(time.year, time.month + 1)
              for (let i = 1; i <= nextMonthNeed; i++) {
                this.dateRange[p].push({
                  text: i,
                  date: moment({year: nextMonth.year, month: nextMonth.month, day: i}),
                  sclass: 'datepicker-item-gray'
                })
              }
            }
          }
        }
      }
    }
    </script>
    
    <style lang="scss" rel="stylesheet/scss" lang="scss">
    @import '~styles/utilities/all';
    
    .datepicker{
      position: relative;
      display: inline-block;
    
    }
    input.datepicker-input.with-reset-button {
      padding-right: 25px;
    }
    .datepicker > button.close {
      position: absolute;
      top: 0;
      right: 0;
      outline: none;
      z-index: 2;
      display: block;
      width: 34px;
      height: 34px;
      line-height: 34px;
      text-align: center;
    }
    .datepicker > button.close:focus {
      opacity: .2;
    }
    .datepicker-popup{
      position: absolute;
      background-color: $white;
      border-radius: $radius-large;
      box-shadow: 0 2px 3px rgba($black, 0.1), 0 0 0 1px rgba($black, 0.1);
      margin-top: 2px;
      z-index: 1000;
      @include clearfix;
    }
    .datepicker-inner{
      width: 218px;
      float: left;
    }
    .datepicker-body{
      padding: 10px 10px;
      text-align: center;
    }
    .datepicker-ctrl p,
    .datepicker-ctrl span,
    .datepicker-body span{
      display: inline-block;
      width: 28px;
      line-height: 28px;
      height: 28px;
      // border-radius: 4px;
    }
    .datepicker-ctrl p {
      width: 65%;
    }
    .datepicker-ctrl span {
      position: absolute;
    }
    .datepicker-body span {
      text-align: center;
    }
    .datepicker-monthRange span{
      width: 48px;
      height: 50px;
      line-height: 45px;
    }
    .datepicker-item-disable {
      background-color: $white!important;
      cursor: not-allowed!important;
    }
    .decadeRange span:first-child,
    .decadeRange span:last-child,
    .datepicker-item-disable,
    .datepicker-item-gray{
      color: $text-light;
    }
    .datepicker-dateRange-item-active:hover,
    .datepicker-dateRange-item-active {
      background: $primary!important;
      color: $text-invert!important;
    }
    .datepicker-monthRange {
      margin-top: 10px
    }
    .datepicker-monthRange span,
    .datepicker-ctrl span,
    .datepicker-ctrl p,
    .datepicker-dateRange span {
      cursor: pointer;
    }
    .datepicker-monthRange span:hover,
    .datepicker-ctrl p:hover,
    .datepicker-ctrl i:hover,
    .datepicker-dateRange span:hover,
    .datepicker-dateRange-item-hover {
      background-color : $grey-lighter;
    }
    .datepicker-dateRange {
      .daytoday-start,
      .daytoday-start:hover,
      .daytoday-end,
      .daytoday-end:hover{
        background: $primary!important;
        color: $white!important;
      }
    }
    .datepicker-dateRange .daytoday-range,
    .datepicker-dateRange .daytoday-range:hover{
      background-color: $gray;
    }
    .datepicker-weekRange span{
      font-weight: bold;
    }
    .datepicker-label{
      background-color: #f8f8f8;
      font-weight: 700;
      padding: 7px 0;
      text-align: center;
    }
    
    .datepicker-header {
      cursor: pointer;
    }
    
    .datepicker-ctrl{
      position: relative;
      /*height: 30px;*/
      line-height: 30px;
      font-weight: bold;
      text-align: center;
    }
    .month-btn{
      font-weight: bold;
      -webkit-user-select:none;
      -moz-user-select:none;
      -ms-user-select:none;
      user-select:none;
    }
    .datepicker-preBtn{
      left: 2px;
    }
    .datepicker-nextBtn{
      right: 2px;
    }
    </style>
    
    

    The language file can exclude date locale strings now, just like this: i18n/en.js:

    export default {
      limit: 'Limit reached ({{limit}} items max).',
      loading: 'Loading...',
      minLength: 'Min. Length',
      notSelected: 'Nothing Selected',
      required: 'Required',
      search: 'Search'
    }
    

    You can set the locale with moment.js during initialization. By the way, if you set the default locale from 'en' to 'zh-cn': moment.locale('zh-cn') Monday is the first day of the week now.

    opened by mirari 1
  • Add support for an array of disabled dates

    Add support for an array of disabled dates

    Dates are formatted and provided to a disabled-days prop on de calendar component. Sample of the input data:

    ["2022-02-05","2022-02-06","2022-02-08"]

    opened by dwaynehulsman 0
  • Bootstrap styles are not applied properly

    Bootstrap styles are not applied properly

    Hello,

    Bootstrap styles are not applied properly. Inside grid control must use 100% width. Moreover I need to be able to set class of input element to handle valid/invalid feedback.

    image

    Can you fix it ?

    opened by scramatte 0
  • Action required: Greenkeeper could not be activated 🚨

    Action required: Greenkeeper could not be activated 🚨


    ☝️ Important announcement: Greenkeeper will be saying goodbye 👋 and passing the torch to Snyk on June 3rd, 2020! Find out how to migrate to Snyk and more at greenkeeper.io


    🚨 You need to enable Continuous Integration on Greenkeeper branches of this repository. 🚨

    To enable Greenkeeper, you need to make sure that a commit status is reported on all branches. This is required by Greenkeeper because it uses your CI build statuses to figure out when to notify you about breaking changes.

    Since we didn’t receive a CI status on the greenkeeper/initial branch, it’s possible that you don’t have CI set up yet. We recommend using:

    If you have already set up a CI for this repository, you might need to check how it’s configured. Make sure it is set to run on all new branches. If you don’t want it to run on absolutely every branch, you can whitelist branches starting with greenkeeper/.

    Once you have installed and configured CI on this repository correctly, you’ll need to re-trigger Greenkeeper’s initial pull request. To do this, please click the 'fix repo' button on account.greenkeeper.io.

    greenkeeper 
    opened by greenkeeper[bot] 0
  • Fix time zones (Date.parse())

    Fix time zones (Date.parse())

    Fixed mismatch of time zones with date formats yyyy-MM-dd and yyyy/MM/dd https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse#Date_Time_String_Format

    opened by diableroua 1
A vue component for lunar calendar.

vue-lunar-calendar A vue component for lunar calendar. Uses Moment.js for date operations. This is the Korean lunar calendar. It is different from Chi

Kim WooHyun 70 Aug 20, 2022
Simple Vue component to show a month-grid calendar with events

VueSimpleCalendar Introduction vue-simple-calendar is a flexible, themeable, lightweight calendar component for Vue that supports multi-day scheduled

Richard Tallent 762 Jan 3, 2023
A simple infinite calendar component in Vue 2

vue-infinite-calendar A simple infinite calendar component in Vue 2 Build Setup # install dependencies npm install # serve with hot reload at localho

Rares S 15 Feb 28, 2022
A calendar component for Vue.js

calendar Chinese This is a calendar component based on vue.js . support custom content. No dependencies. Currently, It only supports month view. You c

Kylin 47 Aug 16, 2022
vue 2.x calendar component

vue2-calendar vue 2 calendar, datepicker component which supported lunar or date event Live Demo >> This project is not only a vue component, but also

Terry Cai 485 Dec 18, 2022
Vue.js Functional Calendar | Component/Package

Vue Functional Calendar Modern calendar and datepicker module for Vue.js Demo Demo: https://y3jnxov469.codesandbox.io/ Lightweight, high-performance c

Manuk 425 Dec 27, 2022
📅 A feature-rich calendar component, support multiple modes and gesture sliding. For vue 3.0+

?? A calendar component for vue3.0. Support gesture sliding, range selection, according to the week switch...

飞翔的荷兰人 434 Jan 3, 2023
a horizontal calendar component for Vue.js

a horizontal calendar component for Vue.js

jacques 37 Aug 10, 2022
A simple vue calendar component. Base for further development and styling.

vue-simple-calendar A simple vue calendar component with minimal css. A base for further development/styling. DEMO Dependencies date-fns, lodash-es In

Roman Ranniew 6 Jun 15, 2022
Calendar component

vue-calendar-picker Calendar component vue-calendar-picker demo on jsfiddle Example - basic <template> <calendar></calendar> </template> <script>

Franck Freiburger 47 Nov 24, 2022
A lightweight calendar component for Vue2

vue2-calendar Check out the demo here on Code Sandbox: Introduction This is a simple and small event calendar component for Vue js. It is very lightwe

Vincent 55 May 2, 2022
vue calendar fullCalendar. no jquery required. Schedule events management

##vue-fullcalendar Works for Vue2 now. This is a fullCalendar component based on vue.js . No Jquery or fullCalendar.js required. Currently, It only su

Sunny Wang 1.5k Dec 18, 2022
An elegant calendar and datepicker plugin for Vue.

An elegant calendar and datepicker plugin for Vuejs. npm i --save v-calendar Documentation For full documentation, visit vcalendar.io. Attributes High

Nathan Reyes 3.7k Dec 31, 2022
Vue.js wrapper for TOAST UI Calendar

Vue TOAST UI Calendar A Vue.js wrapper for TOAST UI Calendar Installation npm install --save tui-calendar @lkmadushan/vue-tuicalendar Usage Example Tr

Kalpa Madushan Perera 129 Aug 13, 2022
Toast UI Calendar for Vue

TOAST UI Calendar for Vue This is Vue component wrapping TOAST UI Calendar. ?? Table of Contents Collect statistics on the use of open source Install

NHN 195 Dec 6, 2022
A Vue JS full calendar, no dependency, no BS. :metal:

vue-cal A Vue JS full calendar, no dependency, no BS. ?? Installation npm i vue-cal Vue 3 npm i vue-cal@next Demo & Documentation antoniandre.github

Antoni 982 Dec 29, 2022
A full 12-Month view calendar made by vue.js.

English | 繁體中文 There is no full year (12 months on a page) calendar right now, the Vue-material-year-calendar is designed to solve this problem. ?? 12

null 114 Dec 13, 2022
Simple and clean calendar written in Vue.js

Vuelendar Simple and clean calendar written in Vue.js. Check out full Vuelendar's documentation here. Features Select single date Select range of date

The Codest 76 Oct 5, 2022
Full Calendar based on Vue.js

Vue Spring Calendar It's a Vue based component which provides the functionality of a full-calendar that shows daily events. Installation npm install v

Brahim 40 Jun 8, 2022