// Styles import "../../../../src/components/VCalendar/mixins/calendar-with-events.sass"; // Mixins import CalendarBase from './calendar-base'; // Util import props from '../util/props'; import { getDayIdentifier, parseTime } from '../util/timestamp'; import { parseEvent, isEventOn } from '../util/events'; /* @vue/component */ export default CalendarBase.extend({ name: 'calendar-with-events', props: props.events, computed: { noEvents() { return this.events.length === 0; }, parsedEvents() { return this.events.map((input, index) => parseEvent(input, index, this.eventStart, this.eventEnd)); }, eventColorFunction() { return typeof this.eventColor === 'function' ? this.eventColor : () => this.eventColor; }, eventTextColorFunction() { return typeof this.eventTextColor === 'function' ? this.eventTextColor : () => this.eventTextColor; }, eventNameFunction() { return typeof this.eventName === 'function' ? this.eventName : (event, timedEvent) => { const name = event.input[this.eventName]; if (event.start.hasTime) { if (timedEvent) { const showStart = event.start.hour < 12 && event.end.hour >= 12; const start = this.formatTime(event.start, showStart); const end = this.formatTime(event.end, true); return `${name}
${start} - ${end}`; } else { const time = this.formatTime(event.start, true); return `${time} ${name}`; } } return name; }; } }, methods: { formatTime(withTime, ampm) { const suffix = ampm ? withTime.hour < 12 ? 'a' : 'p' : ''; const hour = withTime.hour % 12 || 12; const minute = withTime.minute; return minute > 0 ? minute < 10 ? `${hour}:0${minute}${suffix}` : `${hour}:${minute}${suffix}` : `${hour}${suffix}`; }, updateEventVisibility() { if (this.noEvents || !this.eventMore) { return; } const eventHeight = this.eventHeight; const eventsMap = this.getEventsMap(); for (const date in eventsMap) { const { parent, events, more } = eventsMap[date]; if (!more) { break; } const parentBounds = parent.getBoundingClientRect(); const last = events.length - 1; let hide = false; let hidden = 0; for (let i = 0; i <= last; i++) { if (!hide) { const eventBounds = events[i].getBoundingClientRect(); hide = eventBounds.bottom + eventHeight > parentBounds.bottom && i !== last; } if (hide) { const id = events[i].getAttribute('data-event'); this.hideEvents(id); hidden++; } } if (hide) { more.style.display = ''; more.innerHTML = this.$vuetify.lang.t(this.eventMoreText, hidden); } else { more.style.display = 'none'; } } }, hideEvents(id) { const elements = this.$refs.events; elements.forEach(el => { if (el.getAttribute('data-event') === id) { el.style.display = 'none'; } }); }, getEventsMap() { const eventsMap = {}; const elements = this.$refs.events; if (!elements || !elements.forEach) { return eventsMap; } elements.forEach(el => { const date = el.getAttribute('data-date'); if (el.parentElement && date) { if (!(date in eventsMap)) { eventsMap[date] = { parent: el.parentElement, more: null, events: [] }; } if (el.getAttribute('data-more')) { eventsMap[date].more = el; } else { eventsMap[date].events.push(el); el.style.display = ''; } } }); return eventsMap; }, genDayEvent({ offset, event }, index, day) { const eventHeight = this.eventHeight; const eventMarginBottom = this.eventMarginBottom; const relativeOffset = (offset - index) * (eventHeight + eventMarginBottom); // 1 = margin bottom const dayIdentifier = getDayIdentifier(day); const start = dayIdentifier === event.startIdentifier; const end = dayIdentifier === event.endIdentifier; const scope = { event: event.input, day, outside: day.outside, start, end, timed: false }; return this.genEvent(event, scope, start || day.index === 0, false, { staticClass: 'v-event', class: { 'v-event-start': start, 'v-event-end': end }, style: { height: `${eventHeight}px`, top: `${relativeOffset}px`, 'margin-bottom': `${eventMarginBottom}px` }, attrs: { 'data-date': day.date, 'data-event': event.index }, key: event.index, ref: 'events', refInFor: true }); }, genTimedEvent({ offset, event, columnCount, column }, index, day) { const dayIdentifier = getDayIdentifier(day); const start = event.startIdentifier >= dayIdentifier; const end = event.endIdentifier > dayIdentifier; const top = start ? day.timeToY(event.start) : 0; const bottom = end ? day.timeToY(1440) : day.timeToY(event.end); const height = Math.max(this.eventHeight, bottom - top); const left = columnCount === -1 ? offset * 5 : column * 100 / columnCount; const right = columnCount === -1 ? 0 : Math.max(0, (columnCount - column - 2) * 100 / columnCount + 10); const scope = { event: event.input, day, outside: day.outside, start, end, timed: true }; return this.genEvent(event, scope, true, true, { staticClass: 'v-event-timed', style: { top: `${top}px`, height: `${height}px`, left: `${left}%`, right: `${right}%` } }); }, genEvent(event, scope, showName, timedEvent, data) { const slot = this.$scopedSlots.event; const text = this.eventTextColorFunction(event.input); const background = this.eventColorFunction(event.input); return this.$createElement('div', this.setTextColor(text, this.setBackgroundColor(background, { on: this.getDefaultMouseEventHandlers(':event', nativeEvent => ({ ...scope, nativeEvent })), directives: [{ name: 'ripple', value: this.eventRipple != null ? this.eventRipple : true }], ...data })), slot ? slot(scope) : showName ? [this.genName(event, timedEvent)] : undefined); }, genName(event, timedEvent) { return this.$createElement('div', { staticClass: 'pl-1', domProps: { innerHTML: this.eventNameFunction(event, timedEvent) } }); }, genMore(day) { return this.$createElement('div', { staticClass: 'v-event-more pl-1', attrs: { 'data-date': day.date, 'data-more': 1 }, directives: [{ name: 'ripple', value: this.eventRipple != null ? this.eventRipple : true }], on: { click: () => this.$emit('click:more', day) }, style: { display: 'none' }, ref: 'events', refInFor: true }); }, getEventsForDay(day) { const identifier = getDayIdentifier(day); return this.parsedEvents.filter(event => isEventOn(event, identifier)); }, getEventsForDayAll(day) { const identifier = getDayIdentifier(day); return this.parsedEvents.filter(event => event.allDay && isEventOn(event, identifier)); }, getEventsForDayTimed(day) { const identifier = getDayIdentifier(day); return this.parsedEvents.filter(event => !event.allDay && isEventOn(event, identifier)); }, isSameColumn(a, b) { const astart = parseTime(a.event.start); const bstart = parseTime(b.event.start); const diff = astart - bstart; const abs = diff < 0 ? -diff : diff; return abs < this.eventOverlapThreshold; }, isOverlapping(a, b) { const astart = parseTime(a.event.start); const bstart = parseTime(b.event.start); if (a.offset < b.offset && bstart < astart) { const aend = astart + this.eventOverlapThreshold; const bend = parseTime(b.event.end); return !(astart >= bend || aend <= bstart); } return false; }, getScopedSlots() { if (this.noEvents) { return this.$scopedSlots; } /** * Over the span of a week (for example) we want to maintain an event in the same row (for weekly and monthly views). * We keep track of those rows by indexToOffset. If the value in that array is -1, then we can place an event at that spot. * For a daily view with timed events we arrange them based on columns and offsets. If two or more events start at around the * same time (eventOverlapThreshold) they go into columns. If one event starts inside another it is indented the appropriate amount. * If one event overlaps another after those adjustments are made those events are placed in columns together instead of any defined * indents. */ const parsedEvents = this.parsedEvents; const indexToOffset = parsedEvents.map(event => -1); const resetOnWeekday = this.weekdays[0]; const checkReset = day => { if (day.weekday === resetOnWeekday) { for (let i = 0; i < indexToOffset.length; i++) { indexToOffset[i] = -1; } } }; const getOffset = (visual, visuals) => { let offset = indexToOffset[visual.event.index]; if (offset === -1) { let min = Number.MAX_SAFE_INTEGER; let max = -1; visuals.forEach(other => { const otherOffset = indexToOffset[other.event.index]; if (otherOffset !== -1) { min = Math.min(min, otherOffset); max = Math.max(max, otherOffset); } }); offset = min > 0 && max !== -1 ? min - 1 : max + 1; indexToOffset[visual.event.index] = offset; } return offset; }; const getVisuals = (events, timed) => { const visuals = events.map(event => ({ event, offset: 0, columnCount: -1, column: -1 })); // sort events by start date/time visuals.sort((a, b) => a.event.startTimestampIdentifier - b.event.startTimestampIdentifier); if (timed) { // timed events can be organized into columns visuals.forEach(visual => { if (visual.columnCount !== -1) { return; } const columns = []; visuals.forEach(other => { if (other.columnCount === -1 && this.isSameColumn(visual, other)) { columns.push(other); } }); if (columns.length > 1) { columns.forEach((visual, visualIndex) => { visual.column = visualIndex; visual.columnCount = columns.length; }); } }); // for any not organized into columns, if they overlap another event // not in a column they are offset visuals.forEach(visual => { if (visual.columnCount === -1) { visuals.forEach(other => { const otherOffset = indexToOffset[other.event.index]; if (otherOffset !== -1 && other.event.endTimestampIdentifier <= visual.event.startTimestampIdentifier) { indexToOffset[other.event.index] = -1; } }); visual.offset = getOffset(visual, visuals); } }); // for any not organized into columns, if a previous event overlaps this event // join them into the columns visuals.forEach(visual => { if (visual.columnCount === -1) { const columns = [visual]; visuals.forEach(other => { if (other !== visual && other.columnCount === -1 && this.isOverlapping(visual, other)) { columns.push(other); } }); if (columns.length > 1) { columns.forEach((visual, visualIndex) => { visual.column = visualIndex; visual.columnCount = columns.length; }); } } }); } else { visuals.forEach(visual => { visual.offset = getOffset(visual, visuals); }); } visuals.sort((a, b) => a.column - b.column || a.offset - b.offset); return visuals; }; const getSlotChildren = (day, getter, mapper, timed) => { checkReset(day); const events = getter(day); return events.length === 0 ? undefined : getVisuals(events, timed).map((visual, index) => mapper(visual, index, day)); }; return { ...this.$scopedSlots, day: day => { const children = getSlotChildren(day, this.getEventsForDay, this.genDayEvent, false); if (children && children.length > 0 && this.eventMore) { children.push(this.genMore(day)); } return children; }, 'day-header': day => { return getSlotChildren(day, this.getEventsForDayAll, this.genDayEvent, false); }, 'day-body': day => { return [this.$createElement('div', { staticClass: 'v-event-timed-container' }, getSlotChildren(day, this.getEventsForDayTimed, this.genTimedEvent, true))]; } }; } } }); //# sourceMappingURL=calendar-with-events.js.map