UNPKG

vuetify

Version:

Vue Material Component Framework

381 lines (342 loc) 10.1 kB
// Styles // import '../../stylus/components/_calendar-daily.styl' // Types import { VNode, Component } from 'vue' // Mixins import CalendarWithEvents from './mixins/calendar-with-events' // Util import props from './util/props' import { DAYS_IN_MONTH_MAX, DAY_MIN, DAYS_IN_WEEK, parseTimestamp, validateTimestamp, relativeDays, nextDay, prevDay, copyTimestamp, updateFormatted, updateWeekday, updateRelative, getStartOfMonth, getEndOfMonth, VTime, VTimestampInput, timestampToDate, } from './util/timestamp' // Calendars import VCalendarMonthly from './VCalendarMonthly' import VCalendarDaily from './VCalendarDaily' import VCalendarWeekly from './VCalendarWeekly' import VCalendarCategory from './VCalendarCategory' import { CalendarTimestamp, CalendarFormatter } from 'vuetify/types' // Types interface VCalendarRenderProps { start: CalendarTimestamp end: CalendarTimestamp component: string | Component maxDays: number weekdays: number[] categories: string[] } /* @vue/component */ export default CalendarWithEvents.extend({ name: 'v-calendar', props: { ...props.calendar, ...props.weeks, ...props.intervals, ...props.category, }, data: () => ({ lastStart: null as CalendarTimestamp | null, lastEnd: null as CalendarTimestamp | null, }), computed: { parsedValue (): CalendarTimestamp { return (validateTimestamp(this.value) ? parseTimestamp(this.value, true) : (this.parsedStart || this.times.today)) }, parsedCategoryDays (): number { return parseInt(this.categoryDays) || 1 }, renderProps (): VCalendarRenderProps { const around = this.parsedValue let component: any = null let maxDays = this.maxDays let weekdays = this.parsedWeekdays let categories = this.parsedCategories let start = around let end = around switch (this.type) { case 'month': component = VCalendarMonthly start = getStartOfMonth(around) end = getEndOfMonth(around) break case 'week': component = VCalendarDaily start = this.getStartOfWeek(around) end = this.getEndOfWeek(around) maxDays = 7 break case 'day': component = VCalendarDaily maxDays = 1 weekdays = [start.weekday] break case '4day': component = VCalendarDaily end = relativeDays(copyTimestamp(end), nextDay, 4) updateFormatted(end) maxDays = 4 weekdays = [ start.weekday, (start.weekday + 1) % 7, (start.weekday + 2) % 7, (start.weekday + 3) % 7, ] break case 'custom-weekly': component = VCalendarWeekly start = this.parsedStart || around end = this.parsedEnd break case 'custom-daily': component = VCalendarDaily start = this.parsedStart || around end = this.parsedEnd break case 'category': const days = this.parsedCategoryDays component = VCalendarCategory end = relativeDays(copyTimestamp(end), nextDay, days) updateFormatted(end) maxDays = days weekdays = [] for (let i = 0; i < days; i++) { weekdays.push((start.weekday + i) % 7) } categories = this.getCategoryList(categories) break default: throw new Error(this.type + ' is not a valid Calendar type') } return { component, start, end, maxDays, weekdays, categories } }, eventWeekdays (): number[] { return this.renderProps.weekdays }, categoryMode (): boolean { return this.type === 'category' }, title (): string { const { start, end } = this.renderProps const spanYears = start.year !== end.year const spanMonths = spanYears || start.month !== end.month if (spanYears) { return this.monthShortFormatter(start, true) + ' ' + start.year + ' - ' + this.monthShortFormatter(end, true) + ' ' + end.year } if (spanMonths) { return this.monthShortFormatter(start, true) + ' - ' + this.monthShortFormatter(end, true) + ' ' + end.year } else { return this.monthLongFormatter(start, false) + ' ' + start.year } }, monthLongFormatter (): CalendarFormatter { return this.getFormatter({ timeZone: 'UTC', month: 'long', }) }, monthShortFormatter (): CalendarFormatter { return this.getFormatter({ timeZone: 'UTC', month: 'short', }) }, parsedCategories (): string[] { return typeof this.categories === 'string' && this.categories ? this.categories.split(/\s*,\s*/) : Array.isArray(this.categories) ? this.categories as string[] : [] }, }, watch: { renderProps: 'checkChange', }, mounted () { this.updateEventVisibility() this.checkChange() }, updated () { window.requestAnimationFrame(this.updateEventVisibility) }, methods: { checkChange (): void { const { lastStart, lastEnd } = this const { start, end } = this.renderProps if (!lastStart || !lastEnd || start.date !== lastStart.date || end.date !== lastEnd.date) { this.lastStart = start this.lastEnd = end this.$emit('change', { start, end }) } }, move (amount = 1): void { const moved = copyTimestamp(this.parsedValue) const forward = amount > 0 const mover = forward ? nextDay : prevDay const limit = forward ? DAYS_IN_MONTH_MAX : DAY_MIN let times = forward ? amount : -amount while (--times >= 0) { switch (this.type) { case 'month': moved.day = limit mover(moved) break case 'week': relativeDays(moved, mover, DAYS_IN_WEEK) break case 'day': relativeDays(moved, mover, 1) break case '4day': relativeDays(moved, mover, 4) break case 'category': relativeDays(moved, mover, this.parsedCategoryDays) break } } updateWeekday(moved) updateFormatted(moved) updateRelative(moved, this.times.now) if (this.value instanceof Date) { this.$emit('input', timestampToDate(moved)) } else if (typeof this.value === 'number') { this.$emit('input', timestampToDate(moved).getTime()) } else { this.$emit('input', moved.date) } this.$emit('moved', moved) }, next (amount = 1): void { this.move(amount) }, prev (amount = 1): void { this.move(-amount) }, timeToY (time: VTime, clamp = true): number | false { const c = this.$children[0] as any if (c && c.timeToY) { return c.timeToY(time, clamp) } else { return false } }, timeDelta (time: VTime): number | false { const c = this.$children[0] as any if (c && c.timeDelta) { return c.timeDelta(time) } else { return false } }, minutesToPixels (minutes: number): number { const c = this.$children[0] as any if (c && c.minutesToPixels) { return c.minutesToPixels(minutes) } else { return -1 } }, scrollToTime (time: VTime): boolean { const c = this.$children[0] as any if (c && c.scrollToTime) { return c.scrollToTime(time) } else { return false } }, parseTimestamp (input: VTimestampInput, required?: false): CalendarTimestamp | null { return parseTimestamp(input, required, this.times.now) }, timestampToDate (timestamp: CalendarTimestamp): Date { return timestampToDate(timestamp) }, getCategoryList (categories: string[]): string[] { if (!this.noEvents) { const categoryMap = categories.reduce((map, category, index) => { map[category] = { index, count: 0 } return map }, Object.create(null)) if (!this.categoryHideDynamic || !this.categoryShowAll) { let categoryLength = categories.length this.parsedEvents.forEach(ev => { let category = ev.category if (typeof category !== 'string') { category = this.categoryForInvalid } if (!category) { return } if (category in categoryMap) { categoryMap[category].count++ } else if (!this.categoryHideDynamic) { categoryMap[category] = { index: categoryLength++, count: 1, } } }) } if (!this.categoryShowAll) { for (const category in categoryMap) { if (categoryMap[category].count === 0) { delete categoryMap[category] } } } categories = Object.keys(categoryMap) } return categories }, }, render (h): VNode { const { start, end, maxDays, component, weekdays, categories } = this.renderProps return h(component, { staticClass: 'v-calendar', class: { 'v-calendar-events': !this.noEvents, }, props: { ...this.$props, start: start.date, end: end.date, maxDays, weekdays, categories, }, directives: [{ modifiers: { quiet: true }, name: 'resize', value: this.updateEventVisibility, }], on: { ...this.$listeners, 'click:date': (day: CalendarTimestamp) => { if (this.$listeners.input) { this.$emit('input', day.date) } if (this.$listeners['click:date']) { this.$emit('click:date', day) } }, }, scopedSlots: this.getScopedSlots(), }) }, })