diff --git a/package.json b/package.json index fd1e623..ea1a727 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.77.3", + "version": "1.77.4", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view", "main": "index.js", "scripts": { diff --git a/src/app/globals.css b/src/app/globals.css index d531c86..73ee5ad 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -3167,8 +3167,6 @@ h3 { .time-grid-wrapper .weekly-days-grid { flex: 1; overflow: visible; - /* Align grid items to the top so sticky headers work correctly */ - align-items: start; } .time-grid-wrapper .weekly-day-column { @@ -3190,6 +3188,9 @@ h3 { .time-grid-wrapper .time-slots-container { overflow: visible; flex: 1; + /* touch-action is NOT inherited — must be set on the actual touched element. + pan-y tells iOS: vertical swipe = scroll immediately, don't wait for JS handlers */ + touch-action: pan-y; } /* All-Day Events Section */ diff --git a/src/components/WeeklyView.tsx b/src/components/WeeklyView.tsx index 8b90d7e..e75d4d4 100644 --- a/src/components/WeeklyView.tsx +++ b/src/components/WeeklyView.tsx @@ -3110,34 +3110,28 @@ export default function WeeklyView() { } }, [currentWeekStart, session, fetchCalendarEvents]); + // Disable browser scroll restoration so Safari doesn't fight our initial scroll position + useEffect(() => { + if (typeof window !== 'undefined' && window.history.scrollRestoration) { + window.history.scrollRestoration = 'manual'; + } + }, []); + // Scroll to preferred start hour (initial load + when user changes startHour) useEffect(() => { if (!isLoading) { - const performScroll = () => { - const slotsPerHour = 60 / cellDuration; - const slotHeight = getSlotHeight(cellDuration); - const scrollOffset = workingHoursStart * slotsPerHour * slotHeight; + const slotsPerHour = 60 / cellDuration; + const slotHeight = getSlotHeight(cellDuration); + const scrollOffset = workingHoursStart * slotsPerHour * slotHeight; - // Single scroll container: gridRef points to time-grid-wrapper + // Single scroll — no interval, no enforcement loop. + // We set scrollRestoration='manual' so the browser won't override this. + const delay = isInitialScrollDone.current ? 50 : 300; + const timer = setTimeout(() => { if (gridRef.current) gridRef.current.scrollTop = scrollOffset; - intendedScrollTop.current = scrollOffset; - - // On initial load, re-enforce for 2s to fight browser auto-scroll restoration - if (!isInitialScrollDone.current) { - const interval = setInterval(() => { - if (gridRef.current) gridRef.current.scrollTop = scrollOffset; - }, 50); - - setTimeout(() => { - clearInterval(interval); - isInitialScrollDone.current = true; - }, 2000); - } - }; - - // Delay slightly to ensure layout is stable - const timer = setTimeout(performScroll, isInitialScrollDone.current ? 50 : 500); + isInitialScrollDone.current = true; + }, delay); return () => clearTimeout(timer); } }, [isLoading, workingHoursStart, cellDuration]);