Fix RTL handling, holiday names direction and UI details for full page RTL.

This commit is contained in:
Leo Vasanko 2025-08-26 09:09:32 -06:00
parent 9e3f7ddd57
commit 5752855f52
4 changed files with 88 additions and 55 deletions

View File

@ -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>

View File

@ -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,
() => {

View File

@ -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;

View File

@ -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,