@wslyhbb/vuejs-datepicker
Version:
A simple Vue.js datepicker component. Supports disabling of dates, inline mode, translations. Fork of initial vuejs-datepicker which is abandoned.
354 lines (324 loc) • 9.99 kB
JavaScript
import { makeDateUtils, rtlLangs } from '@/utils/DateUtils'
import DisabledDate from '@/utils/DisabledDate'
export default {
props: {
bootstrapStyling: {
type: Boolean,
default: false
},
calendarClass: [String, Object, Array],
calendarStyle: {
type: Object
},
disabledDates: {
type: Object
},
isTypeable: {
type: Boolean,
default: false
},
isUpDisabled: {
type: Boolean,
default: false
},
language: {
type: Object
},
pageDate: {
type: Date,
default: null
},
selectedDate: {
type: Date,
default: null
},
slideDuration: {
type: Number,
default: 250
},
tabbableCellId: {
type: Number,
default: null
},
useUtc: {
type: Boolean,
default: false
},
visible: {
type: Boolean,
default: false
}
},
emits: {
select: (cell) => {
return typeof cell === 'object'
},
selectedDisabled: (date) => {
return typeof date === 'object'
},
setFocus: (refArray) => {
return Array.isArray(refArray) && refArray.length === 1 && refArray[0] === 'input'
}
},
data () {
const constructedDateUtils = makeDateUtils(this.useUtc, this.language)
return {
utils: constructedDateUtils
}
},
watch: {
language (newLanguage) {
this.utils = makeDateUtils(this.useUtc, newLanguage)
},
useUtc (newUtc) {
this.utils = makeDateUtils(newUtc, this.language)
}
},
computed: {
/**
* A look-up object created from 'disabledDates' prop
* @return {Object}
*/
disabledConfig () {
if (!this.disabledDates) {
return {
has: {
from: false,
to: false
}
}
}
return new DisabledDate(this.utils, this.disabledDates).config
},
earliestPossibleDate () {
if (!this.disabledDates) return null
return new DisabledDate(
this.utils,
this.disabledDates
).getEarliestPossibleDate(this.disabledDates.to)
},
isRtl () {
return rtlLangs.indexOf(this.language) !== -1
},
latestPossibleDate () {
if (!this.disabledDates) return null
return new DisabledDate(
this.utils,
this.disabledDates
).getLatestPossibleDate(this.disabledDates.from)
},
/**
* Returns the current page's full year as an integer.
* @return {Number}
*/
pageYear () {
return this.utils.getFullYear(this.pageDate)
}
},
methods: {
/**
* Used when an arrow key press would cause the focus to land on a disabled date
* @param {Object} options
*/
addMoreSteps (options) {
if (options.stepsRemaining <= 0 && Math.abs(options.delta) > 1) {
return Math.abs(options.delta)
}
return options.stepsRemaining
},
/**
* Changes the page and focuses the cell that is being 'arrowed' to
* @param {Object} options
*/
changePageAndSetFocus (options) {
const { delta } = options
const isPageDisabled =
(delta > 0 && this.isNextDisabled) ||
(delta < 0 && this.isPreviousDisabled)
if (isPageDisabled) {
return
}
this.changePage({
incrementBy: Math.sign(delta),
focusRefs: ['arrow-to-cell']
})
this.$nextTick(() => {
this.setFocusOnNewPage(options)
})
},
/**
* Focuses the input field, if typeable
*/
focusInput () {
if (this.isTypeable) {
this.$emit('setFocus', ['input'])
}
},
/**
* Returns the element that should be focused when navigating via an arrow key
* @param {HTMLButtonElement} currentElement The element currently being iterated on
* @param {Number} delta The number of cells that the focus should move
* @param {Number} stepsRemaining The number of steps remaining in the iteration
* @return {HTMLButtonElement | void}
*/
getElement ({ currentElement, delta, stepsRemaining }) {
const element = this.getElementSibling(currentElement, delta)
const options = {
currentElement: element,
delta,
stepsRemaining: stepsRemaining - 1
}
if (!element) {
return this.changePageAndSetFocus(options)
}
if (this.isBeyondPossibleDate(options)) {
return this.firstOrLastPossibleDate(options)
}
if (this.isMutedOrDisabled(element)) {
options.stepsRemaining = this.addMoreSteps(options)
return this.getElement(options)
}
if (stepsRemaining > 1 && options.currentElement) {
return this.getElement(options)
}
return element
},
/**
* Returns the element directly next to the currentElement
* @param {HTMLButtonElement} currentElement The element currently being iterated on
* @param {Number} delta The number of cells that the focus should move
* @return {HTMLButtonElement}
*/
getElementSibling (currentElement, delta) {
const isNext = delta > 0
return isNext
? currentElement.nextElementSibling
: currentElement.previousElementSibling
},
/**
* Returns the first or last cell, depending on the direction of the search
* @param {Number} delta The number of cells that the focus should move
* @return {HTMLButtonElement}
*/
getFirstOrLastElement (delta) {
const isNext = delta > 0
const elements = this.$refs.cells.$el.children
return isNext ? elements[0] : elements[elements.length - 1]
},
/**
* Returns the first or last non-disabled date, depending on the direction of the search
* @param {HTMLButtonElement} currentElement The element currently being iterated on
* @param {Number} delta The number of cells that the focus should move
*/
firstOrLastPossibleDate ({ currentElement, delta }) {
if (delta > 0) {
return this.getElementSibling(currentElement, -1)
}
return this.getElementSibling(currentElement, 1)
},
/**
* Moves the focused cell up/down/left/right
* @param {Object}
*/
handleArrow ({ delta }) {
const activeElement = document.activeElement.shadowRoot
? document.activeElement.shadowRoot.activeElement
: document.activeElement
const stepsRemaining = Math.abs(delta)
const options = {
currentElement: activeElement,
delta,
stepsRemaining
}
this.setFocusToAvailableCell(options)
},
/**
* Determines which transition to use (for edge dates) and emits a 'select' event
* @param {Object} cell
*/
select (cell) {
if (cell.isDisabled) {
this.$emit('selectedDisabled', cell)
} else {
this.$emit('select', cell)
}
},
/**
* Returns true if the given element cannot be focused
* @param {HTMLButtonElement} currentElement The element currently being iterated on
* @param {Number} delta The number of cells that the focus should move
* @return {Boolean}
*/
isBeyondPossibleDate ({ currentElement, delta }) {
if (delta > 0 && this.latestPossibleDate) {
return this.isDatePossible(currentElement, delta)
}
if (delta < 0 && this.earliestPossibleDate) {
return this.isDatePossible(currentElement, delta)
}
return false
},
/**
* Returns true if the current element's date is NOT possible, given the `disabled-dates`
* @param {HTMLButtonElement} element The element in question
* @param {Number} delta Used to determine direction of travel
* @return {Boolean}
*/
isDatePossible (element, delta) {
const cellId = element.getAttribute('data-id')
const cellDate = new Date(this.cells[cellId].timestamp)
if (delta > 0) {
return cellDate > this.latestPossibleDate
}
return cellDate < this.earliestPossibleDate
},
/**
* Returns true if the given element cannot be focused
* @param {HTMLButtonElement} element The element in question
* @return {Boolean}
*/
isMutedOrDisabled (element) {
const isMuted = element.classList.value.split(' ').includes('muted')
const isDisabled = element.disabled
return isMuted || isDisabled
},
/**
* Sets the focus on the correct cell following a page change
* @param {Object} options
*/
setFocusOnNewPage ({ delta, stepsRemaining }) {
const currentElement = this.getFirstOrLastElement(delta)
const options = {
currentElement,
delta,
stepsRemaining
}
if (stepsRemaining <= 0) {
if (this.isMutedOrDisabled(currentElement)) {
options.stepsRemaining = Math.abs(options.delta)
setTimeout(() => {
this.setFocusToAvailableCell(options)
}, this.slideDuration)
return
}
setTimeout(() => {
currentElement.focus()
}, this.slideDuration)
return
}
setTimeout(() => {
this.setFocusToAvailableCell(options)
}, this.slideDuration)
},
/**
* Sets the focus on the next focusable cell when an arrow key is pressed
* @param {Object} options
*/
setFocusToAvailableCell (options) {
const element = this.getElement(options)
if (element) {
element.focus()
}
}
}
}