From 62f9097ac0d5e61864bc872e2da248a0462d66a7 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Fri, 22 Aug 2025 19:33:39 -0600 Subject: [PATCH] Improved year scroller, removed Numeric outline for simpler forms. --- src/components/CalendarHeader.vue | 52 ++++++++++++++++++++----------- src/components/CalendarView.vue | 5 ++- src/components/Numeric.vue | 13 ++++---- 3 files changed, 44 insertions(+), 26 deletions(-) diff --git a/src/components/CalendarHeader.vue b/src/components/CalendarHeader.vue index 17d90b7..afacf15 100644 --- a/src/components/CalendarHeader.vue +++ b/src/components/CalendarHeader.vue @@ -24,9 +24,12 @@ const topVirtualWeek = computed(() => { }) const currentYear = computed(() => { - const firstDay = new Date(baseDate) - firstDay.setDate(firstDay.getDate() + topVirtualWeek.value * 7) - return isoWeekInfo(firstDay).year + const weekStart = new Date(baseDate) + weekStart.setDate(weekStart.getDate() + topVirtualWeek.value * 7) + // ISO anchor: Thursday of current calendar week + const anchor = new Date(weekStart) + anchor.setDate(anchor.getDate() + ((4 - anchor.getDay() + 7) % 7)) + return isoWeekInfo(anchor).year }) function virtualWeekOf(d) { @@ -36,30 +39,41 @@ function virtualWeekOf(d) { return Math.floor((fd - baseDate) / WEEK_MS) } +function isoWeekMonday(isoYear, isoWeek) { + // Monday of ISO week 1 + const jan4 = new Date(isoYear, 0, 4) + const week1Mon = new Date(jan4) + week1Mon.setDate(week1Mon.getDate() - ((week1Mon.getDay() + 6) % 7)) + const target = new Date(week1Mon) + target.setDate(target.getDate() + (isoWeek - 1) * 7) + return target +} + function changeYear(y) { if (y == null) return y = Math.round(Math.max(calendarStore.minYear, Math.min(calendarStore.maxYear, y))) if (y === currentYear.value) return - // Current ISO week + intra-week fraction const vw = topVirtualWeek.value + // Fraction within current row for smooth vertical position preservation const weekStartScroll = (vw - props.minVirtualWeek) * props.rowHeight const frac = Math.max(0, Math.min(1, (props.scrollTop - weekStartScroll) / props.rowHeight)) - const weekStart = new Date(baseDate) - weekStart.setDate(weekStart.getDate() + vw * 7) - let { week: isoW } = isoWeekInfo(weekStart) - // Build Monday of isoW in target year (Jan 4 trick) - const jan4 = new Date(y, 0, 4) - const jan4Mon = new Date(jan4) - jan4Mon.setDate(jan4.getDate() - ((jan4.getDay() || 7) - 1)) // Monday of week 1 - let monday = new Date(jan4Mon) - monday.setDate(monday.getDate() + (isoW - 1) * 7) - // If overflow (week 53 not present) step back - if (isoWeekInfo(monday).year !== y) { - monday.setDate(monday.getDate() - 7) + // Derive current ISO week via anchor Thursday + const curCalWeekStart = new Date(baseDate) + curCalWeekStart.setDate(curCalWeekStart.getDate() + vw * 7) + const curAnchorThu = new Date(curCalWeekStart) + curAnchorThu.setDate(curAnchorThu.getDate() + ((4 - curAnchorThu.getDay() + 7) % 7)) + let { week: isoW } = isoWeekInfo(curAnchorThu) + // Build Monday of that ISO week in target year; fallback if week absent (53) + let weekMon = isoWeekMonday(y, isoW) + if (isoWeekInfo(weekMon).year !== y) { + isoW-- + weekMon = isoWeekMonday(y, isoW) } - const shift = (monday.getDay() - calendarStore.config.first_day + 7) % 7 - monday.setDate(monday.getDate() - shift) - const targetVW = virtualWeekOf(monday) + // Align to configured first day + const shift = (weekMon.getDay() - calendarStore.config.first_day + 7) % 7 + const calWeekStart = new Date(weekMon) + calWeekStart.setDate(calWeekStart.getDate() - shift) + const targetVW = virtualWeekOf(calWeekStart) let scrollTop = (targetVW - props.minVirtualWeek) * props.rowHeight + frac * props.rowHeight if (Math.abs(scrollTop - props.scrollTop) < 0.5) scrollTop += 0.25 * props.rowHeight emit('year-change', { year: y, scrollTop }) diff --git a/src/components/CalendarView.vue b/src/components/CalendarView.vue index 63b0f87..036cc63 100644 --- a/src/components/CalendarView.vue +++ b/src/components/CalendarView.vue @@ -130,7 +130,10 @@ function getFirstDayForVirtualWeek(virtualWeek) { function createWeek(virtualWeek) { const firstDay = getFirstDayForVirtualWeek(virtualWeek) - const weekNumber = isoWeekInfo(firstDay).week + // ISO week number should be based on the Thursday of this week (anchor) to avoid off-by-one when week starts Sunday or other days + const isoAnchor = new Date(firstDay) + isoAnchor.setDate(isoAnchor.getDate() + ((4 - isoAnchor.getDay() + 7) % 7)) + const weekNumber = isoWeekInfo(isoAnchor).week const days = [] const cur = new Date(firstDay) let hasFirst = false diff --git a/src/components/Numeric.vue b/src/components/Numeric.vue index 3a8baf2..c2085dd 100644 --- a/src/components/Numeric.vue +++ b/src/components/Numeric.vue @@ -12,7 +12,7 @@ tabindex="0" @pointerdown="onPointerDown" @keydown="onKey" - @wheel.prevent="onWheel" + @wheel.prevent="onWheel" > {{ display }} @@ -246,16 +246,17 @@ function onWheel(e) { justify-content: center; padding: 0 0.4rem; gap: 0.25rem; - border: 1px solid var(--input-border, var(--muted)); - background: var(--panel-alt); + border: 1px solid transparent; + background: transparent; border-radius: 0.4rem; min-height: 1.8rem; font-variant-numeric: tabular-nums; - touch-action: none; /* allow custom drag without scrolling */ + touch-action: none; } .mini-stepper.drag-mode:focus-visible { - outline: 2px solid var(--input-focus, #2563eb); - outline-offset: 2px; + border-color: var(--input-border, var(--muted)); + box-shadow: 0 0 0 2px var(--input-focus, #2563eb); + outline: none; } .mini-stepper.drag-mode .value { font-weight: 600;