calendar/src/components/CalendarWeek.vue
2025-08-25 10:33:07 -06:00

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>