quasar
Version:
Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time
1,461 lines (1,235 loc) • 40.7 kB
JavaScript
import Vue from 'vue'
import QBtn from '../btn/QBtn.js'
import DateTimeMixin from '../../mixins/datetime.js'
import { slot } from '../../utils/slot.js'
import { formatDate, __splitDate, getDateDiff } from '../../utils/date.js'
import { pad } from '../../utils/format.js'
import { jalaaliMonthLength, toGregorian } from '../../utils/date-persian.js'
import cache from '../../utils/cache.js'
const yearsInterval = 20
const views = [ 'Calendar', 'Years', 'Months' ]
const viewIsValid = v => views.includes(v)
const yearMonthValidator = v => /^-?[\d]+\/[0-1]\d$/.test(v)
const lineStr = ' \u2014 '
export default Vue.extend({
name: 'QDate',
mixins: [ DateTimeMixin ],
props: {
multiple: Boolean,
range: Boolean,
title: String,
subtitle: String,
mask: {
// this mask is forced
// when using persian calendar
default: 'YYYY/MM/DD'
},
defaultYearMonth: {
type: String,
validator: yearMonthValidator
},
yearsInMonthView: Boolean,
events: [ Array, Function ],
eventColor: [ String, Function ],
emitImmediately: Boolean,
options: [ Array, Function ],
navigationMinYearMonth: {
type: String,
validator: yearMonthValidator
},
navigationMaxYearMonth: {
type: String,
validator: yearMonthValidator
},
noUnset: Boolean,
firstDayOfWeek: [ String, Number ],
todayBtn: Boolean,
minimal: Boolean,
defaultView: {
type: String,
default: 'Calendar',
validator: viewIsValid
}
},
data () {
const
innerMask = this.__getMask(),
innerLocale = this.__getLocale(),
viewModel = this.__getViewModel(innerMask, innerLocale),
year = viewModel.year,
direction = this.$q.lang.rtl === true ? 'right' : 'left'
return {
view: this.defaultView,
monthDirection: direction,
yearDirection: direction,
startYear: year - (year % yearsInterval) - (year < 0 ? yearsInterval : 0),
editRange: void 0,
innerMask,
innerLocale,
viewModel // model of current calendar view
}
},
watch: {
value (v) {
if (this.lastEmitValue === v) {
this.lastEmitValue = 0
}
else {
const { year, month } = this.__getViewModel(this.innerMask, this.innerLocale)
this.__updateViewModel(year, month)
}
},
view () {
this.$refs.blurTarget !== void 0 && this.$refs.blurTarget.focus()
},
'viewModel.year' (year) {
this.$emit('navigation', { year, month: this.viewModel.month })
},
'viewModel.month' (month) {
this.$emit('navigation', { year: this.viewModel.year, month })
},
computedMask (val) {
this.__updateValue(val, this.innerLocale, 'mask')
this.innerMask = val
},
computedLocale (val) {
this.__updateValue(this.innerMask, val, 'locale')
this.innerLocale = val
}
},
computed: {
classes () {
const type = this.landscape === true ? 'landscape' : 'portrait'
return `q-date q-date--${type} q-date--${type}-${this.minimal === true ? 'minimal' : 'standard'}` +
(this.isDark === true ? ' q-date--dark q-dark' : '') +
(this.bordered === true ? ` q-date--bordered` : '') +
(this.square === true ? ` q-date--square no-border-radius` : '') +
(this.flat === true ? ` q-date--flat no-shadow` : '') +
(this.disable === true ? ' disabled' : (this.readonly === true ? ' q-date--readonly' : ''))
},
isImmediate () {
return this.emitImmediately === true &&
this.multiple !== true &&
this.range !== true
},
normalizedModel () {
return Array.isArray(this.value) === true
? this.value
: (this.value !== null && this.value !== void 0 ? [ this.value ] : [])
},
daysModel () {
return this.normalizedModel
.filter(date => typeof date === 'string')
.map(date => this.__decodeString(date, this.innerMask, this.innerLocale))
.filter(date => date.dateHash !== null)
},
rangeModel () {
const fn = date => this.__decodeString(date, this.innerMask, this.innerLocale)
return this.normalizedModel
.filter(date => Object(date) === date && date.from !== void 0 && date.to !== void 0)
.map(range => ({ from: fn(range.from), to: fn(range.to) }))
.filter(range => range.from.dateHash !== null && range.to.dateHash !== null && range.from.dateHash < range.to.dateHash)
},
getNativeDateFn () {
return this.calendar !== 'persian'
? model => new Date(model.year, model.month - 1, model.day)
: model => {
const gDate = toGregorian(model.year, model.month, model.day)
return new Date(gDate.gy, gDate.gm - 1, gDate.gd)
}
},
encodeObjectFn () {
return this.calendar === 'persian'
? this.__getDayHash
: (date, mask, locale) => formatDate(
new Date(
date.year,
date.month - 1,
date.day,
date.hour,
date.minute,
date.second,
date.millisecond
),
mask === void 0 ? this.innerMask : mask,
locale === void 0 ? this.innerLocale : locale,
date.year,
date.timezoneOffset
)
},
daysInModel () {
return this.daysModel.length + this.rangeModel.reduce(
(acc, range) => acc + 1 + getDateDiff(
this.getNativeDateFn(range.to),
this.getNativeDateFn(range.from)
),
0
)
},
headerTitle () {
if (this.title !== void 0 && this.title !== null && this.title.length > 0) {
return this.title
}
if (this.editRange !== void 0) {
const model = this.editRange.init
const date = this.getNativeDateFn(model)
return this.innerLocale.daysShort[ date.getDay() ] + ', ' +
this.innerLocale.monthsShort[ model.month - 1 ] + ' ' +
model.day + lineStr + '?'
}
if (this.daysInModel === 0) {
return lineStr
}
if (this.daysInModel > 1) {
return `${this.daysInModel} ${this.innerLocale.pluralDay}`
}
const model = this.daysModel[0]
const date = this.getNativeDateFn(model)
if (isNaN(date.valueOf()) === true) {
return lineStr
}
if (this.innerLocale.headerTitle !== void 0) {
return this.innerLocale.headerTitle(date, model)
}
return this.innerLocale.daysShort[ date.getDay() ] + ', ' +
this.innerLocale.monthsShort[ model.month - 1 ] + ' ' +
model.day
},
headerSubtitle () {
if (this.subtitle !== void 0 && this.subtitle !== null && this.subtitle.length > 0) {
return this.subtitle
}
if (this.daysInModel === 0) {
return lineStr
}
if (this.daysInModel > 1) {
const from = this.minSelectedModel
const to = this.maxSelectedModel
const month = this.innerLocale.monthsShort
return month[from.month - 1] + (
from.year !== to.year
? ' ' + from.year + lineStr + month[to.month - 1] + ' '
: (
from.month !== to.month
? lineStr + month[to.month - 1]
: ''
)
) + ' ' + to.year
}
return this.daysModel[0].year
},
minSelectedModel () {
const model = this.daysModel.concat(this.rangeModel.map(range => range.from))
.sort((a, b) => a.year - b.year || a.month - b.month)
return model[0]
},
maxSelectedModel () {
const model = this.daysModel.concat(this.rangeModel.map(range => range.to))
.sort((a, b) => b.year - a.year || b.month - a.month)
return model[0]
},
dateArrow () {
const val = [ this.$q.iconSet.datetime.arrowLeft, this.$q.iconSet.datetime.arrowRight ]
return this.$q.lang.rtl === true ? val.reverse() : val
},
computedFirstDayOfWeek () {
return this.firstDayOfWeek !== void 0
? Number(this.firstDayOfWeek)
: this.innerLocale.firstDayOfWeek
},
daysOfWeek () {
const
days = this.innerLocale.daysShort,
first = this.computedFirstDayOfWeek
return first > 0
? days.slice(first, 7).concat(days.slice(0, first))
: days
},
daysInMonth () {
const date = this.viewModel
return this.calendar !== 'persian'
? (new Date(date.year, date.month, 0)).getDate()
: jalaaliMonthLength(date.year, date.month)
},
today () {
return this.__getCurrentDate()
},
evtColor () {
return typeof this.eventColor === 'function'
? this.eventColor
: () => this.eventColor
},
minNav () {
if (this.navigationMinYearMonth !== void 0) {
const data = this.navigationMinYearMonth.split('/')
return { year: parseInt(data[0], 10), month: parseInt(data[1], 10) }
}
},
maxNav () {
if (this.navigationMaxYearMonth !== void 0) {
const data = this.navigationMaxYearMonth.split('/')
return { year: parseInt(data[0], 10), month: parseInt(data[1], 10) }
}
},
navBoundaries () {
const data = {
month: { prev: true, next: true },
year: { prev: true, next: true }
}
if (this.minNav !== void 0 && this.minNav.year >= this.viewModel.year) {
data.year.prev = false
if (this.minNav.year === this.viewModel.year && this.minNav.month >= this.viewModel.month) {
data.month.prev = false
}
}
if (this.maxNav !== void 0 && this.maxNav.year <= this.viewModel.year) {
data.year.next = false
if (this.maxNav.year === this.viewModel.year && this.maxNav.month <= this.viewModel.month) {
data.month.next = false
}
}
return data
},
daysMap () {
const map = {}
this.daysModel.forEach(entry => {
const hash = this.__getMonthHash(entry)
if (map[hash] === void 0) {
map[hash] = []
}
map[hash].push(entry.day)
})
return map
},
rangeMap () {
const map = {}
this.rangeModel.forEach(entry => {
const hashFrom = this.__getMonthHash(entry.from)
const hashTo = this.__getMonthHash(entry.to)
if (map[hashFrom] === void 0) {
map[hashFrom] = []
}
map[hashFrom].push({
from: entry.from.day,
to: hashFrom === hashTo ? entry.to.day : void 0,
range: entry
})
if (hashFrom < hashTo) {
let hash
const { year, month } = entry.from
const cur = month < 12
? { year, month: month + 1 }
: { year: year + 1, month: 1 }
while ((hash = this.__getMonthHash(cur)) <= hashTo) {
if (map[hash] === void 0) {
map[hash] = []
}
map[hash].push({
from: void 0,
to: hash === hashTo ? entry.to.day : void 0,
range: entry
})
cur.month++
if (cur.month > 12) {
cur.year++
cur.month = 1
}
}
}
})
return map
},
rangeView () {
if (this.editRange === void 0) {
return
}
const { init, initHash, final, finalHash } = this.editRange
const [ from, to ] = initHash <= finalHash
? [ init, final ]
: [ final, init ]
const fromHash = this.__getMonthHash(from)
const toHash = this.__getMonthHash(to)
if (fromHash !== this.viewMonthHash && toHash !== this.viewMonthHash) {
return
}
const view = {}
if (fromHash === this.viewMonthHash) {
view.from = from.day
view.includeFrom = true
}
else {
view.from = 1
}
if (toHash === this.viewMonthHash) {
view.to = to.day
view.includeTo = true
}
else {
view.to = this.daysInMonth
}
return view
},
viewMonthHash () {
return this.__getMonthHash(this.viewModel)
},
selectionDaysMap () {
const map = {}
if (this.options === void 0) {
for (let i = 1; i <= this.daysInMonth; i++) {
map[i] = true
}
return map
}
const fn = typeof this.options === 'function'
? this.options
: date => this.options.includes(date)
for (let i = 1; i <= this.daysInMonth; i++) {
const dayHash = this.viewMonthHash + '/' + pad(i)
map[i] = fn(dayHash)
}
return map
},
eventDaysMap () {
const map = {}
if (this.events === void 0) {
for (let i = 1; i <= this.daysInMonth; i++) {
map[i] = false
}
}
else {
const fn = typeof this.events === 'function'
? this.events
: date => this.events.includes(date)
for (let i = 1; i <= this.daysInMonth; i++) {
const dayHash = this.viewMonthHash + '/' + pad(i)
map[i] = fn(dayHash) === true && this.evtColor(dayHash)
}
}
return map
},
viewDays () {
let date, endDay
const { year, month } = this.viewModel
if (this.calendar !== 'persian') {
date = new Date(year, month - 1, 1)
endDay = (new Date(year, month - 1, 0)).getDate()
}
else {
const gDate = toGregorian(year, month, 1)
date = new Date(gDate.gy, gDate.gm - 1, gDate.gd)
let prevJM = month - 1
let prevJY = year
if (prevJM === 0) {
prevJM = 12
prevJY--
}
endDay = jalaaliMonthLength(prevJY, prevJM)
}
return {
days: date.getDay() - this.computedFirstDayOfWeek - 1,
endDay
}
},
days () {
const res = []
const { days, endDay } = this.viewDays
const len = days < 0 ? days + 7 : days
if (len < 6) {
for (let i = endDay - len; i <= endDay; i++) {
res.push({ i, fill: true })
}
}
const index = res.length
for (let i = 1; i <= this.daysInMonth; i++) {
const day = { i, event: this.eventDaysMap[i], classes: [] }
if (this.selectionDaysMap[i] === true) {
day.in = true
day.flat = true
}
res.push(day)
}
// if current view has days in model
if (this.daysMap[this.viewMonthHash] !== void 0) {
this.daysMap[this.viewMonthHash].forEach(day => {
const i = index + day - 1
Object.assign(res[i], {
selected: true,
unelevated: true,
flat: false,
color: this.computedColor,
textColor: this.computedTextColor
})
})
}
// if current view has ranges in model
if (this.rangeMap[this.viewMonthHash] !== void 0) {
this.rangeMap[this.viewMonthHash].forEach(entry => {
if (entry.from !== void 0) {
const from = index + entry.from - 1
const to = index + (entry.to || this.daysInMonth) - 1
for (let day = from; day <= to; day++) {
Object.assign(res[day], {
range: entry.range,
unelevated: true,
color: this.computedColor,
textColor: this.computedTextColor
})
}
Object.assign(res[from], {
rangeFrom: true,
flat: false
})
entry.to !== void 0 && Object.assign(res[to], {
rangeTo: true,
flat: false
})
}
else if (entry.to !== void 0) {
const to = index + entry.to - 1
for (let day = index; day <= to; day++) {
Object.assign(res[day], {
range: entry.range,
unelevated: true,
color: this.computedColor,
textColor: this.computedTextColor
})
}
Object.assign(res[to], {
flat: false,
rangeTo: true
})
}
else {
const to = index + this.daysInMonth - 1
for (let day = index; day <= to; day++) {
Object.assign(res[day], {
range: entry.range,
unelevated: true,
color: this.computedColor,
textColor: this.computedTextColor
})
}
}
})
}
if (this.rangeView !== void 0) {
const from = index + this.rangeView.from - 1
const to = index + this.rangeView.to - 1
for (let day = from; day <= to; day++) {
res[day].color = this.computedColor
res[day].editRange = true
}
if (this.rangeView.includeFrom === true) {
res[from].editRangeFrom = true
}
if (this.rangeView.includeTo === true) {
res[to].editRangeTo = true
}
}
if (this.viewModel.year === this.today.year && this.viewModel.month === this.today.month) {
res[index + this.today.day - 1].today = true
}
const left = res.length % 7
if (left > 0) {
const afterDays = 7 - left
for (let i = 1; i <= afterDays; i++) {
res.push({ i, fill: true })
}
}
res.forEach(day => {
let cls = `q-date__calendar-item `
if (day.fill === true) {
cls += 'q-date__calendar-item--fill'
}
else {
cls += `q-date__calendar-item--${day.in === true ? 'in' : 'out'}`
if (day.range !== void 0) {
cls += ` q-date__range${day.rangeTo === true ? '-to' : (day.rangeFrom === true ? '-from' : '')}`
}
if (day.editRange === true) {
cls += ` q-date__edit-range${day.editRangeFrom === true ? '-from' : ''}${day.editRangeTo === true ? '-to' : ''}`
}
if (day.range !== void 0 || day.editRange === true) {
cls += ` text-${day.color}`
}
}
day.classes = cls
})
return res
},
attrs () {
if (this.disable === true) {
return { 'aria-disabled': 'true' }
}
if (this.readonly === true) {
return { 'aria-readonly': 'true' }
}
}
},
methods: {
setToday () {
this.__toggleDate(this.today, this.__getMonthHash(this.today))
this.setCalendarTo(this.today.year, this.today.month)
},
setView (view) {
if (viewIsValid(view) === true) {
this.view = view
}
},
offsetCalendar (type, descending) {
if (['month', 'year'].includes(type)) {
this[`__goTo${type === 'month' ? 'Month' : 'Year'}`](
descending === true ? -1 : 1
)
}
},
setCalendarTo (year, month) {
this.view = 'Calendar'
this.__updateViewModel(year, month)
},
setEditingRange (from, to) {
if (this.range === false || !from) {
this.editRange = void 0
return
}
const init = Object.assign({ ...this.viewModel }, from)
const final = to !== void 0
? Object.assign({ ...this.viewModel }, to)
: init
this.editRange = {
init,
initHash: this.__getDayHash(init),
final,
finalHash: this.__getDayHash(final)
}
this.setCalendarTo(init.year, init.month)
},
__getMask () {
return this.calendar === 'persian' ? 'YYYY/MM/DD' : this.mask
},
__decodeString (date, mask, locale) {
return __splitDate(
date,
mask,
locale,
this.calendar,
{
hour: 0,
minute: 0,
second: 0,
millisecond: 0
}
)
},
__getViewModel (mask, locale) {
const model = Array.isArray(this.value) === true
? this.value
: (this.value ? [ this.value ] : [])
if (model.length === 0) {
return this.__getDefaultViewModel()
}
const decoded = this.__decodeString(
model[0].from !== void 0 ? model[0].from : model[0],
mask,
locale
)
return decoded.dateHash === null
? this.__getDefaultViewModel()
: decoded
},
__getDefaultViewModel () {
let year, month
if (this.defaultYearMonth !== void 0) {
const d = this.defaultYearMonth.split('/')
year = parseInt(d[0], 10)
month = parseInt(d[1], 10)
}
else {
// may come from data() where computed
// props are not yet available
const d = this.today !== void 0
? this.today
: this.__getCurrentDate()
year = d.year
month = d.month
}
return {
year,
month,
day: 1,
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
dateHash: year + '/' + pad(month) + '/01'
}
},
__getHeader (h) {
if (this.minimal === true) { return }
return h('div', {
staticClass: 'q-date__header',
class: this.headerClass
}, [
h('div', {
staticClass: 'relative-position'
}, [
h('transition', {
props: {
name: 'q-transition--fade'
}
}, [
h('div', {
key: 'h-yr-' + this.headerSubtitle,
staticClass: 'q-date__header-subtitle q-date__header-link',
class: this.view === 'Years' ? 'q-date__header-link--active' : 'cursor-pointer',
attrs: { tabindex: this.computedTabindex },
on: cache(this, 'vY', {
click: () => { this.view = 'Years' },
keyup: e => { e.keyCode === 13 && (this.view = 'Years') }
})
}, [ this.headerSubtitle ])
])
]),
h('div', {
staticClass: 'q-date__header-title relative-position flex no-wrap'
}, [
h('div', {
staticClass: 'relative-position col'
}, [
h('transition', {
props: {
name: 'q-transition--fade'
}
}, [
h('div', {
key: 'h-sub' + this.headerTitle,
staticClass: 'q-date__header-title-label q-date__header-link',
class: this.view === 'Calendar' ? 'q-date__header-link--active' : 'cursor-pointer',
attrs: { tabindex: this.computedTabindex },
on: cache(this, 'vC', {
click: () => { this.view = 'Calendar' },
keyup: e => { e.keyCode === 13 && (this.view = 'Calendar') }
})
}, [ this.headerTitle ])
])
]),
this.todayBtn === true ? h(QBtn, {
staticClass: 'q-date__header-today self-start',
props: {
icon: this.$q.iconSet.datetime.today,
flat: true,
size: 'sm',
round: true,
tabindex: this.computedTabindex
},
on: cache(this, 'today', { click: this.setToday })
}) : null
])
])
},
__getNavigation (h, { label, view, key, dir, goTo, boundaries, cls }) {
return [
h('div', {
staticClass: 'row items-center q-date__arrow'
}, [
h(QBtn, {
props: {
round: true,
dense: true,
size: 'sm',
flat: true,
icon: this.dateArrow[0],
tabindex: this.computedTabindex,
disable: boundaries.prev === false
},
on: cache(this, 'go-#' + view, { click () { goTo(-1) } })
})
]),
h('div', {
staticClass: 'relative-position overflow-hidden flex flex-center' + cls
}, [
h('transition', {
props: {
name: 'q-transition--jump-' + dir
}
}, [
h('div', { key }, [
h(QBtn, {
props: {
flat: true,
dense: true,
noCaps: true,
label,
tabindex: this.computedTabindex
},
on: cache(this, 'view#' + view, { click: () => { this.view = view } })
})
])
])
]),
h('div', {
staticClass: 'row items-center q-date__arrow'
}, [
h(QBtn, {
props: {
round: true,
dense: true,
size: 'sm',
flat: true,
icon: this.dateArrow[1],
tabindex: this.computedTabindex,
disable: boundaries.next === false
},
on: cache(this, 'go+#' + view, { click () { goTo(1) } })
})
])
]
},
__getCalendarView (h) {
return [
h('div', {
key: 'calendar-view',
staticClass: 'q-date__view q-date__calendar'
}, [
h('div', {
staticClass: 'q-date__navigation row items-center no-wrap'
}, this.__getNavigation(h, {
label: this.innerLocale.months[ this.viewModel.month - 1 ],
view: 'Months',
key: this.viewModel.month,
dir: this.monthDirection,
goTo: this.__goToMonth,
boundaries: this.navBoundaries.month,
cls: ' col'
}).concat(this.__getNavigation(h, {
label: this.viewModel.year,
view: 'Years',
key: this.viewModel.year,
dir: this.yearDirection,
goTo: this.__goToYear,
boundaries: this.navBoundaries.year,
cls: ''
}))),
h('div', {
staticClass: 'q-date__calendar-weekdays row items-center no-wrap'
}, this.daysOfWeek.map(day => h('div', { staticClass: 'q-date__calendar-item' }, [ h('div', [ day ]) ]))),
h('div', {
staticClass: 'q-date__calendar-days-container relative-position overflow-hidden'
}, [
h('transition', {
props: {
name: 'q-transition--slide-' + this.monthDirection
}
}, [
h('div', {
key: this.viewMonthHash,
staticClass: 'q-date__calendar-days fit'
}, this.days.map(day => h('div', { staticClass: day.classes }, [
day.in === true
? h(QBtn, {
staticClass: day.today === true ? 'q-date__today' : null,
props: {
dense: true,
flat: day.flat,
unelevated: day.unelevated,
color: day.color,
textColor: day.textColor,
label: day.i,
tabindex: this.computedTabindex
},
on: cache(this, 'day#' + day.i, {
click: () => { this.__onDayClick(day.i) },
mouseover: () => { this.__onDayMouseover(day.i) }
})
}, day.event !== false ? [
h('div', { staticClass: 'q-date__event bg-' + day.event })
] : null)
: h('div', [ day.i ])
])))
])
])
])
]
},
__getMonthsView (h) {
const currentYear = this.viewModel.year === this.today.year
const isDisabled = month => {
return (
(this.minNav !== void 0 && this.viewModel.year === this.minNav.year && this.minNav.month > month) ||
(this.maxNav !== void 0 && this.viewModel.year === this.maxNav.year && this.maxNav.month < month)
)
}
const content = this.innerLocale.monthsShort.map((month, i) => {
const active = this.viewModel.month === i + 1
return h('div', {
staticClass: 'q-date__months-item flex flex-center'
}, [
h(QBtn, {
staticClass: currentYear === true && this.today.month === i + 1 ? 'q-date__today' : null,
props: {
flat: active !== true,
label: month,
unelevated: active,
color: active === true ? this.computedColor : null,
textColor: active === true ? this.computedTextColor : null,
tabindex: this.computedTabindex,
disable: isDisabled(i + 1)
},
on: cache(this, 'month#' + i, { click: () => { this.__setMonth(i + 1) } })
})
])
})
this.yearsInMonthView === true && content.unshift(
h('div', { staticClass: 'row no-wrap full-width' }, [
this.__getNavigation(h, {
label: this.viewModel.year,
view: 'Years',
key: this.viewModel.year,
dir: this.yearDirection,
goTo: this.__goToYear,
boundaries: this.navBoundaries.year,
cls: ' col'
})
])
)
return h('div', {
key: 'months-view',
staticClass: 'q-date__view q-date__months flex flex-center'
}, content)
},
__getYearsView (h) {
const
start = this.startYear,
stop = start + yearsInterval,
years = []
const isDisabled = year => {
return (
(this.minNav !== void 0 && this.minNav.year > year) ||
(this.maxNav !== void 0 && this.maxNav.year < year)
)
}
for (let i = start; i <= stop; i++) {
const active = this.viewModel.year === i
years.push(
h('div', {
staticClass: 'q-date__years-item flex flex-center'
}, [
h(QBtn, {
key: 'yr' + i,
staticClass: this.today.year === i ? 'q-date__today' : null,
props: {
flat: !active,
label: i,
dense: true,
unelevated: active,
color: active === true ? this.computedColor : null,
textColor: active === true ? this.computedTextColor : null,
tabindex: this.computedTabindex,
disable: isDisabled(i)
},
on: cache(this, 'yr#' + i, { click: () => { this.__setYear(i) } })
})
])
)
}
return h('div', {
staticClass: 'q-date__view q-date__years flex flex-center'
}, [
h('div', {
staticClass: 'col-auto'
}, [
h(QBtn, {
props: {
round: true,
dense: true,
flat: true,
icon: this.dateArrow[0],
tabindex: this.computedTabindex,
disable: isDisabled(start)
},
on: cache(this, 'y-', { click: () => { this.startYear -= yearsInterval } })
})
]),
h('div', {
staticClass: 'q-date__years-content col self-stretch row items-center'
}, years),
h('div', {
staticClass: 'col-auto'
}, [
h(QBtn, {
props: {
round: true,
dense: true,
flat: true,
icon: this.dateArrow[1],
tabindex: this.computedTabindex,
disable: isDisabled(stop)
},
on: cache(this, 'y+', { click: () => { this.startYear += yearsInterval } })
})
])
])
},
__goToMonth (offset) {
let year = this.viewModel.year
let month = Number(this.viewModel.month) + offset
if (month === 13) {
month = 1
year++
}
else if (month === 0) {
month = 12
year--
}
this.__updateViewModel(year, month)
this.isImmediate === true && this.__emitImmediately('month')
},
__goToYear (offset) {
const year = Number(this.viewModel.year) + offset
this.__updateViewModel(year, this.viewModel.month)
this.isImmediate === true && this.__emitImmediately('year')
},
__setYear (year) {
this.__updateViewModel(year, this.viewModel.month)
this.view = this.defaultView === 'Years' ? 'Months' : 'Calendar'
this.isImmediate === true && this.__emitImmediately('year')
},
__setMonth (month) {
this.__updateViewModel(this.viewModel.year, month)
this.view = 'Calendar'
this.isImmediate === true && this.__emitImmediately('month')
},
__getMonthHash (date) {
return date.year + '/' + pad(date.month)
},
__toggleDate (date, monthHash) {
const month = this.daysMap[monthHash]
const fn = month !== void 0 && month.includes(date.day) === true
? this.__removeFromModel
: this.__addToModel
fn(date)
},
__getShortDate (date) {
return { year: date.year, month: date.month, day: date.day }
},
__onDayClick (dayIndex) {
const day = { ...this.viewModel, day: dayIndex }
if (this.range === false) {
this.__toggleDate(day, this.viewMonthHash)
return
}
if (this.editRange === void 0) {
const dayProps = this.days.find(day => day.fill !== true && day.i === dayIndex)
if (dayProps.range !== void 0) {
this.__removeFromModel({ target: day, from: dayProps.range.from, to: dayProps.range.to })
return
}
if (dayProps.selected === true) {
this.__removeFromModel(day)
return
}
const initHash = this.__getDayHash(day)
this.editRange = {
init: day,
initHash,
final: day,
finalHash: initHash
}
this.$emit('range-start', this.__getShortDate(day))
}
else {
const
initHash = this.editRange.initHash,
finalHash = this.__getDayHash(day),
payload = initHash <= finalHash
? { from: this.editRange.init, to: day }
: { from: day, to: this.editRange.init }
this.editRange = void 0
this.__addToModel(initHash === finalHash ? day : { target: day, ...payload })
this.$emit('range-end', {
from: this.__getShortDate(payload.from),
to: this.__getShortDate(payload.to)
})
}
},
__onDayMouseover (dayIndex) {
if (this.editRange !== void 0) {
const final = { ...this.viewModel, day: dayIndex }
Object.assign(this.editRange, {
final,
finalHash: this.__getDayHash(final)
})
}
},
__updateViewModel (year, month) {
if (this.minNav !== void 0 && year <= this.minNav.year) {
year = this.minNav.year
if (month < this.minNav.month) {
month = this.minNav.month
}
}
if (this.maxNav !== void 0 && year >= this.maxNav.year) {
year = this.maxNav.year
if (month > this.maxNav.month) {
month = this.maxNav.month
}
}
const newHash = year + '/' + pad(month) + '/01'
if (newHash !== this.viewModel.dateHash) {
this.monthDirection = (this.viewModel.dateHash < newHash) === (this.$q.lang.rtl !== true) ? 'left' : 'right'
if (year !== this.viewModel.year) {
this.yearDirection = this.monthDirection
}
this.$nextTick(() => {
this.startYear = year - year % yearsInterval - (year < 0 ? yearsInterval : 0)
Object.assign(this.viewModel, {
year,
month,
day: 1,
dateHash: newHash
})
})
}
},
__emitValue (val, action, date) {
const value = val !== null && val.length === 1 && this.multiple === false
? val[0]
: val
this.lastEmitValue = value
const { reason, details } = this.__getEmitParams(action, date)
this.$emit('input', value, reason, details)
},
__emitImmediately (reason) {
const date = this.daysModel[0] !== void 0 && this.daysModel[0].dateHash !== null
? this.daysModel[0]
: { ...this.viewModel } // inherit day, hours, minutes, milliseconds...
// nextTick required because of animation delay in viewModel
this.$nextTick(() => {
date.year = this.viewModel.year
date.month = this.viewModel.month
const maxDay = this.calendar !== 'persian'
? (new Date(date.year, date.month, 0)).getDate()
: jalaaliMonthLength(date.year, date.month)
date.day = Math.min(Math.max(1, date.day), maxDay)
const value = this.__encodeEntry(date)
this.lastEmitValue = value
const { details } = this.__getEmitParams('', date)
this.$emit('input', value, reason, details)
})
},
__getEmitParams (action, date) {
return date.from !== void 0
? {
reason: `${action}-range`,
details: {
...this.__getShortDate(date.target),
from: this.__getShortDate(date.from),
to: this.__getShortDate(date.to),
changed: true // TODO remove in v2; legacy purposes
}
}
: {
reason: `${action}-day`,
details: {
...this.__getShortDate(date),
changed: true // TODO remove in v2; legacy purposes
}
}
},
__encodeEntry (date, mask, locale) {
return date.from !== void 0
? { from: this.encodeObjectFn(date.from, mask, locale), to: this.encodeObjectFn(date.to, mask, locale) }
: this.encodeObjectFn(date, mask, locale)
},
__addToModel (date) {
let value
if (this.multiple === true) {
if (date.from !== void 0) {
// we also need to filter out intersections
const fromHash = this.__getDayHash(date.from)
const toHash = this.__getDayHash(date.to)
const days = this.daysModel
.filter(day => day.dateHash < fromHash || day.dateHash > toHash)
const ranges = this.rangeModel
.filter(({ from, to }) => to.dateHash < fromHash || from.dateHash > toHash)
value = days.concat(ranges).concat(date).map(entry => this.__encodeEntry(entry))
}
else {
const model = this.normalizedModel.slice()
model.push(this.__encodeEntry(date))
value = model
}
}
else {
value = this.__encodeEntry(date)
}
this.__emitValue(value, 'add', date)
},
__removeFromModel (date) {
if (this.noUnset === true) {
return
}
let model = null
if (this.multiple === true && Array.isArray(this.value) === true) {
const val = this.__encodeEntry(date)
if (date.from !== void 0) {
model = this.value.filter(
date => date.from !== void 0
? (date.from !== val.from && date.to !== val.to)
: true
)
}
else {
model = this.value.filter(date => date !== val)
}
if (model.length === 0) {
model = null
}
}
this.__emitValue(model, 'remove', date)
},
__updateValue (mask, locale, reason) {
const model = this.daysModel
.concat(this.rangeModel)
.map(entry => this.__encodeEntry(entry, mask, locale))
.filter(entry => {
return entry.from !== void 0
? entry.from.dateHash !== null && entry.to.dateHash !== null
: entry.dateHash !== null
})
this.$emit('input', (this.multiple === true ? model : model[0]) || null, reason)
}
},
render (h) {
const content = [
h('div', {
staticClass: 'q-date__content col relative-position'
}, [
h('transition', {
props: { name: 'q-transition--fade' }
}, [
this[`__get${this.view}View`](h)
])
])
]
const def = slot(this, 'default')
def !== void 0 && content.push(
h('div', { staticClass: 'q-date__actions' }, def)
)
if (this.name !== void 0 && this.disable !== true) {
this.__injectFormInput(content, 'push')
}
return h('div', {
class: this.classes,
attrs: this.attrs,
on: { ...this.qListeners }
}, [
this.__getHeader(h),
h('div', {
staticClass: 'q-date__main col column',
attrs: { tabindex: -1 },
ref: 'blurTarget'
}, content)
])
}
})