UNPKG

quasar

Version:

Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time

1,468 lines (1,225 loc) 42.2 kB
import { h, ref, computed, watch, Transition, nextTick, getCurrentInstance } from 'vue' import QBtn from '../btn/QBtn.js' import useDark, { useDarkProps } from '../../composables/private/use-dark.js' import useCache from '../../composables/private/use-cache.js' import { useFormProps, useFormAttrs, useFormInject } from '../../composables/private/use-form.js' import useDatetime, { useDatetimeProps, useDatetimeEmits, getDayHash } from './use-datetime.js' import { createComponent } from '../../utils/private/create.js' import { hSlot } from '../../utils/private/render.js' import { formatDate, __splitDate, getDateDiff } from '../../utils/date.js' import { pad } from '../../utils/format.js' import { jalaaliMonthLength, toGregorian } from '../../utils/private/date-persian.js' import { isObject } from '../../utils/is.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 ' function getMonthHash (date) { return date.year + '/' + pad(date.month) } export default createComponent({ name: 'QDate', props: { ...useDatetimeProps, ...useFormProps, ...useDarkProps, 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 } }, emits: [ ...useDatetimeEmits, 'rangeStart', 'rangeEnd', 'navigation' ], setup (props, { slots, emit }) { const { proxy } = getCurrentInstance() const { $q } = proxy const isDark = useDark(props, $q) const { getCache } = useCache() const { tabindex, headerClass, getLocale, getCurrentDate } = useDatetime(props, $q) let lastEmitValue const formAttrs = useFormAttrs(props) const injectFormInput = useFormInject(formAttrs) const blurTargetRef = ref(null) const innerMask = ref(getMask()) const innerLocale = ref(getLocale()) const mask = computed(() => getMask()) const locale = computed(() => getLocale()) const today = computed(() => getCurrentDate()) // model of current calendar view: const viewModel = ref(getViewModel(innerMask.value, innerLocale.value)) const view = ref(props.defaultView) const direction = $q.lang.rtl === true ? 'right' : 'left' const monthDirection = ref(direction.value) const yearDirection = ref(direction.value) const year = viewModel.value.year const startYear = ref(year - (year % yearsInterval) - (year < 0 ? yearsInterval : 0)) const editRange = ref(null) const classes = computed(() => { const type = props.landscape === true ? 'landscape' : 'portrait' return `q-date q-date--${ type } q-date--${ type }-${ props.minimal === true ? 'minimal' : 'standard' }` + (isDark.value === true ? ' q-date--dark q-dark' : '') + (props.bordered === true ? ' q-date--bordered' : '') + (props.square === true ? ' q-date--square no-border-radius' : '') + (props.flat === true ? ' q-date--flat no-shadow' : '') + (props.disable === true ? ' disabled' : (props.readonly === true ? ' q-date--readonly' : '')) }) const computedColor = computed(() => { return props.color || 'primary' }) const computedTextColor = computed(() => { return props.textColor || 'white' }) const isImmediate = computed(() => props.emitImmediately === true && props.multiple !== true && props.range !== true ) const normalizedModel = computed(() => ( Array.isArray(props.modelValue) === true ? props.modelValue : (props.modelValue !== null && props.modelValue !== void 0 ? [ props.modelValue ] : []) )) const daysModel = computed(() => normalizedModel.value .filter(date => typeof date === 'string') .map(date => decodeString(date, innerMask.value, innerLocale.value)) .filter(date => date.dateHash !== null && date.day !== null && date.month !== null && date.year !== null ) ) const rangeModel = computed(() => { const fn = date => decodeString(date, innerMask.value, innerLocale.value) return normalizedModel.value .filter(date => isObject(date) === true && 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) }) const getNativeDateFn = computed(() => ( props.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) } )) const encodeObjectFn = computed(() => ( props.calendar === 'persian' ? 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 ? innerMask.value : mask, locale === void 0 ? innerLocale.value : locale, date.year, date.timezoneOffset ) )) const daysInModel = computed(() => daysModel.value.length + rangeModel.value.reduce( (acc, range) => acc + 1 + getDateDiff( getNativeDateFn.value(range.to), getNativeDateFn.value(range.from) ), 0 ) ) const headerTitle = computed(() => { if (props.title !== void 0 && props.title !== null && props.title.length > 0) { return props.title } if (editRange.value !== null) { const model = editRange.value.init const date = getNativeDateFn.value(model) return innerLocale.value.daysShort[ date.getDay() ] + ', ' + innerLocale.value.monthsShort[ model.month - 1 ] + ' ' + model.day + lineStr + '?' } if (daysInModel.value === 0) { return lineStr } if (daysInModel.value > 1) { return `${ daysInModel.value } ${ innerLocale.value.pluralDay }` } const model = daysModel.value[ 0 ] const date = getNativeDateFn.value(model) if (isNaN(date.valueOf()) === true) { return lineStr } if (innerLocale.value.headerTitle !== void 0) { return innerLocale.value.headerTitle(date, model) } return innerLocale.value.daysShort[ date.getDay() ] + ', ' + innerLocale.value.monthsShort[ model.month - 1 ] + ' ' + model.day }) const minSelectedModel = computed(() => { const model = daysModel.value.concat(rangeModel.value.map(range => range.from)) .sort((a, b) => a.year - b.year || a.month - b.month) return model[ 0 ] }) const maxSelectedModel = computed(() => { const model = daysModel.value.concat(rangeModel.value.map(range => range.to)) .sort((a, b) => b.year - a.year || b.month - a.month) return model[ 0 ] }) const headerSubtitle = computed(() => { if (props.subtitle !== void 0 && props.subtitle !== null && props.subtitle.length > 0) { return props.subtitle } if (daysInModel.value === 0) { return lineStr } if (daysInModel.value > 1) { const from = minSelectedModel.value const to = maxSelectedModel.value const month = innerLocale.value.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 daysModel.value[ 0 ].year }) const dateArrow = computed(() => { const val = [ $q.iconSet.datetime.arrowLeft, $q.iconSet.datetime.arrowRight ] return $q.lang.rtl === true ? val.reverse() : val }) const computedFirstDayOfWeek = computed(() => ( props.firstDayOfWeek !== void 0 ? Number(props.firstDayOfWeek) : innerLocale.value.firstDayOfWeek )) const daysOfWeek = computed(() => { const days = innerLocale.value.daysShort, first = computedFirstDayOfWeek.value return first > 0 ? days.slice(first, 7).concat(days.slice(0, first)) : days }) const daysInMonth = computed(() => { const date = viewModel.value return props.calendar !== 'persian' ? (new Date(date.year, date.month, 0)).getDate() : jalaaliMonthLength(date.year, date.month) }) const evtColor = computed(() => ( typeof props.eventColor === 'function' ? props.eventColor : () => props.eventColor )) const minNav = computed(() => { if (props.navigationMinYearMonth === void 0) { return null } const data = props.navigationMinYearMonth.split('/') return { year: parseInt(data[ 0 ], 10), month: parseInt(data[ 1 ], 10) } }) const maxNav = computed(() => { if (props.navigationMaxYearMonth === void 0) { return null } const data = props.navigationMaxYearMonth.split('/') return { year: parseInt(data[ 0 ], 10), month: parseInt(data[ 1 ], 10) } }) const navBoundaries = computed(() => { const data = { month: { prev: true, next: true }, year: { prev: true, next: true } } if (minNav.value !== null && minNav.value.year >= viewModel.value.year) { data.year.prev = false if (minNav.value.year === viewModel.value.year && minNav.value.month >= viewModel.value.month) { data.month.prev = false } } if (maxNav.value !== null && maxNav.value.year <= viewModel.value.year) { data.year.next = false if (maxNav.value.year === viewModel.value.year && maxNav.value.month <= viewModel.value.month) { data.month.next = false } } return data }) const daysMap = computed(() => { const map = {} daysModel.value.forEach(entry => { const hash = getMonthHash(entry) if (map[ hash ] === void 0) { map[ hash ] = [] } map[ hash ].push(entry.day) }) return map }) const rangeMap = computed(() => { const map = {} rangeModel.value.forEach(entry => { const hashFrom = getMonthHash(entry.from) const hashTo = 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 = 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 }) const rangeView = computed(() => { if (editRange.value === null) { return } const { init, initHash, final, finalHash } = editRange.value const [ from, to ] = initHash <= finalHash ? [ init, final ] : [ final, init ] const fromHash = getMonthHash(from) const toHash = getMonthHash(to) if (fromHash !== viewMonthHash.value && toHash !== viewMonthHash.value) { return } const view = {} if (fromHash === viewMonthHash.value) { view.from = from.day view.includeFrom = true } else { view.from = 1 } if (toHash === viewMonthHash.value) { view.to = to.day view.includeTo = true } else { view.to = daysInMonth.value } return view }) const viewMonthHash = computed(() => getMonthHash(viewModel.value)) const selectionDaysMap = computed(() => { const map = {} if (props.options === void 0) { for (let i = 1; i <= daysInMonth.value; i++) { map[ i ] = true } return map } const fn = typeof props.options === 'function' ? props.options : date => props.options.includes(date) for (let i = 1; i <= daysInMonth.value; i++) { const dayHash = viewMonthHash.value + '/' + pad(i) map[ i ] = fn(dayHash) } return map }) const eventDaysMap = computed(() => { const map = {} if (props.events === void 0) { for (let i = 1; i <= daysInMonth.value; i++) { map[ i ] = false } } else { const fn = typeof props.events === 'function' ? props.events : date => props.events.includes(date) for (let i = 1; i <= daysInMonth.value; i++) { const dayHash = viewMonthHash.value + '/' + pad(i) map[ i ] = fn(dayHash) === true && evtColor.value(dayHash) } } return map }) const viewDays = computed(() => { let date, endDay const { year, month } = viewModel.value if (props.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() - computedFirstDayOfWeek.value - 1, endDay } }) const days = computed(() => { const res = [] const { days, endDay } = viewDays.value 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 <= daysInMonth.value; i++) { const day = { i, event: eventDaysMap.value[ i ], classes: [] } if (selectionDaysMap.value[ i ] === true) { day.in = true day.flat = true } res.push(day) } // if current view has days in model if (daysMap.value[ viewMonthHash.value ] !== void 0) { daysMap.value[ viewMonthHash.value ].forEach(day => { const i = index + day - 1 Object.assign(res[ i ], { selected: true, unelevated: true, flat: false, color: computedColor.value, textColor: computedTextColor.value }) }) } // if current view has ranges in model if (rangeMap.value[ viewMonthHash.value ] !== void 0) { rangeMap.value[ viewMonthHash.value ].forEach(entry => { if (entry.from !== void 0) { const from = index + entry.from - 1 const to = index + (entry.to || daysInMonth.value) - 1 for (let day = from; day <= to; day++) { Object.assign(res[ day ], { range: entry.range, unelevated: true, color: computedColor.value, textColor: computedTextColor.value }) } 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: computedColor.value, textColor: computedTextColor.value }) } Object.assign(res[ to ], { flat: false, rangeTo: true }) } else { const to = index + daysInMonth.value - 1 for (let day = index; day <= to; day++) { Object.assign(res[ day ], { range: entry.range, unelevated: true, color: computedColor.value, textColor: computedTextColor.value }) } } }) } if (rangeView.value !== void 0) { const from = index + rangeView.value.from - 1 const to = index + rangeView.value.to - 1 for (let day = from; day <= to; day++) { res[ day ].color = computedColor.value res[ day ].editRange = true } if (rangeView.value.includeFrom === true) { res[ from ].editRangeFrom = true } if (rangeView.value.includeTo === true) { res[ to ].editRangeTo = true } } if (viewModel.value.year === today.value.year && viewModel.value.month === today.value.month) { res[ index + today.value.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 }) const attributes = computed(() => ( props.disable === true ? { 'aria-disabled': 'true' } : (props.readonly === true ? { 'aria-readonly': 'true' } : {}) )) watch(() => props.modelValue, v => { if (lastEmitValue === v) { lastEmitValue = 0 } else { const model = getViewModel(innerMask.value, innerLocale.value) updateViewModel(model.year, model.month, model) } }) watch(view, () => { if (blurTargetRef.value !== null && proxy.$el.contains(document.activeElement) === true) { blurTargetRef.value.focus() } }) watch(() => viewModel.value.year + '|' + viewModel.value.month, () => { emit('navigation', { year: viewModel.value.year, month: viewModel.value.month }) }) watch(mask, val => { updateValue(val, innerLocale.value, 'mask') innerMask.value = val }) watch(locale, val => { updateValue(innerMask.value, val, 'locale') innerLocale.value = val }) function setToday () { const date = today.value const month = daysMap.value[ getMonthHash(date) ] if (month === void 0 || month.includes(date.day) === false) { addToModel(date) } setCalendarTo(date.year, date.month) } function setView (viewMode) { if (viewIsValid(viewMode) === true) { view.value = viewMode } } function offsetCalendar (type, descending) { if ([ 'month', 'year' ].includes(type)) { const fn = type === 'month' ? goToMonth : goToYear fn(descending === true ? -1 : 1) } } function setCalendarTo (year, month) { view.value = 'Calendar' updateViewModel(year, month) } function setEditingRange (from, to) { if (props.range === false || !from) { editRange.value = null return } const init = Object.assign({ ...viewModel.value }, from) const final = to !== void 0 ? Object.assign({ ...viewModel.value }, to) : init editRange.value = { init, initHash: getDayHash(init), final, finalHash: getDayHash(final) } setCalendarTo(init.year, init.month) } function getMask () { return props.calendar === 'persian' ? 'YYYY/MM/DD' : props.mask } function decodeString (date, mask, locale) { return __splitDate( date, mask, locale, props.calendar, { hour: 0, minute: 0, second: 0, millisecond: 0 } ) } function getViewModel (mask, locale) { const model = Array.isArray(props.modelValue) === true ? props.modelValue : (props.modelValue ? [ props.modelValue ] : []) if (model.length === 0) { return getDefaultViewModel() } const target = model[ model.length - 1 ] const decoded = decodeString( target.from !== void 0 ? target.from : target, mask, locale ) return decoded.dateHash === null ? getDefaultViewModel() : decoded } function getDefaultViewModel () { let year, month if (props.defaultYearMonth !== void 0) { const d = props.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 = today.value !== void 0 ? today.value : 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' } } function goToMonth (offset) { let year = viewModel.value.year let month = Number(viewModel.value.month) + offset if (month === 13) { month = 1 year++ } else if (month === 0) { month = 12 year-- } updateViewModel(year, month) isImmediate.value === true && emitImmediately('month') } function goToYear (offset) { const year = Number(viewModel.value.year) + offset updateViewModel(year, viewModel.value.month) isImmediate.value === true && emitImmediately('year') } function setYear (year) { updateViewModel(year, viewModel.value.month) view.value = props.defaultView === 'Years' ? 'Months' : 'Calendar' isImmediate.value === true && emitImmediately('year') } function setMonth (month) { updateViewModel(viewModel.value.year, month) view.value = 'Calendar' isImmediate.value === true && emitImmediately('month') } function toggleDate (date, monthHash) { const month = daysMap.value[ monthHash ] const fn = month !== void 0 && month.includes(date.day) === true ? removeFromModel : addToModel fn(date) } function getShortDate (date) { return { year: date.year, month: date.month, day: date.day } } function updateViewModel (year, month, time) { if (minNav.value !== null && year <= minNav.value.year) { year = minNav.value.year if (month < minNav.value.month) { month = minNav.value.month } } if (maxNav.value !== null && year >= maxNav.value.year) { year = maxNav.value.year if (month > maxNav.value.month) { month = maxNav.value.month } } if (time !== void 0) { const { hour, minute, second, millisecond, timezoneOffset, timeHash } = time Object.assign(viewModel.value, { hour, minute, second, millisecond, timezoneOffset, timeHash }) } const newHash = year + '/' + pad(month) + '/01' if (newHash !== viewModel.value.dateHash) { monthDirection.value = (viewModel.value.dateHash < newHash) === ($q.lang.rtl !== true) ? 'left' : 'right' if (year !== viewModel.value.year) { yearDirection.value = monthDirection.value } nextTick(() => { startYear.value = year - year % yearsInterval - (year < 0 ? yearsInterval : 0) Object.assign(viewModel.value, { year, month, day: 1, dateHash: newHash }) }) } } function emitValue (val, action, date) { const value = val !== null && val.length === 1 && props.multiple === false ? val[ 0 ] : val lastEmitValue = value const { reason, details } = getEmitParams(action, date) emit('update:modelValue', value, reason, details) } function emitImmediately (reason) { const date = daysModel.value[ 0 ] !== void 0 && daysModel.value[ 0 ].dateHash !== null ? { ...daysModel.value[ 0 ] } : { ...viewModel.value } // inherit day, hours, minutes, milliseconds... // nextTick required because of animation delay in viewModel nextTick(() => { date.year = viewModel.value.year date.month = viewModel.value.month const maxDay = props.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 = encodeEntry(date) lastEmitValue = value const { details } = getEmitParams('', date) emit('update:modelValue', value, reason, details) }) } function getEmitParams (action, date) { return date.from !== void 0 ? { reason: `${ action }-range`, details: { ...getShortDate(date.target), from: getShortDate(date.from), to: getShortDate(date.to) } } : { reason: `${ action }-day`, details: getShortDate(date) } } function encodeEntry (date, mask, locale) { return date.from !== void 0 ? { from: encodeObjectFn.value(date.from, mask, locale), to: encodeObjectFn.value(date.to, mask, locale) } : encodeObjectFn.value(date, mask, locale) } function addToModel (date) { let value if (props.multiple === true) { if (date.from !== void 0) { // we also need to filter out intersections const fromHash = getDayHash(date.from) const toHash = getDayHash(date.to) const days = daysModel.value .filter(day => day.dateHash < fromHash || day.dateHash > toHash) const ranges = rangeModel.value .filter(({ from, to }) => to.dateHash < fromHash || from.dateHash > toHash) value = days.concat(ranges).concat(date).map(entry => encodeEntry(entry)) } else { const model = normalizedModel.value.slice() model.push(encodeEntry(date)) value = model } } else { value = encodeEntry(date) } emitValue(value, 'add', date) } function removeFromModel (date) { if (props.noUnset === true) { return } let model = null if (props.multiple === true && Array.isArray(props.modelValue) === true) { const val = encodeEntry(date) if (date.from !== void 0) { model = props.modelValue.filter( date => ( date.from !== void 0 ? (date.from !== val.from && date.to !== val.to) : true ) ) } else { model = props.modelValue.filter(date => date !== val) } if (model.length === 0) { model = null } } emitValue(model, 'remove', date) } function updateValue (mask, locale, reason) { const model = daysModel.value .concat(rangeModel.value) .map(entry => encodeEntry(entry, mask, locale)) .filter(entry => { return entry.from !== void 0 ? entry.from.dateHash !== null && entry.to.dateHash !== null : entry.dateHash !== null }) emit('update:modelValue', (props.multiple === true ? model : model[ 0 ]) || null, reason) } function getHeader () { if (props.minimal === true) { return } return h('div', { class: 'q-date__header ' + headerClass.value }, [ h('div', { class: 'relative-position' }, [ h(Transition, { name: 'q-transition--fade' }, () => h('div', { key: 'h-yr-' + headerSubtitle.value, class: 'q-date__header-subtitle q-date__header-link ' + (view.value === 'Years' ? 'q-date__header-link--active' : 'cursor-pointer'), tabindex: tabindex.value, ...getCache('vY', { onClick () { view.value = 'Years' }, onKeyup (e) { e.keyCode === 13 && (view.value = 'Years') } }) }, [ headerSubtitle.value ])) ]), h('div', { class: 'q-date__header-title relative-position flex no-wrap' }, [ h('div', { class: 'relative-position col' }, [ h(Transition, { name: 'q-transition--fade' }, () => h('div', { key: 'h-sub' + headerTitle.value, class: 'q-date__header-title-label q-date__header-link ' + (view.value === 'Calendar' ? 'q-date__header-link--active' : 'cursor-pointer'), tabindex: tabindex.value, ...getCache('vC', { onClick () { view.value = 'Calendar' }, onKeyup (e) { e.keyCode === 13 && (view.value = 'Calendar') } }) }, [ headerTitle.value ])) ]), props.todayBtn === true ? h(QBtn, { class: 'q-date__header-today self-start', icon: $q.iconSet.datetime.today, flat: true, size: 'sm', round: true, tabindex: tabindex.value, onClick: setToday }) : null ]) ]) } function getNavigation ({ label, type, key, dir, goTo, boundaries, cls }) { return [ h('div', { class: 'row items-center q-date__arrow' }, [ h(QBtn, { round: true, dense: true, size: 'sm', flat: true, icon: dateArrow.value[ 0 ], tabindex: tabindex.value, disable: boundaries.prev === false, ...getCache('go-#' + type, { onClick () { goTo(-1) } }) }) ]), h('div', { class: 'relative-position overflow-hidden flex flex-center' + cls }, [ h(Transition, { name: 'q-transition--jump-' + dir }, () => h('div', { key }, [ h(QBtn, { flat: true, dense: true, noCaps: true, label, tabindex: tabindex.value, ...getCache('view#' + type, { onClick: () => { view.value = type } }) }) ])) ]), h('div', { class: 'row items-center q-date__arrow' }, [ h(QBtn, { round: true, dense: true, size: 'sm', flat: true, icon: dateArrow.value[ 1 ], tabindex: tabindex.value, disable: boundaries.next === false, ...getCache('go+#' + type, { onClick () { goTo(1) } }) }) ]) ] } const renderViews = { Calendar: () => ([ h('div', { key: 'calendar-view', class: 'q-date__view q-date__calendar' }, [ h('div', { class: 'q-date__navigation row items-center no-wrap' }, getNavigation({ label: innerLocale.value.months[ viewModel.value.month - 1 ], type: 'Months', key: viewModel.value.month, dir: monthDirection.value, goTo: goToMonth, boundaries: navBoundaries.value.month, cls: ' col' }).concat(getNavigation({ label: viewModel.value.year, type: 'Years', key: viewModel.value.year, dir: yearDirection.value, goTo: goToYear, boundaries: navBoundaries.value.year, cls: '' }))), h('div', { class: 'q-date__calendar-weekdays row items-center no-wrap' }, daysOfWeek.value.map(day => h('div', { class: 'q-date__calendar-item' }, [ h('div', day) ]))), h('div', { class: 'q-date__calendar-days-container relative-position overflow-hidden' }, [ h(Transition, { name: 'q-transition--slide-' + monthDirection.value }, () => h('div', { key: viewMonthHash.value, class: 'q-date__calendar-days fit' }, days.value.map(day => h('div', { class: day.classes }, [ day.in === true ? h( QBtn, { class: day.today === true ? 'q-date__today' : '', dense: true, flat: day.flat, unelevated: day.unelevated, color: day.color, textColor: day.textColor, label: day.i, tabindex: tabindex.value, ...getCache('day#' + day.i, { onClick: () => { onDayClick(day.i) }, onMouseover: () => { onDayMouseover(day.i) } }) }, day.event !== false ? () => h('div', { class: 'q-date__event bg-' + day.event }) : null ) : h('div', '' + day.i) ])))) ]) ]) ]), Months () { const currentYear = viewModel.value.year === today.value.year const isDisabled = month => { return ( (minNav.value !== null && viewModel.value.year === minNav.value.year && minNav.value.month > month) || (maxNav.value !== null && viewModel.value.year === maxNav.value.year && maxNav.value.month < month) ) } const content = innerLocale.value.monthsShort.map((month, i) => { const active = viewModel.value.month === i + 1 return h('div', { class: 'q-date__months-item flex flex-center' }, [ h(QBtn, { class: currentYear === true && today.value.month === i + 1 ? 'q-date__today' : null, flat: active !== true, label: month, unelevated: active, color: active === true ? computedColor.value : null, textColor: active === true ? computedTextColor.value : null, tabindex: tabindex.value, disable: isDisabled(i + 1), ...getCache('month#' + i, { onClick: () => { setMonth(i + 1) } }) }) ]) }) props.yearsInMonthView === true && content.unshift( h('div', { class: 'row no-wrap full-width' }, [ getNavigation({ label: viewModel.value.year, type: 'Years', key: viewModel.value.year, dir: yearDirection.value, goTo: goToYear, boundaries: navBoundaries.value.year, cls: ' col' }) ]) ) return h('div', { key: 'months-view', class: 'q-date__view q-date__months flex flex-center' }, content) }, Years () { const start = startYear.value, stop = start + yearsInterval, years = [] const isDisabled = year => { return ( (minNav.value !== null && minNav.value.year > year) || (maxNav.value !== null && maxNav.value.year < year) ) } for (let i = start; i <= stop; i++) { const active = viewModel.value.year === i years.push( h('div', { class: 'q-date__years-item flex flex-center' }, [ h(QBtn, { key: 'yr' + i, class: today.value.year === i ? 'q-date__today' : null, flat: !active, label: i, dense: true, unelevated: active, color: active === true ? computedColor.value : null, textColor: active === true ? computedTextColor.value : null, tabindex: tabindex.value, disable: isDisabled(i), ...getCache('yr#' + i, { onClick: () => { setYear(i) } }) }) ]) ) } return h('div', { class: 'q-date__view q-date__years flex flex-center' }, [ h('div', { class: 'col-auto' }, [ h(QBtn, { round: true, dense: true, flat: true, icon: dateArrow.value[ 0 ], tabindex: tabindex.value, disable: isDisabled(start), ...getCache('y-', { onClick: () => { startYear.value -= yearsInterval } }) }) ]), h('div', { class: 'q-date__years-content col self-stretch row items-center' }, years), h('div', { class: 'col-auto' }, [ h(QBtn, { round: true, dense: true, flat: true, icon: dateArrow.value[ 1 ], tabindex: tabindex.value, disable: isDisabled(stop), ...getCache('y+', { onClick: () => { startYear.value += yearsInterval } }) }) ]) ]) } } function onDayClick (dayIndex) { const day = { ...viewModel.value, day: dayIndex } if (props.range === false) { toggleDate(day, viewMonthHash.value) return } if (editRange.value === null) { const dayProps = days.value.find(day => day.fill !== true && day.i === dayIndex) if (props.noUnset !== true && dayProps.range !== void 0) { removeFromModel({ target: day, from: dayProps.range.from, to: dayProps.range.to }) return } if (dayProps.selected === true) { removeFromModel(day) return } const initHash = getDayHash(day) editRange.value = { init: day, initHash, final: day, finalHash: initHash } emit('rangeStart', getShortDate(day)) } else { const initHash = editRange.value.initHash, finalHash = getDayHash(day), payload = initHash <= finalHash ? { from: editRange.value.init, to: day } : { from: day, to: editRange.value.init } editRange.value = null addToModel(initHash === finalHash ? day : { target: day, ...payload }) emit('rangeEnd', { from: getShortDate(payload.from), to: getShortDate(payload.to) }) } } function onDayMouseover (dayIndex) { if (editRange.value !== null) { const final = { ...viewModel.value, day: dayIndex } Object.assign(editRange.value, { final, finalHash: getDayHash(final) }) } } // expose public methods Object.assign(proxy, { setToday, setView, offsetCalendar, setCalendarTo, setEditingRange }) return () => { const content = [ h('div', { class: 'q-date__content col relative-position' }, [ h(Transition, { name: 'q-transition--fade' }, renderViews[ view.value ]) ]) ] const def = hSlot(slots.default) def !== void 0 && content.push( h('div', { class: 'q-date__actions' }, def) ) if (props.name !== void 0 && props.disable !== true) { injectFormInput(content, 'push') } return h('div', { class: classes.value, ...attributes.value }, [ getHeader(), h('div', { ref: blurTargetRef, class: 'q-date__main col column', tabindex: -1 }, content) ]) } } })