fix: iOS scroll blocked by enforcement interval + missing touch-action on slots
Three root causes fixed: 1. Scroll enforcement interval (every 50ms for 2s) was the main blocker: The setInterval forced gridRef.scrollTop on every frame, making iOS think the page was always scrolling programmatically. User touches during those 2 seconds were overridden. The header appeared 'stuck' because the view was frozen. Replaced with a single setTimeout scroll (300ms on first load, 50ms on subsequent). Added history.scrollRestoration='manual' so Safari doesn't fight us with scroll position restoration. 2. touch-action: pan-y was only on .time-grid-wrapper — NOT inherited by CSS. Slot child elements had touch-action: auto (default), so iOS still waited for JS before committing scroll on day column touches. Added touch-action: pan-y directly to .time-slots-container (the actual touched element area). Time column labels have no JS handlers so iOS scrolled them freely — this explains why time column worked but day columns didn't. 3. align-items: start on .weekly-days-grid was wrong (added in last commit). Removed — CSS grid default stretch is correct. v1.77.4
This commit is contained in:
parent
aef7c291b4
commit
4c1ef5019e
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "my-weekly-todo-list",
|
"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",
|
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -3167,8 +3167,6 @@ h3 {
|
|||||||
.time-grid-wrapper .weekly-days-grid {
|
.time-grid-wrapper .weekly-days-grid {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
/* Align grid items to the top so sticky headers work correctly */
|
|
||||||
align-items: start;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-grid-wrapper .weekly-day-column {
|
.time-grid-wrapper .weekly-day-column {
|
||||||
@ -3190,6 +3188,9 @@ h3 {
|
|||||||
.time-grid-wrapper .time-slots-container {
|
.time-grid-wrapper .time-slots-container {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
flex: 1;
|
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 */
|
/* All-Day Events Section */
|
||||||
|
|||||||
@ -3110,34 +3110,28 @@ export default function WeeklyView() {
|
|||||||
}
|
}
|
||||||
}, [currentWeekStart, session, fetchCalendarEvents]);
|
}, [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)
|
// Scroll to preferred start hour (initial load + when user changes startHour)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isLoading) {
|
if (!isLoading) {
|
||||||
const performScroll = () => {
|
|
||||||
const slotsPerHour = 60 / cellDuration;
|
const slotsPerHour = 60 / cellDuration;
|
||||||
const slotHeight = getSlotHeight(cellDuration);
|
const slotHeight = getSlotHeight(cellDuration);
|
||||||
const scrollOffset = workingHoursStart * slotsPerHour * slotHeight;
|
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;
|
if (gridRef.current) gridRef.current.scrollTop = scrollOffset;
|
||||||
|
|
||||||
intendedScrollTop.current = 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;
|
isInitialScrollDone.current = true;
|
||||||
}, 2000);
|
}, delay);
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Delay slightly to ensure layout is stable
|
|
||||||
const timer = setTimeout(performScroll, isInitialScrollDone.current ? 50 : 500);
|
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}
|
}
|
||||||
}, [isLoading, workingHoursStart, cellDuration]);
|
}, [isLoading, workingHoursStart, cellDuration]);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user