From 5752855f5217005482b12cf2ab3d0f751ab3e316 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Tue, 26 Aug 2025 09:09:32 -0600 Subject: [PATCH 01/17] Fix RTL handling, holiday names direction and UI details for full page RTL. --- src/components/CalendarDay.vue | 78 ++++++++++++++++--------------- src/components/CalendarView.vue | 37 +++++++++------ src/components/HeaderControls.vue | 5 +- src/plugins/virtualWeeks.js | 23 +++++++++ 4 files changed, 88 insertions(+), 55 deletions(-) diff --git a/src/components/CalendarDay.vue b/src/components/CalendarDay.vue index fde7669..0b93324 100644 --- a/src/components/CalendarDay.vue +++ b/src/components/CalendarDay.vue @@ -21,13 +21,10 @@ const props = defineProps({ ]" :data-date="props.day.date" > -

{{ props.day.displayText }}

+

{{ props.day.displayText }}

{{ props.day.lunarPhase }} - -
- - {{ props.day.holiday.name }} - +
+ {{ props.day.holiday.name }}
@@ -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; } diff --git a/src/components/CalendarView.vue b/src/components/CalendarView.vue index b2912e6..387f5d7 100644 --- a/src/components/CalendarView.vue +++ b/src/components/CalendarView.vue @@ -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, () => { diff --git a/src/components/HeaderControls.vue b/src/components/HeaderControls.vue index e61a373..e1ca08a 100644 --- a/src/components/HeaderControls.vue +++ b/src/components/HeaderControls.vue @@ -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; diff --git a/src/plugins/virtualWeeks.js b/src/plugins/virtualWeeks.js index 6d73466..817228f 100644 --- a/src/plugins/virtualWeeks.js +++ b/src/plugins/virtualWeeks.js @@ -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, -- 2.49.0 From 4c967e44010453f205952b1e3f94a4229375d376 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Tue, 26 Aug 2025 10:04:32 -0600 Subject: [PATCH 02/17] Replace favicon with calendar --- public/favicon.ico | Bin 4286 -> 10503 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/public/favicon.ico b/public/favicon.ico index df36fcfb72584e00488330b560ebcf34a41c64c2..4bf8806ab1e3047726fac9bc268ec959995079ef 100644 GIT binary patch literal 10503 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 literal 4286 zcmZQzU}RuqP*4ET3Jfa*7#PGD7#K7d7#I{77#JKFAmR)lAOIqU2X5Zs$bgLL=_@3A zjhlBkf-u-E^l}5#e(vTSjvJvE#HNe&P`g3?jcVTE_#KKtY>*hu-2k;;s(FXw$>tr7 z|DhPf28q$seyH6be^xc`aQp|g8{`HM8zcsjqlp`k?AB}E;dmd(Zjk*T3=#v$(Zmf< z`&pZJIL^XiH^_bv2FZccP&Evoc7y!o(Y(X)10MT9av(JzwN!Hh)P8~H9gaKj*bVYO z2!qss)KbNMsNEp{q&4qw{6&QQAT=PhAUzbj0cyWO^A5*L7iKh$o<<{gf0*zB%ZV*ek6akv4b2c(xQH$d$Mg`s)#4##Kc_BU;D{GXL$3C18c zx;#`5NH53?lHCBcpR;*~<1!4hcRKzrpKSX--p3S-L2Mjh0MZLGgCzT*c7xm<)V#y- z3yS?a9sf71bNHW@Xz@SJ!xW4`Y>*fhH-Pkl%mA51v>TxIi#G3YJcMF5D4p$e{9n{+ z^FPkh6a|CCu-Feuiy$*VW)WpS)NYV_3!8U1{z0*Sr{n+HW%mD*!_C3|hP%PT6f6dk z!{P>z86dMjW)gG*)P9ZT9geq9><0OLyW{`dGAmTOVd3Cm3YKf$4zCkIeurU@Ss*j< z+7Gpxxp{}*uwyWnns+ArO_!|@D?-JmqL!|{JXy){Z+gZlMPypL%f2*-Jv z{(*|Y)q(V2GYe`5$S$z`P`g3ysYPp3f$NrjCM-5(c2Q8ptk?oiME5yuZM-3=hU zAT!X-h1vzO6J$So^A5*3=xSPaIsUJhX8S+E&kP=>F!STRO_wBvm~$isnlXSdhz$~h z$-`)nUXU3ev(U|l+6l5d9HUJID&yBX{7+ATmhrH(1){x7pC(=HT=LB0y}A7)UP8)AS^=9*`Lzvp{CT%!kq-J3)5yHScf)ByT?WW^if zV!|8eX^Mgqe9c%vaSpN<8H2>Ya%k#7X5$(!Tt{e|LXt$|6>oq zKji=a2jLI=|NlQ=hu{Ou|Nnz<1LOby3=ClWkAa~cg#R!w*n{v71_t>L3=I4r{D6Uh a9fS`sFffB~17iat17ib}cK|F4vKj!X^7qaF -- 2.49.0 From 3ef836813050bd5578fc0a958a728d50201f05e4 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Tue, 26 Aug 2025 10:05:25 -0600 Subject: [PATCH 03/17] Improved locale/rtl handling. Whole calendar now flips RTL --- index.html | 12 +++--------- src/App.vue | 3 +++ src/components/CalendarView.vue | 3 ++- src/components/EventOverlay.vue | 1 + src/utils/locale.js | 4 ++++ 5 files changed, 13 insertions(+), 10 deletions(-) create mode 100644 src/utils/locale.js diff --git a/index.html b/index.html index 1506caa..3ceea3f 100644 --- a/index.html +++ b/index.html @@ -1,12 +1,6 @@ - - + Calendar - - - -
- - - + +
\ No newline at end of file diff --git a/src/App.vue b/src/App.vue index 5836a2c..4c0fe18 100644 --- a/src/App.vue +++ b/src/App.vue @@ -3,6 +3,7 @@ import { ref, onMounted, onBeforeUnmount } from 'vue' import CalendarView from './components/CalendarView.vue' import EventDialog from './components/EventDialog.vue' import { useCalendarStore } from './stores/CalendarStore' +import { lang } from './utils/locale' const eventDialog = ref(null) const calendarStore = useCalendarStore() @@ -35,6 +36,8 @@ function handleGlobalKey(e) { onMounted(() => { calendarStore.initializeHolidaysFromConfig() document.addEventListener('keydown', handleGlobalKey, { passive: false }) + // Set document language via shared util + if (lang) document.documentElement.setAttribute('lang', lang) }) onBeforeUnmount(() => { diff --git a/src/components/CalendarView.vue b/src/components/CalendarView.vue index 387f5d7..aa57e68 100644 --- a/src/components/CalendarView.vue +++ b/src/components/CalendarView.vue @@ -13,6 +13,7 @@ import { daysInclusive, addDaysStr, MIN_YEAR, MAX_YEAR } from '@/utils/date' import { toLocalString, fromLocalString, DEFAULT_TZ } from '@/utils/date' import { addDays, differenceInWeeks } from 'date-fns' import { createVirtualWeekManager } from '@/plugins/virtualWeeks' +import { rtl } from '@/utils/locale' const calendarStore = useCalendarStore() const emit = defineEmits(['create-event', 'edit-event']) @@ -433,7 +434,7 @@ window.addEventListener('resize', () => {