Event search (Ctrl+F), locale/RTL handling, weekday selector workday/weekend, refactored event handling, Firefox compatibility #3
@ -21,13 +21,10 @@ const props = defineProps({
|
||||
]"
|
||||
:data-date="props.day.date"
|
||||
>
|
||||
<h1>{{ props.day.displayText }}</h1>
|
||||
<h1 class="day-number">{{ props.day.displayText }}</h1>
|
||||
<span v-if="props.day.lunarPhase" class="lunar-phase">{{ props.day.lunarPhase }}</span>
|
||||
|
||||
<div v-if="props.day.holiday" class="holiday-info">
|
||||
<span class="holiday-name" :title="props.day.holiday.name">
|
||||
<div v-if="props.day.holiday" class="holiday-info" dir="auto" :title="props.day.holiday.name">
|
||||
{{ props.day.holiday.name }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -38,19 +35,27 @@ const props = defineProps({
|
||||
border-right: 1px solid var(--border-color);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
user-select: none;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
display: grid;
|
||||
/* 3 columns: day number, flexible space, lunar phase */
|
||||
grid-template-columns: min-content 1fr min-content;
|
||||
/* 3 rows: header, flexible filler, holiday label */
|
||||
grid-template-rows: auto 1fr auto;
|
||||
/* Named grid areas (only ones actually used) */
|
||||
grid-template-areas:
|
||||
'day-number . lunar-phase'
|
||||
'day-number . lunar-phase'
|
||||
'holiday-info holiday-info holiday-info';
|
||||
/* Explicit areas mainly for clarity */
|
||||
grid-auto-flow: row;
|
||||
padding: 0.25em;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: var(--row-h);
|
||||
font-weight: 700;
|
||||
transition: background-color 0.15s ease;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.cell h1 {
|
||||
.cell h1.day-number {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-width: 1.5em;
|
||||
@ -58,15 +63,16 @@ const props = defineProps({
|
||||
font-weight: 700;
|
||||
color: var(--ink);
|
||||
transition: background-color 0.15s ease;
|
||||
grid-area: day-number;
|
||||
}
|
||||
.cell.weekend h1 {
|
||||
.cell.weekend h1.day-number {
|
||||
color: var(--weekend);
|
||||
}
|
||||
.cell.firstday h1 {
|
||||
.cell.firstday h1.day-number {
|
||||
color: var(--firstday);
|
||||
text-shadow: 0 0 0.1em var(--strong);
|
||||
}
|
||||
.cell.today h1 {
|
||||
.cell.today h1.day-number {
|
||||
border-radius: 2em;
|
||||
background: var(--today);
|
||||
border: 0.2em solid var(--today);
|
||||
@ -77,16 +83,9 @@ const props = defineProps({
|
||||
.cell.selected {
|
||||
filter: hue-rotate(180deg);
|
||||
}
|
||||
.cell.selected h1 {
|
||||
.cell.selected h1.day-number {
|
||||
color: var(--strong);
|
||||
}
|
||||
.lunar-phase {
|
||||
position: absolute;
|
||||
top: 0.5em;
|
||||
right: 0.2em;
|
||||
font-size: 0.8em;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.cell.holiday {
|
||||
background-image: linear-gradient(
|
||||
135deg,
|
||||
@ -103,27 +102,32 @@ const props = defineProps({
|
||||
);
|
||||
}
|
||||
}
|
||||
.cell.holiday h1 {
|
||||
.cell.holiday h1.day-number {
|
||||
/* Slight emphasis without forcing a specific hue */
|
||||
color: var(--holiday);
|
||||
text-shadow: 0 0 0.3em rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
.holiday-info {
|
||||
position: absolute;
|
||||
bottom: 0.1em;
|
||||
left: 0.1em;
|
||||
right: 0.1em;
|
||||
line-height: 1;
|
||||
overflow: hidden;
|
||||
font-size: clamp(1.2vw, 0.6em, 1em);
|
||||
.lunar-phase {
|
||||
grid-area: lunar-phase;
|
||||
align-self: start;
|
||||
justify-self: end;
|
||||
margin-top: 0.5em;
|
||||
margin-inline-end: 0.2em;
|
||||
font-size: 0.8em;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.holiday-name {
|
||||
display: block;
|
||||
color: var(--holiday-label);
|
||||
padding: 0.15em 0.35em 0.15em 0.25em;
|
||||
.holiday-info {
|
||||
grid-area: holiday-info;
|
||||
align-self: end;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
color: var(--holiday-label);
|
||||
font-size: clamp(1.2vw, 0.6em, 1em);
|
||||
line-height: 1;
|
||||
padding-inline: 0.15em;
|
||||
padding-block-end: 0.05em;
|
||||
pointer-events: auto;
|
||||
}
|
||||
</style>
|
||||
|
@ -87,7 +87,7 @@ const vwm = createVirtualWeekManager({
|
||||
contentHeight,
|
||||
})
|
||||
const visibleWeeks = vwm.visibleWeeks
|
||||
const { scheduleWindowUpdate, resetWeeks, refreshEvents } = vwm
|
||||
const { scheduleWindowUpdate, resetWeeks, refreshEvents, refreshHolidays } = vwm
|
||||
|
||||
// Scroll managers (after scheduleWindowUpdate available)
|
||||
const scrollManager = createScrollManager({ viewport, scheduleRebuild: scheduleWindowUpdate })
|
||||
@ -98,8 +98,7 @@ const weekColumnScrollManager = createWeekColumnScrollManager({
|
||||
contentHeight,
|
||||
setScrollTop,
|
||||
})
|
||||
const { isWeekColDragging, handleWeekColMouseDown, handlePointerLockChange } =
|
||||
weekColumnScrollManager
|
||||
const { handleWeekColMouseDown, handlePointerLockChange } = weekColumnScrollManager
|
||||
const monthScrollManager = createMonthScrollManager({
|
||||
viewport,
|
||||
viewportHeight,
|
||||
@ -160,6 +159,25 @@ function clearSelection() {
|
||||
selection.value = { startDate: null, dayCount: 0 }
|
||||
}
|
||||
|
||||
// React to holiday config changes: rebuild or refresh holidays
|
||||
watch(
|
||||
() => [
|
||||
calendarStore.config.holidays.enabled,
|
||||
calendarStore.config.holidays.country,
|
||||
calendarStore.config.holidays.state,
|
||||
calendarStore.config.holidays.region,
|
||||
],
|
||||
(_newVals, _oldVals) => {
|
||||
// If weeks already built, just refresh holiday info
|
||||
if (visibleWeeks.value.length) {
|
||||
refreshHolidays('config-change')
|
||||
} else {
|
||||
resetWeeks('holiday-config-change')
|
||||
}
|
||||
},
|
||||
{ deep: false },
|
||||
)
|
||||
|
||||
function startDrag(dateStr) {
|
||||
dateStr = normalizeDate(dateStr)
|
||||
if (calendarStore.config.select_days === 0) return
|
||||
@ -294,15 +312,6 @@ function calculateSelection(anchorStr, otherStr) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------- Week label column drag scrolling ----------------
|
||||
function getWeekLabelRect() {
|
||||
// Prefer header year label width as stable reference
|
||||
const headerYear = document.querySelector('.calendar-header .year-label')
|
||||
if (headerYear) return headerYear.getBoundingClientRect()
|
||||
const weekLabel = viewport.value?.querySelector('.week-row .week-label')
|
||||
return weekLabel ? weekLabel.getBoundingClientRect() : null
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
computeRowHeight()
|
||||
calendarStore.updateCurrentDate()
|
||||
@ -376,8 +385,6 @@ const handleEventClick = (payload) => {
|
||||
emit('edit-event', payload)
|
||||
}
|
||||
|
||||
// header year change delegated to manager
|
||||
|
||||
// Heuristic: rotate month label (180deg) only for predominantly Latin text.
|
||||
// We explicitly avoid locale detection; rely solely on characters present.
|
||||
// Disable rotation if any CJK Unified Ideograph or Compatibility Ideograph appears.
|
||||
@ -402,7 +409,7 @@ watch(
|
||||
},
|
||||
)
|
||||
|
||||
// Watch lightweight mutation counter only (not deep events map) and rebuild lazily
|
||||
// Event changes
|
||||
watch(
|
||||
() => calendarStore.events,
|
||||
() => {
|
||||
|
@ -101,12 +101,12 @@ onBeforeUnmount(() => {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
margin-right: 1.5rem;
|
||||
margin-inline-end: 2rem;
|
||||
}
|
||||
.toggle-btn {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
inset-inline-end: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--muted);
|
||||
@ -157,7 +157,6 @@ onBeforeUnmount(() => {
|
||||
color: var(--muted);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-right: 0.6rem;
|
||||
cursor: pointer;
|
||||
font-size: 1.5rem;
|
||||
line-height: 1;
|
||||
|
@ -371,6 +371,28 @@ export function createVirtualWeekManager({
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh holiday data for currently visible weeks without rebuilding structure
|
||||
function refreshHolidays(reason = 'holidays-refresh') {
|
||||
if (!visibleWeeks.value.length) return
|
||||
const enabled = calendarStore.config.holidays.enabled
|
||||
if (enabled) calendarStore._ensureHolidaysInitialized?.()
|
||||
for (const week of visibleWeeks.value) {
|
||||
for (const day of week.days) {
|
||||
if (enabled) {
|
||||
const holiday = getHolidayForDate(day.date)
|
||||
;((day.holiday = holiday), (day.isHoliday = holiday !== null))
|
||||
} else {
|
||||
day.holiday = null
|
||||
day.isHoliday = false
|
||||
}
|
||||
}
|
||||
}
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug('[VirtualWeeks] refreshHolidays', reason, { weeks: visibleWeeks.value.length })
|
||||
}
|
||||
}
|
||||
|
||||
function goToToday() {
|
||||
const top = addDays(new Date(calendarStore.now), -21)
|
||||
const targetWeekIndex = getWeekIndex(top)
|
||||
@ -391,6 +413,7 @@ export function createVirtualWeekManager({
|
||||
resetWeeks,
|
||||
updateVisibleWeeks,
|
||||
refreshEvents,
|
||||
refreshHolidays,
|
||||
getWeekIndex,
|
||||
getFirstDayForVirtualWeek,
|
||||
goToToday,
|
||||
|
Loading…
x
Reference in New Issue
Block a user