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`));