131 lines
3.0 KiB
Vue
131 lines
3.0 KiB
Vue
<script setup>
|
|
import CalendarDay from './CalendarDay.vue'
|
|
import EventOverlay from './EventOverlay.vue'
|
|
|
|
const props = defineProps({ week: Object, dragging: { type: Boolean, default: false } })
|
|
|
|
const emit = defineEmits([
|
|
'day-mousedown',
|
|
'day-mouseenter',
|
|
'day-mouseup',
|
|
'day-touchstart',
|
|
'event-click',
|
|
])
|
|
|
|
const handleDayMouseDown = (dateStr) => {
|
|
emit('day-mousedown', dateStr)
|
|
}
|
|
|
|
const handleDayMouseEnter = (dateStr) => {
|
|
emit('day-mouseenter', dateStr)
|
|
}
|
|
|
|
const handleDayMouseUp = (dateStr) => {
|
|
emit('day-mouseup', dateStr)
|
|
}
|
|
|
|
const handleDayTouchStart = (dateStr) => {
|
|
emit('day-touchstart', dateStr)
|
|
}
|
|
|
|
// touchmove & touchend handled globally in CalendarView
|
|
|
|
const handleEventClick = (payload) => {
|
|
emit('event-click', payload)
|
|
}
|
|
|
|
// Only apply upside-down rotation (bottomup) for Latin script month labels
|
|
function shouldRotateMonth(label) {
|
|
if (!label) return false
|
|
try {
|
|
return /\p{Script=Latin}/u.test(label)
|
|
} catch (e) {
|
|
return /[A-Za-z\u00C0-\u024F\u1E00-\u1EFF]/u.test(label)
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="week-row" :style="{ top: `${props.week.top}px` }">
|
|
<div class="week-label">W{{ props.week.weekNumber }}</div>
|
|
<div class="days-grid">
|
|
<CalendarDay
|
|
v-for="day in props.week.days"
|
|
:key="day.date"
|
|
:day="day"
|
|
:dragging="props.dragging"
|
|
@mousedown="handleDayMouseDown(day.date)"
|
|
@mouseenter="handleDayMouseEnter(day.date)"
|
|
@mouseup="handleDayMouseUp(day.date)"
|
|
@touchstart="handleDayTouchStart(day.date)"
|
|
/>
|
|
<EventOverlay :week="props.week" @event-click="handleEventClick" />
|
|
</div>
|
|
<div
|
|
v-if="props.week.monthLabel"
|
|
class="month-label"
|
|
:class="props.week.monthLabel.monthClass"
|
|
:style="{ height: `calc(var(--row-h) * ${props.week.monthLabel.weeksSpan})` }"
|
|
>
|
|
<span :class="{ bottomup: shouldRotateMonth(props.week.monthLabel.text) }">{{
|
|
props.week.monthLabel.text
|
|
}}</span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.week-row {
|
|
display: grid;
|
|
grid-template-columns: var(--week-w) repeat(7, 1fr) var(--month-w);
|
|
position: absolute;
|
|
height: var(--row-h);
|
|
width: 100%;
|
|
}
|
|
|
|
.week-label {
|
|
display: grid;
|
|
place-items: center;
|
|
width: 100%;
|
|
color: var(--muted);
|
|
font-size: 1.2em;
|
|
font-weight: 500;
|
|
user-select: none;
|
|
}
|
|
.days-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(7, 1fr);
|
|
position: relative;
|
|
height: 100%;
|
|
width: 100%;
|
|
}
|
|
.week-label {
|
|
height: var(--row-h);
|
|
}
|
|
.month-label {
|
|
position: absolute;
|
|
top: 0;
|
|
right: 0;
|
|
width: var(--month-w);
|
|
background-image: linear-gradient(to bottom, rgba(186, 186, 186, 0.3), rgba(186, 186, 186, 0.2));
|
|
font-size: 2em;
|
|
font-weight: 700;
|
|
color: var(--muted);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 15;
|
|
overflow: hidden;
|
|
}
|
|
.month-label > span {
|
|
display: inline-block;
|
|
white-space: nowrap;
|
|
writing-mode: vertical-rl;
|
|
text-orientation: mixed;
|
|
transform-origin: center;
|
|
}
|
|
.bottomup {
|
|
transform: rotate(180deg);
|
|
}
|
|
</style>
|