330 lines
10 KiB
JavaScript
330 lines
10 KiB
JavaScript
import { mergeProps as _mergeProps, createVNode as _createVNode, withDirectives as _withDirectives } from "vue";
|
|
// Styles
|
|
import "./VCalendarCategory.css";
|
|
import "./VCalendarDaily.css";
|
|
import "./VCalendarWeekly.css";
|
|
|
|
// Components
|
|
import { VCalendarCategory } from "./VCalendarCategory.js";
|
|
import { VCalendarDaily } from "./VCalendarDaily.js";
|
|
import { VCalendarWeekly } from "./VCalendarWeekly.js"; // Composables
|
|
import { makeCalendarBaseProps } from "./composables/calendarBase.js";
|
|
import { makeCalendarWithEventsProps, useCalendarWithEvents } from "./composables/calendarWithEvents.js";
|
|
import { forwardRefs } from "../../composables/forwardRefs.js"; // Directives
|
|
import vResize from "../../directives/resize/index.js"; // Utilities
|
|
import { computed, onMounted, onUpdated, ref, watch } from 'vue';
|
|
import { getParsedCategories } from "./util/parser.js";
|
|
import { copyTimestamp, DAY_MIN, DAYS_IN_MONTH_MAX, DAYS_IN_WEEK, getEndOfMonth, getStartOfMonth, nextDay, prevDay, relativeDays, timestampToDate, updateFormatted, updateRelative, updateWeekday, validateTimestamp } from "./util/timestamp.js";
|
|
import { genericComponent, useRender } from "../../util/index.js"; // Types
|
|
// Types
|
|
export const VCalendar = genericComponent()({
|
|
name: 'VCalendar',
|
|
directives: {
|
|
vResize
|
|
},
|
|
props: {
|
|
modelValue: {
|
|
type: [String, Number, Date],
|
|
validate: validateTimestamp
|
|
},
|
|
categoryDays: {
|
|
type: [Number, String],
|
|
default: 1,
|
|
validate: x => isFinite(parseInt(x)) && parseInt(x) > 0
|
|
},
|
|
categories: {
|
|
type: [Array, String],
|
|
default: ''
|
|
},
|
|
categoryText: {
|
|
type: [String, Function]
|
|
},
|
|
maxDays: {
|
|
type: Number,
|
|
default: 7
|
|
},
|
|
categoryHideDynamic: {
|
|
type: Boolean
|
|
},
|
|
categoryShowAll: {
|
|
type: Boolean
|
|
},
|
|
categoryForInvalid: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
...makeCalendarBaseProps(),
|
|
...makeCalendarWithEventsProps()
|
|
},
|
|
setup(props, {
|
|
slots,
|
|
attrs,
|
|
emit
|
|
}) {
|
|
const root = ref();
|
|
const base = useCalendarWithEvents(props, slots, attrs);
|
|
const lastStart = ref(null);
|
|
const lastEnd = ref(null);
|
|
const parsedCategoryDays = computed(() => {
|
|
return parseInt(String(props.categoryDays)) || 1;
|
|
});
|
|
const parsedCategories = computed(() => {
|
|
return getParsedCategories(props.categories, props.categoryText);
|
|
});
|
|
const renderProps = computed(() => {
|
|
const around = base.parsedValue.value;
|
|
let component = null;
|
|
let maxDays = props.maxDays;
|
|
let categories = parsedCategories.value;
|
|
let start = around;
|
|
let end = around;
|
|
switch (props.type) {
|
|
case 'month':
|
|
component = VCalendarWeekly;
|
|
start = getStartOfMonth(around);
|
|
end = getEndOfMonth(around);
|
|
break;
|
|
case 'week':
|
|
component = VCalendarDaily;
|
|
start = base.getStartOfWeek(around);
|
|
end = base.getEndOfWeek(around);
|
|
maxDays = 7;
|
|
break;
|
|
case 'day':
|
|
component = VCalendarDaily;
|
|
maxDays = 1;
|
|
break;
|
|
case '4day':
|
|
component = VCalendarDaily;
|
|
end = relativeDays(copyTimestamp(end), nextDay, 3);
|
|
updateFormatted(end);
|
|
maxDays = 4;
|
|
break;
|
|
case 'custom-weekly':
|
|
component = VCalendarWeekly;
|
|
start = base.parsedStart.value || around;
|
|
end = base.parsedEnd.value;
|
|
break;
|
|
case 'custom-daily':
|
|
component = VCalendarDaily;
|
|
start = base.parsedStart.value || around;
|
|
end = base.parsedEnd.value;
|
|
break;
|
|
case 'category':
|
|
const days = parsedCategoryDays.value;
|
|
component = VCalendarCategory;
|
|
end = relativeDays(copyTimestamp(end), nextDay, days);
|
|
updateFormatted(end);
|
|
maxDays = days;
|
|
categories = getCategoryList(categories);
|
|
break;
|
|
default:
|
|
const type = props.type;
|
|
throw new Error(`${type} is not a valid Calendar type`);
|
|
}
|
|
return {
|
|
component,
|
|
start,
|
|
end,
|
|
maxDays,
|
|
categories
|
|
};
|
|
});
|
|
const eventWeekdays = computed(() => {
|
|
return base.effectiveWeekdays.value;
|
|
});
|
|
const categoryMode = computed(() => {
|
|
return props.type === 'category';
|
|
});
|
|
const monthLongFormatter = computed(() => {
|
|
return base.getFormatter({
|
|
timeZone: 'UTC',
|
|
month: 'long'
|
|
});
|
|
});
|
|
const monthShortFormatter = computed(() => {
|
|
return base.getFormatter({
|
|
timeZone: 'UTC',
|
|
month: 'short'
|
|
});
|
|
});
|
|
const title = computed(() => {
|
|
const {
|
|
start,
|
|
end
|
|
} = renderProps.value;
|
|
const spanYears = start.year !== end.year;
|
|
const spanMonths = spanYears || start.month !== end.month;
|
|
if (spanYears) {
|
|
return monthShortFormatter.value(start, true) + ' ' + start.year + ' - ' + monthShortFormatter.value(end, true) + ' ' + end.year;
|
|
}
|
|
if (spanMonths) {
|
|
return monthShortFormatter.value(start, true) + ' - ' + monthShortFormatter.value(end, true) + ' ' + end.year;
|
|
} else {
|
|
return monthLongFormatter.value(start, false) + ' ' + start.year;
|
|
}
|
|
});
|
|
function checkChange() {
|
|
const {
|
|
start,
|
|
end
|
|
} = renderProps.value;
|
|
if (!lastStart.value || !lastEnd.value || start.date !== lastStart.value.date || end.date !== lastEnd.value.date) {
|
|
lastStart.value = start;
|
|
lastEnd.value = end;
|
|
emit('change', {
|
|
start,
|
|
end
|
|
});
|
|
}
|
|
}
|
|
function move(amount = 1) {
|
|
const moved = copyTimestamp(base.parsedValue.value);
|
|
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 (props.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, parsedCategoryDays.value);
|
|
break;
|
|
}
|
|
}
|
|
updateWeekday(moved);
|
|
updateFormatted(moved);
|
|
updateRelative(moved, base.times.now);
|
|
if (props.modelValue instanceof Date) {
|
|
emit('update:modelValue', timestampToDate(moved));
|
|
} else if (typeof props.modelValue === 'number') {
|
|
emit('update:modelValue', timestampToDate(moved).getTime());
|
|
} else {
|
|
emit('update:modelValue', moved.date);
|
|
}
|
|
emit('moved', moved);
|
|
}
|
|
function next(amount = 1) {
|
|
move(amount);
|
|
}
|
|
function prev(amount = 1) {
|
|
move(-amount);
|
|
}
|
|
function getCategoryList(categories) {
|
|
if (!base.noEvents.value) {
|
|
const categoryMap = categories.reduce((map, category, index) => {
|
|
if (typeof category === 'object' && category.categoryName) map[category.categoryName] = {
|
|
index,
|
|
count: 0
|
|
};else if (typeof category === 'string') map[category] = {
|
|
index,
|
|
count: 0
|
|
};
|
|
return map;
|
|
}, {});
|
|
if (!props.categoryHideDynamic || !props.categoryShowAll) {
|
|
let categoryLength = categories.length;
|
|
base.parsedEvents.value.forEach(ev => {
|
|
let category = ev.category;
|
|
if (typeof category !== 'string') {
|
|
category = props.categoryForInvalid;
|
|
}
|
|
if (!category) {
|
|
return;
|
|
}
|
|
if (category in categoryMap) {
|
|
categoryMap[category].count++;
|
|
} else if (!props.categoryHideDynamic) {
|
|
categoryMap[category] = {
|
|
index: categoryLength++,
|
|
count: 1
|
|
};
|
|
}
|
|
});
|
|
}
|
|
if (!props.categoryShowAll) {
|
|
for (const category in categoryMap) {
|
|
if (categoryMap[category].count === 0) {
|
|
delete categoryMap[category];
|
|
}
|
|
}
|
|
}
|
|
categories = categories.filter(category => {
|
|
if (typeof category === 'object' && category.categoryName) {
|
|
return categoryMap.hasOwnProperty(category.categoryName);
|
|
} else if (typeof category === 'string') {
|
|
return categoryMap.hasOwnProperty(category);
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
return categories;
|
|
}
|
|
watch(renderProps, checkChange);
|
|
onMounted(() => {
|
|
base.updateEventVisibility();
|
|
checkChange();
|
|
});
|
|
onUpdated(() => {
|
|
window.requestAnimationFrame(base.updateEventVisibility);
|
|
});
|
|
useRender(() => {
|
|
const {
|
|
start,
|
|
end,
|
|
maxDays,
|
|
component: Component,
|
|
categories
|
|
} = renderProps.value;
|
|
return _withDirectives(_createVNode(Component, _mergeProps({
|
|
"ref": root,
|
|
"class": ['v-calendar', {
|
|
'v-calendar-events': !base.noEvents.value
|
|
}],
|
|
"role": "grid"
|
|
}, Component.filterProps(props), {
|
|
"start": start.date,
|
|
"end": end.date,
|
|
"maxDays": maxDays,
|
|
"weekdays": base.effectiveWeekdays.value,
|
|
"categories": categories,
|
|
"onClick:date": (e, day) => {
|
|
if (attrs['onUpdate:modelValue']) emit('update:modelValue', day.date);
|
|
}
|
|
}), base.getScopedSlots()), [[vResize, base.updateEventVisibility, void 0, {
|
|
quiet: true
|
|
}]]);
|
|
});
|
|
return forwardRefs({
|
|
...base,
|
|
lastStart,
|
|
lastEnd,
|
|
parsedCategoryDays,
|
|
renderProps,
|
|
eventWeekdays,
|
|
categoryMode,
|
|
title,
|
|
monthLongFormatter,
|
|
monthShortFormatter,
|
|
parsedCategories,
|
|
checkChange,
|
|
move,
|
|
next,
|
|
prev,
|
|
getCategoryList
|
|
}, root);
|
|
}
|
|
});
|
|
//# sourceMappingURL=VCalendar.js.map
|