diff --git a/js/ui/swipePopoverCalendar.js b/js/ui/swipePopoverCalendar.js index acce50d..74d1f6f 100644 --- a/js/ui/swipePopoverCalendar.js +++ b/js/ui/swipePopoverCalendar.js @@ -54,7 +54,7 @@ export function createSwipePopoverCalendarHTML({ monthLabelTextClass = 'text-[10px] font-semibold leading-none tabular-nums whitespace-nowrap', }) { const weekdayHeader = ` -
+
${weekdays.map((d) => `
${d}
`).join('')}
`; @@ -75,7 +75,7 @@ export function createSwipePopoverCalendarHTML({ return `
-
+
@@ -111,6 +111,7 @@ export function initSwipePopoverCalendar({ theme = DEFAULT_THEME, getMonthAnchor, setMonthAnchor, + canNavigateToMonth, getSelectionKeys, onSelectionCommit, resolveDayState, @@ -188,6 +189,23 @@ export function initSwipePopoverCalendar({ track.style.transform = `translate3d(${restOffset + dx}px, 0, 0)`; }; + const getNavigationTarget = (monthDelta) => { + const anchor = normalizeMonth(getMonthAnchor()); + return startOfMonth(new Date(anchor.getFullYear(), anchor.getMonth() + monthDelta, 1)); + }; + + const canNavigate = (monthDelta) => { + if (typeof canNavigateToMonth !== 'function') return true; + const anchor = normalizeMonth(getMonthAnchor()); + const target = getNavigationTarget(monthDelta); + return canNavigateToMonth(target, { currentMonth: anchor, monthDelta }) !== false; + }; + + const getAllowedDragDx = (dx) => { + if (dx === 0) return 0; + return canNavigate(dx > 0 ? -1 : 1) ? dx : 0; + }; + const getSelectedSet = (previewSelection = null) => { const raw = previewSelection ?? getSelectionKeys(); if (Array.isArray(raw)) return new Set(raw); @@ -259,12 +277,16 @@ export function initSwipePopoverCalendar({ }; const commitNavigation = (monthDelta) => { + if (!canNavigate(monthDelta)) { + setDragTranslate(0, ANIMATION_MS); + setTimeout(() => syncGripVisibility(false), ANIMATION_MS + 20); + return; + } animatingNav = true; const targetDx = monthDelta < 0 ? panelWidth : -panelWidth; setDragTranslate(targetDx, ANIMATION_MS); setTimeout(() => { - const anchor = normalizeMonth(getMonthAnchor()); - setMonthAnchor(startOfMonth(new Date(anchor.getFullYear(), anchor.getMonth() + monthDelta, 1))); + setMonthAnchor(getNavigationTarget(monthDelta)); render(); resetTrackPosition(); animatingNav = false; @@ -352,7 +374,7 @@ export function initSwipePopoverCalendar({ axis = Math.abs(dx) >= Math.abs(dy) ? 'x' : 'y'; if (axis === 'x') syncGripVisibility(true); } - if (axis === 'x') setDragTranslate(dx, 0); + if (axis === 'x') setDragTranslate(getAllowedDragDx(dx), 0); }); const endGesture = (e) => { @@ -360,7 +382,8 @@ export function initSwipePopoverCalendar({ ptrId = null; if (!moved || axis !== 'x') return; const dx = e ? e.clientX - startX : 0; - if (Math.abs(dx) >= SWIPE_THRESHOLD) commitNavigation(dx > 0 ? -1 : 1); + const monthDelta = dx > 0 ? -1 : 1; + if (Math.abs(dx) >= SWIPE_THRESHOLD && canNavigate(monthDelta)) commitNavigation(monthDelta); else { setDragTranslate(0, ANIMATION_MS); setTimeout(() => syncGripVisibility(false), ANIMATION_MS + 20); diff --git a/js/views/Pantry.js b/js/views/Pantry.js index de72688..56872eb 100644 --- a/js/views/Pantry.js +++ b/js/views/Pantry.js @@ -313,6 +313,11 @@ function bindPantryCalendarInteractions() { if (nextAnchor.getTime() < minAnchor.getTime()) return; calendarMonthAnchor = nextAnchor; }, + canNavigateToMonth: (nextMonth) => { + const nextAnchor = startOfMonth(nextMonth); + const minAnchor = startOfMonth(getToday()); + return nextAnchor.getTime() >= minAnchor.getTime(); + }, getSelectionKeys: () => dateKey(horizonEndDate), onSelectionCommit: (selectedKey) => { selectHorizonDate(new Date(`${selectedKey}T00:00:00`));