fix: restore sticky day headers on mobile

Non-passive touchmove listener was permanently attached to document,
breaking browser scroll optimizations. Now only attached during active
long-press and removed immediately after.

v1.73.1

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
mARTin 2026-03-25 02:38:02 +01:00
parent 98a47a62de
commit dd16e5604a
2 changed files with 26 additions and 10 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "my-weekly-todo-list", "name": "my-weekly-todo-list",
"version": "1.73.0", "version": "1.73.1",
"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": {

View File

@ -3969,8 +3969,25 @@ export default function WeeklyView() {
}; };
}, []); // eslint-disable-line react-hooks/exhaustive-deps }, []); // eslint-disable-line react-hooks/exhaustive-deps
// Touch long-press drag-to-create: touchmove + touchend on document // Touch long-press drag-to-create: touchmove + touchend handlers
useEffect(() => { // Stored as refs so the onTouchStart can attach/detach them only when needed
// (avoids a permanent non-passive touchmove listener that breaks scroll/sticky)
const touchMoveHandlerRef = useRef<((e: TouchEvent) => void) | null>(null);
const touchEndHandlerRef = useRef<(() => void) | null>(null);
const cleanupTouchListeners = useCallback(() => {
if (touchMoveHandlerRef.current) {
document.removeEventListener('touchmove', touchMoveHandlerRef.current);
touchMoveHandlerRef.current = null;
}
if (touchEndHandlerRef.current) {
document.removeEventListener('touchend', touchEndHandlerRef.current);
document.removeEventListener('touchcancel', touchEndHandlerRef.current);
touchEndHandlerRef.current = null;
}
}, []);
const attachTouchListeners = useCallback(() => {
cleanupTouchListeners();
const handleTouchMove = (e: TouchEvent) => { const handleTouchMove = (e: TouchEvent) => {
const lp = touchLongPressRef.current; const lp = touchLongPressRef.current;
if (!lp) return; if (!lp) return;
@ -3981,6 +3998,7 @@ export default function WeeklyView() {
if (Math.abs(touch.clientX - lp.startX) > 10 || Math.abs(touch.clientY - lp.startY) > 10) { if (Math.abs(touch.clientX - lp.startX) > 10 || Math.abs(touch.clientY - lp.startY) > 10) {
clearTimeout(lp.timerId); clearTimeout(lp.timerId);
touchLongPressRef.current = null; touchLongPressRef.current = null;
cleanupTouchListeners();
} }
return; return;
} }
@ -4010,6 +4028,7 @@ export default function WeeklyView() {
clearTimeout(lp.timerId); clearTimeout(lp.timerId);
touchLongPressRef.current = null; touchLongPressRef.current = null;
} }
cleanupTouchListeners();
const drag = slotDragRef.current; const drag = slotDragRef.current;
slotDragRef.current = null; slotDragRef.current = null;
@ -4038,16 +4057,12 @@ export default function WeeklyView() {
}); });
}; };
// Use passive: false for touchmove so we can preventDefault after long-press activates touchMoveHandlerRef.current = handleTouchMove;
touchEndHandlerRef.current = handleTouchEnd;
document.addEventListener('touchmove', handleTouchMove, { passive: false }); document.addEventListener('touchmove', handleTouchMove, { passive: false });
document.addEventListener('touchend', handleTouchEnd); document.addEventListener('touchend', handleTouchEnd);
document.addEventListener('touchcancel', handleTouchEnd); document.addEventListener('touchcancel', handleTouchEnd);
return () => { }, [cleanupTouchListeners]);
document.removeEventListener('touchmove', handleTouchMove);
document.removeEventListener('touchend', handleTouchEnd);
document.removeEventListener('touchcancel', handleTouchEnd);
};
}, []); // eslint-disable-line react-hooks/exhaustive-deps
// Handle recurring event drag confirm (this/all) // Handle recurring event drag confirm (this/all)
const handleRecurringDragConfirm = async (editMode: 'this' | 'future' | 'all') => { const handleRecurringDragConfirm = async (editMode: 'this' | 'future' | 'all') => {
@ -7980,6 +7995,7 @@ export default function WeeklyView() {
slot: slotName, slot: slotName,
activated: false, activated: false,
}; };
attachTouchListeners();
}} }}
onDragOver={(e) => onDragOver={(e) =>
!isProtected && !isOccupiedByTask && !isProtected && !isOccupiedByTask &&