Improved year scroller, removed Numeric outline for simpler forms.

This commit is contained in:
Leo Vasanko 2025-08-22 19:33:39 -06:00
parent 26b2e983ed
commit 62f9097ac0
3 changed files with 44 additions and 26 deletions

View File

@ -24,9 +24,12 @@ const topVirtualWeek = computed(() => {
}) })
const currentYear = computed(() => { const currentYear = computed(() => {
const firstDay = new Date(baseDate) const weekStart = new Date(baseDate)
firstDay.setDate(firstDay.getDate() + topVirtualWeek.value * 7) weekStart.setDate(weekStart.getDate() + topVirtualWeek.value * 7)
return isoWeekInfo(firstDay).year // 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) { function virtualWeekOf(d) {
@ -36,30 +39,41 @@ function virtualWeekOf(d) {
return Math.floor((fd - baseDate) / WEEK_MS) 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) { function changeYear(y) {
if (y == null) return if (y == null) return
y = Math.round(Math.max(calendarStore.minYear, Math.min(calendarStore.maxYear, y))) y = Math.round(Math.max(calendarStore.minYear, Math.min(calendarStore.maxYear, y)))
if (y === currentYear.value) return if (y === currentYear.value) return
// Current ISO week + intra-week fraction
const vw = topVirtualWeek.value const vw = topVirtualWeek.value
// Fraction within current row for smooth vertical position preservation
const weekStartScroll = (vw - props.minVirtualWeek) * props.rowHeight const weekStartScroll = (vw - props.minVirtualWeek) * props.rowHeight
const frac = Math.max(0, Math.min(1, (props.scrollTop - weekStartScroll) / props.rowHeight)) const frac = Math.max(0, Math.min(1, (props.scrollTop - weekStartScroll) / props.rowHeight))
const weekStart = new Date(baseDate) // Derive current ISO week via anchor Thursday
weekStart.setDate(weekStart.getDate() + vw * 7) const curCalWeekStart = new Date(baseDate)
let { week: isoW } = isoWeekInfo(weekStart) curCalWeekStart.setDate(curCalWeekStart.getDate() + vw * 7)
// Build Monday of isoW in target year (Jan 4 trick) const curAnchorThu = new Date(curCalWeekStart)
const jan4 = new Date(y, 0, 4) curAnchorThu.setDate(curAnchorThu.getDate() + ((4 - curAnchorThu.getDay() + 7) % 7))
const jan4Mon = new Date(jan4) let { week: isoW } = isoWeekInfo(curAnchorThu)
jan4Mon.setDate(jan4.getDate() - ((jan4.getDay() || 7) - 1)) // Monday of week 1 // Build Monday of that ISO week in target year; fallback if week absent (53)
let monday = new Date(jan4Mon) let weekMon = isoWeekMonday(y, isoW)
monday.setDate(monday.getDate() + (isoW - 1) * 7) if (isoWeekInfo(weekMon).year !== y) {
// If overflow (week 53 not present) step back isoW--
if (isoWeekInfo(monday).year !== y) { weekMon = isoWeekMonday(y, isoW)
monday.setDate(monday.getDate() - 7)
} }
const shift = (monday.getDay() - calendarStore.config.first_day + 7) % 7 // Align to configured first day
monday.setDate(monday.getDate() - shift) const shift = (weekMon.getDay() - calendarStore.config.first_day + 7) % 7
const targetVW = virtualWeekOf(monday) const calWeekStart = new Date(weekMon)
calWeekStart.setDate(calWeekStart.getDate() - shift)
const targetVW = virtualWeekOf(calWeekStart)
let scrollTop = (targetVW - props.minVirtualWeek) * props.rowHeight + frac * props.rowHeight let scrollTop = (targetVW - props.minVirtualWeek) * props.rowHeight + frac * props.rowHeight
if (Math.abs(scrollTop - props.scrollTop) < 0.5) scrollTop += 0.25 * props.rowHeight if (Math.abs(scrollTop - props.scrollTop) < 0.5) scrollTop += 0.25 * props.rowHeight
emit('year-change', { year: y, scrollTop }) emit('year-change', { year: y, scrollTop })

View File

@ -130,7 +130,10 @@ function getFirstDayForVirtualWeek(virtualWeek) {
function createWeek(virtualWeek) { function createWeek(virtualWeek) {
const firstDay = getFirstDayForVirtualWeek(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 days = []
const cur = new Date(firstDay) const cur = new Date(firstDay)
let hasFirst = false let hasFirst = false

View File

@ -12,7 +12,7 @@
tabindex="0" tabindex="0"
@pointerdown="onPointerDown" @pointerdown="onPointerDown"
@keydown="onKey" @keydown="onKey"
@wheel.prevent="onWheel" @wheel.prevent="onWheel"
> >
<span class="value" :title="String(current)">{{ display }}</span> <span class="value" :title="String(current)">{{ display }}</span>
</div> </div>
@ -246,16 +246,17 @@ function onWheel(e) {
justify-content: center; justify-content: center;
padding: 0 0.4rem; padding: 0 0.4rem;
gap: 0.25rem; gap: 0.25rem;
border: 1px solid var(--input-border, var(--muted)); border: 1px solid transparent;
background: var(--panel-alt); background: transparent;
border-radius: 0.4rem; border-radius: 0.4rem;
min-height: 1.8rem; min-height: 1.8rem;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
touch-action: none; /* allow custom drag without scrolling */ touch-action: none;
} }
.mini-stepper.drag-mode:focus-visible { .mini-stepper.drag-mode:focus-visible {
outline: 2px solid var(--input-focus, #2563eb); border-color: var(--input-border, var(--muted));
outline-offset: 2px; box-shadow: 0 0 0 2px var(--input-focus, #2563eb);
outline: none;
} }
.mini-stepper.drag-mode .value { .mini-stepper.drag-mode .value {
font-weight: 600; font-weight: 600;