296 lines
13 KiB
Vue
296 lines
13 KiB
Vue
<template>
|
|
<div :class="calendarParentClass">
|
|
<div :style="contentWrapStyle">
|
|
<div
|
|
ref="calendarWrapRef"
|
|
v-if="!specificMode"
|
|
:class="calendarWrapClass"
|
|
role="grid"
|
|
:aria-label="defaults.ariaLabels?.calendarWrap"
|
|
>
|
|
<div class="dp__calendar_header" role="row">
|
|
<div class="dp__calendar_header_item" role="gridcell" v-if="weekNumbers">
|
|
{{ weekNumName }}
|
|
</div>
|
|
<div
|
|
class="dp__calendar_header_item"
|
|
role="gridcell"
|
|
v-for="(dayVal, i) in weekDays"
|
|
:key="i"
|
|
data-test="calendar-header"
|
|
>
|
|
<slot v-if="$slots['calendar-header']" name="calendar-header" :day="dayVal" :index="i" />
|
|
<template v-if="!$slots['calendar-header']">
|
|
{{ dayVal }}
|
|
</template>
|
|
</div>
|
|
</div>
|
|
<div class="dp__calendar_header_separator"></div>
|
|
<transition :name="transitionName" :css="!!transitions">
|
|
<div
|
|
class="dp__calendar"
|
|
role="grid"
|
|
:aria-label="defaults.ariaLabels?.calendarDays"
|
|
v-if="showCalendar"
|
|
>
|
|
<div class="dp__calendar_row" role="row" v-for="(week, weekInd) in mappedDates" :key="weekInd">
|
|
<div role="gridcell" v-if="weekNumbers" class="dp__calendar_item dp__week_num">
|
|
<div class="dp__cell_inner">
|
|
{{ getWeekNum(week.days) }}
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-for="(dayVal, dayInd) in week.days"
|
|
role="gridcell"
|
|
class="dp__calendar_item"
|
|
:ref="(el) => assignDayRef(el, weekInd, dayInd)"
|
|
:key="dayInd + weekInd"
|
|
:aria-selected="
|
|
dayVal.classData.dp__active_date ||
|
|
dayVal.classData.dp__range_start ||
|
|
dayVal.classData.dp__range_start
|
|
"
|
|
:aria-disabled="dayVal.classData.dp__cell_disabled"
|
|
:aria-label="defaults.ariaLabels?.day?.(dayVal)"
|
|
tabindex="0"
|
|
:data-test="dayVal.value"
|
|
@click.stop.prevent="$emit('select-date', dayVal)"
|
|
@keydown.enter="$emit('select-date', dayVal)"
|
|
@keydown.space="$emit('handle-space', dayVal)"
|
|
@mouseenter="onMouseOver(dayVal, weekInd, dayInd)"
|
|
@mouseleave="onMouseLeave(dayVal)"
|
|
>
|
|
<div class="dp__cell_inner" :class="dayVal.classData">
|
|
<slot
|
|
name="day"
|
|
v-if="$slots.day && showDay(dayVal)"
|
|
:day="+dayVal.text"
|
|
:date="dayVal.value"
|
|
></slot>
|
|
<template v-if="!$slots.day"> {{ dayVal.text }} </template>
|
|
<div
|
|
v-if="dayVal.marker && showDay(dayVal)"
|
|
:class="markerClass(dayVal.marker)"
|
|
:style="dayVal.marker.color ? { backgroundColor: dayVal.marker.color } : {}"
|
|
></div>
|
|
<div
|
|
class="dp__marker_tooltip"
|
|
v-if="dateMatch(dayVal.value)"
|
|
:style="markerTooltipStyle"
|
|
>
|
|
<div class="dp__tooltip_content" @click.stop v-if="dayVal.marker?.tooltip">
|
|
<div
|
|
v-for="(tooltip, i) in dayVal.marker.tooltip"
|
|
:key="i"
|
|
class="dp__tooltip_text"
|
|
>
|
|
<slot
|
|
name="marker-tooltip"
|
|
v-if="$slots['marker-tooltip']"
|
|
:tooltip="tooltip"
|
|
:day="dayVal.value"
|
|
></slot>
|
|
<template v-if="!$slots['marker-tooltip']">
|
|
<div
|
|
class="dp__tooltip_mark"
|
|
:style="tooltip.color ? { backgroundColor: tooltip.color } : {}"
|
|
></div>
|
|
<div>{{ tooltip.text }}</div>
|
|
</template>
|
|
</div>
|
|
<div class="dp__arrow_bottom_tp"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</transition>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { computed, nextTick, onMounted, ref } from 'vue';
|
|
|
|
import { getDayNames, getDefaultMarker, unrefElement } from '@/utils/util';
|
|
import { useArrowNavigation, useUtils } from '@/components/composables';
|
|
import { AllProps } from '@/utils/props';
|
|
import { getDate, isDateAfter, isDateEqual, resetDateTime } from '@/utils/date-utils';
|
|
|
|
import type { PropType, UnwrapRef } from 'vue';
|
|
import type { DynamicClass, ICalendarDate, ICalendarDay, IMarker } from '@/interfaces';
|
|
|
|
const emit = defineEmits([
|
|
'select-date',
|
|
'set-hover-date',
|
|
'handle-scroll',
|
|
'mount',
|
|
'handle-swipe',
|
|
'handle-space',
|
|
'tooltip-open',
|
|
'tooltip-close',
|
|
]);
|
|
|
|
const props = defineProps({
|
|
mappedDates: { type: Array as PropType<ICalendarDate[]>, default: () => [] },
|
|
getWeekNum: {
|
|
type: Function as PropType<(dates: UnwrapRef<ICalendarDay[]>) => string | number>,
|
|
default: () => '',
|
|
},
|
|
specificMode: { type: Boolean as PropType<boolean>, default: false },
|
|
instance: { type: Number as PropType<number>, default: 0 },
|
|
month: { type: Number as PropType<number>, default: 0 },
|
|
year: { type: Number as PropType<number>, default: 0 },
|
|
...AllProps,
|
|
});
|
|
|
|
const { buildMultiLevelMatrix } = useArrowNavigation();
|
|
const { setDateMonthOrYear, defaults } = useUtils(props);
|
|
|
|
const showMakerTooltip = ref<Date | null>(null);
|
|
const markerTooltipStyle = ref({ bottom: '', left: '', transform: '' });
|
|
const dayRefs = ref<HTMLElement[][]>([]);
|
|
const calendarWrapRef = ref<HTMLElement | null>(null);
|
|
const showCalendar = ref(true);
|
|
const transitionName = ref('');
|
|
const touch = ref({ startX: 0, endX: 0, startY: 0, endY: 0 });
|
|
|
|
const weekDays = computed(() => {
|
|
if (props.dayNames) {
|
|
return Array.isArray(props.dayNames) ? props.dayNames : props.dayNames(props.locale, +props.weekStart);
|
|
}
|
|
return getDayNames(props.locale, +props.weekStart);
|
|
});
|
|
|
|
onMounted(() => {
|
|
emit('mount', { cmp: 'calendar', refs: dayRefs });
|
|
if (!props.noSwipe) {
|
|
if (calendarWrapRef.value) {
|
|
calendarWrapRef.value.addEventListener('touchstart', onTouchStart, { passive: false });
|
|
calendarWrapRef.value.addEventListener('touchend', onTouchEnd, { passive: false });
|
|
calendarWrapRef.value.addEventListener('touchmove', onTouchMove, { passive: false });
|
|
}
|
|
}
|
|
if (props.monthChangeOnScroll && calendarWrapRef.value) {
|
|
calendarWrapRef.value.addEventListener('wheel', onScroll, { passive: false });
|
|
}
|
|
});
|
|
|
|
const getTransitionName = (isNext: boolean) => {
|
|
if (isNext) return props.vertical ? 'vNext' : 'next';
|
|
return props.vertical ? 'vPrevious' : 'previous';
|
|
};
|
|
|
|
const triggerTransition = (month: number, year: number): void => {
|
|
if (props.transitions) {
|
|
const newDate = resetDateTime(setDateMonthOrYear(getDate(), props.month, props.year));
|
|
transitionName.value = isDateAfter(resetDateTime(setDateMonthOrYear(getDate(), month, year)), newDate)
|
|
? defaults.value.transitions[getTransitionName(true)]
|
|
: defaults.value.transitions[getTransitionName(false)];
|
|
showCalendar.value = false;
|
|
nextTick(() => {
|
|
showCalendar.value = true;
|
|
});
|
|
}
|
|
};
|
|
|
|
// Class object for calendar wrapper
|
|
const calendarWrapClass = computed(
|
|
(): DynamicClass => ({
|
|
dp__calendar_wrap: true,
|
|
[props.calendarClassName]: !!props.calendarClassName,
|
|
}),
|
|
);
|
|
|
|
const markerClass = computed((): ((marker: IMarker) => DynamicClass) => (marker) => {
|
|
const defaultMarker = getDefaultMarker(marker);
|
|
return {
|
|
dp__marker_dot: defaultMarker.type === 'dot',
|
|
dp__marker_line: defaultMarker.type === 'line',
|
|
};
|
|
});
|
|
|
|
const dateMatch = computed((): ((day: Date) => boolean) => (day) => isDateEqual(day, showMakerTooltip.value));
|
|
|
|
const calendarParentClass = computed(() => ({
|
|
dp__calendar: true,
|
|
dp__calendar_next: defaults.value.multiCalendars > 0 && props.instance !== 0,
|
|
}));
|
|
|
|
const showDay = computed(() => (day: ICalendarDay) => props.hideOffsetDates ? day.current : true);
|
|
|
|
const contentWrapStyle = computed(() => (props.specificMode ? { height: `${props.modeHeight}px` } : undefined));
|
|
|
|
const onMouseOver = (day: UnwrapRef<ICalendarDay>, weekInd: number, dayInd: number): void => {
|
|
emit('set-hover-date', day);
|
|
if (day.marker?.tooltip?.length) {
|
|
const el = unrefElement(dayRefs.value[weekInd][dayInd]);
|
|
if (el) {
|
|
const { width, height } = el.getBoundingClientRect();
|
|
|
|
markerTooltipStyle.value = {
|
|
bottom: `${height}px`,
|
|
left: `${width / 2}px`,
|
|
transform: `translateX(-50%)`,
|
|
};
|
|
showMakerTooltip.value = day.value;
|
|
emit('tooltip-open', day.value);
|
|
}
|
|
}
|
|
};
|
|
|
|
const onMouseLeave = (day: UnwrapRef<ICalendarDay>): void => {
|
|
if (showMakerTooltip.value) {
|
|
showMakerTooltip.value = null;
|
|
emit('tooltip-close', day.value);
|
|
}
|
|
};
|
|
|
|
const onTouchStart = (ev: TouchEvent): void => {
|
|
touch.value.startX = ev.changedTouches[0].screenX;
|
|
touch.value.startY = ev.changedTouches[0].screenY;
|
|
};
|
|
|
|
const onTouchEnd = (ev: TouchEvent): void => {
|
|
touch.value.endX = ev.changedTouches[0].screenX;
|
|
touch.value.endY = ev.changedTouches[0].screenY;
|
|
handleTouch();
|
|
};
|
|
|
|
const onTouchMove = (ev: TouchEvent): void => {
|
|
if (props.vertical && !props.inline) {
|
|
ev.preventDefault();
|
|
}
|
|
};
|
|
|
|
const handleTouch = () => {
|
|
const property = props.vertical ? 'Y' : 'X';
|
|
if (Math.abs(touch.value[`start${property}`] - touch.value[`end${property}`]) > 10) {
|
|
emit('handle-swipe', touch.value[`start${property}`] > touch.value[`end${property}`] ? 'right' : 'left');
|
|
}
|
|
};
|
|
|
|
const assignDayRef = (el: any, weekInd: number, dayInd: number) => {
|
|
if (el) {
|
|
if (Array.isArray(dayRefs.value[weekInd])) {
|
|
dayRefs.value[weekInd][dayInd] = el;
|
|
} else {
|
|
dayRefs.value[weekInd] = [el];
|
|
}
|
|
}
|
|
if (props.arrowNavigation) {
|
|
buildMultiLevelMatrix(dayRefs.value, 'calendar');
|
|
}
|
|
};
|
|
|
|
const onScroll = (ev: WheelEvent) => {
|
|
if (props.monthChangeOnScroll) {
|
|
ev.preventDefault();
|
|
emit('handle-scroll', ev);
|
|
}
|
|
};
|
|
|
|
defineExpose({ triggerTransition });
|
|
</script>
|