feat: fade/blur header text on hover + fix iOS scroll freeze during calendar fetch
- Header center (KW/year/goal/quote) now fades to 20% opacity and blurs on hover so the controls behind it are readable on smaller screens - Fix iOS scroll freeze: touchmove listener on grid slots was non-passive, causing Safari to wait ~20s (iCloud sync duration) before allowing scroll; now attaches a passive listener initially and only upgrades to non-passive when the long-press drag actually activates (after 500ms hold) v1.77.0
This commit is contained in:
parent
bf61733317
commit
517798ce6c
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "my-weekly-todo-list",
|
"name": "my-weekly-todo-list",
|
||||||
"version": "1.76.2",
|
"version": "1.77.0",
|
||||||
"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": {
|
||||||
|
|||||||
@ -4110,38 +4110,34 @@ export default function WeeklyView() {
|
|||||||
// Stored as refs so the onTouchStart can attach/detach them only when needed
|
// Stored as refs so the onTouchStart can attach/detach them only when needed
|
||||||
// (avoids a permanent non-passive touchmove listener that breaks scroll/sticky)
|
// (avoids a permanent non-passive touchmove listener that breaks scroll/sticky)
|
||||||
const touchMoveHandlerRef = useRef<((e: TouchEvent) => void) | null>(null);
|
const touchMoveHandlerRef = useRef<((e: TouchEvent) => void) | null>(null);
|
||||||
|
const touchMoveDragHandlerRef = useRef<((e: TouchEvent) => void) | null>(null);
|
||||||
const touchEndHandlerRef = useRef<(() => void) | null>(null);
|
const touchEndHandlerRef = useRef<(() => void) | null>(null);
|
||||||
const cleanupTouchListeners = useCallback(() => {
|
const cleanupTouchListeners = useCallback(() => {
|
||||||
if (touchMoveHandlerRef.current) {
|
if (touchMoveHandlerRef.current) {
|
||||||
document.removeEventListener('touchmove', touchMoveHandlerRef.current);
|
document.removeEventListener('touchmove', touchMoveHandlerRef.current);
|
||||||
touchMoveHandlerRef.current = null;
|
touchMoveHandlerRef.current = null;
|
||||||
}
|
}
|
||||||
|
if (touchMoveDragHandlerRef.current) {
|
||||||
|
document.removeEventListener('touchmove', touchMoveDragHandlerRef.current);
|
||||||
|
touchMoveDragHandlerRef.current = null;
|
||||||
|
}
|
||||||
if (touchEndHandlerRef.current) {
|
if (touchEndHandlerRef.current) {
|
||||||
document.removeEventListener('touchend', touchEndHandlerRef.current);
|
document.removeEventListener('touchend', touchEndHandlerRef.current);
|
||||||
document.removeEventListener('touchcancel', touchEndHandlerRef.current);
|
document.removeEventListener('touchcancel', touchEndHandlerRef.current);
|
||||||
touchEndHandlerRef.current = null;
|
touchEndHandlerRef.current = null;
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
const attachTouchListeners = useCallback(() => {
|
|
||||||
cleanupTouchListeners();
|
|
||||||
|
|
||||||
const handleTouchMove = (e: TouchEvent) => {
|
|
||||||
const lp = touchLongPressRef.current;
|
|
||||||
if (!lp) return;
|
|
||||||
|
|
||||||
const touch = e.touches[0];
|
|
||||||
if (!lp.activated) {
|
|
||||||
// Before long-press fires: cancel if finger moves too much (scrolling)
|
|
||||||
if (Math.abs(touch.clientX - lp.startX) > 10 || Math.abs(touch.clientY - lp.startY) > 10) {
|
|
||||||
clearTimeout(lp.timerId);
|
|
||||||
touchLongPressRef.current = null;
|
|
||||||
cleanupTouchListeners();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Called when long-press activates: swap passive cancel-listener for non-passive drag-listener
|
||||||
|
const upgradeToDragListeners = useCallback(() => {
|
||||||
|
if (touchMoveHandlerRef.current) {
|
||||||
|
document.removeEventListener('touchmove', touchMoveHandlerRef.current);
|
||||||
|
touchMoveHandlerRef.current = null;
|
||||||
|
}
|
||||||
|
const handleDragMove = (e: TouchEvent) => {
|
||||||
// Long-press active: prevent scrolling and track slot under finger
|
// Long-press active: prevent scrolling and track slot under finger
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
const touch = e.touches[0];
|
||||||
const el = document.elementFromPoint(touch.clientX, touch.clientY);
|
const el = document.elementFromPoint(touch.clientX, touch.clientY);
|
||||||
const slotEl = el?.closest('[data-slot]') as HTMLElement | null;
|
const slotEl = el?.closest('[data-slot]') as HTMLElement | null;
|
||||||
if (slotEl) {
|
if (slotEl) {
|
||||||
@ -4158,6 +4154,24 @@ export default function WeeklyView() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
touchMoveDragHandlerRef.current = handleDragMove;
|
||||||
|
document.addEventListener('touchmove', handleDragMove, { passive: false });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const attachTouchListeners = useCallback(() => {
|
||||||
|
cleanupTouchListeners();
|
||||||
|
|
||||||
|
// Phase 1: passive listener — only cancels long-press if finger moves (no scroll blocking)
|
||||||
|
const handlePassiveTouchMove = (e: TouchEvent) => {
|
||||||
|
const lp = touchLongPressRef.current;
|
||||||
|
if (!lp || lp.activated) return;
|
||||||
|
const touch = e.touches[0];
|
||||||
|
if (Math.abs(touch.clientX - lp.startX) > 10 || Math.abs(touch.clientY - lp.startY) > 10) {
|
||||||
|
clearTimeout(lp.timerId);
|
||||||
|
touchLongPressRef.current = null;
|
||||||
|
cleanupTouchListeners();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleTouchEnd = () => {
|
const handleTouchEnd = () => {
|
||||||
const lp = touchLongPressRef.current;
|
const lp = touchLongPressRef.current;
|
||||||
@ -4194,9 +4208,9 @@ export default function WeeklyView() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
touchMoveHandlerRef.current = handleTouchMove;
|
touchMoveHandlerRef.current = handlePassiveTouchMove;
|
||||||
touchEndHandlerRef.current = handleTouchEnd;
|
touchEndHandlerRef.current = handleTouchEnd;
|
||||||
document.addEventListener('touchmove', handleTouchMove, { passive: false });
|
document.addEventListener('touchmove', handlePassiveTouchMove, { passive: true });
|
||||||
document.addEventListener('touchend', handleTouchEnd);
|
document.addEventListener('touchend', handleTouchEnd);
|
||||||
document.addEventListener('touchcancel', handleTouchEnd);
|
document.addEventListener('touchcancel', handleTouchEnd);
|
||||||
}, [cleanupTouchListeners]);
|
}, [cleanupTouchListeners]);
|
||||||
@ -6827,7 +6841,7 @@ export default function WeeklyView() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* CENTER SECTION: Week/Year, Goal, Focus Mode - Reveal on Hover */}
|
{/* CENTER SECTION: Week/Year, Goal, Focus Mode - Reveal on Hover */}
|
||||||
<div className="flex items-center justify-center gap-6 absolute left-1/2 transform -translate-x-1/2 group z-10 opacity-100" style={{ pointerEvents: "auto" }}>
|
<div className="flex items-center justify-center gap-6 absolute left-1/2 transform -translate-x-1/2 z-0 transition-all duration-300 group-hover:opacity-20 group-hover:blur-[2px]">
|
||||||
{/* Week & Year */}
|
{/* Week & Year */}
|
||||||
<div className="whitespace-nowrap flex items-center gap-2">
|
<div className="whitespace-nowrap flex items-center gap-2">
|
||||||
{/* Clickable Week & Year */}
|
{/* Clickable Week & Year */}
|
||||||
@ -8092,6 +8106,8 @@ export default function WeeklyView() {
|
|||||||
if (touchLongPressRef.current) {
|
if (touchLongPressRef.current) {
|
||||||
touchLongPressRef.current.activated = true;
|
touchLongPressRef.current.activated = true;
|
||||||
}
|
}
|
||||||
|
// Upgrade from passive to non-passive touchmove now that drag is active
|
||||||
|
upgradeToDragListeners();
|
||||||
const ds = `${slotDate.getFullYear()}-${String(slotDate.getMonth() + 1).padStart(2, '0')}-${String(slotDate.getDate()).padStart(2, '0')}`;
|
const ds = `${slotDate.getFullYear()}-${String(slotDate.getMonth() + 1).padStart(2, '0')}-${String(slotDate.getDate()).padStart(2, '0')}`;
|
||||||
setSlotDragSelection({ dateStr: ds, startSlot: slotName, endSlot: slotName });
|
setSlotDragSelection({ dateStr: ds, startSlot: slotName, endSlot: slotName });
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user