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",
|
||||
"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",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@ -4110,38 +4110,34 @@ export default function WeeklyView() {
|
||||
// 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 touchMoveDragHandlerRef = 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 (touchMoveDragHandlerRef.current) {
|
||||
document.removeEventListener('touchmove', touchMoveDragHandlerRef.current);
|
||||
touchMoveDragHandlerRef.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 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();
|
||||
// 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;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const handleDragMove = (e: TouchEvent) => {
|
||||
// Long-press active: prevent scrolling and track slot under finger
|
||||
e.preventDefault();
|
||||
const touch = e.touches[0];
|
||||
const el = document.elementFromPoint(touch.clientX, touch.clientY);
|
||||
const slotEl = el?.closest('[data-slot]') as HTMLElement | null;
|
||||
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 lp = touchLongPressRef.current;
|
||||
@ -4194,9 +4208,9 @@ export default function WeeklyView() {
|
||||
});
|
||||
};
|
||||
|
||||
touchMoveHandlerRef.current = handleTouchMove;
|
||||
touchMoveHandlerRef.current = handlePassiveTouchMove;
|
||||
touchEndHandlerRef.current = handleTouchEnd;
|
||||
document.addEventListener('touchmove', handleTouchMove, { passive: false });
|
||||
document.addEventListener('touchmove', handlePassiveTouchMove, { passive: true });
|
||||
document.addEventListener('touchend', handleTouchEnd);
|
||||
document.addEventListener('touchcancel', handleTouchEnd);
|
||||
}, [cleanupTouchListeners]);
|
||||
@ -6827,7 +6841,7 @@ export default function WeeklyView() {
|
||||
</div>
|
||||
|
||||
{/* 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 */}
|
||||
<div className="whitespace-nowrap flex items-center gap-2">
|
||||
{/* Clickable Week & Year */}
|
||||
@ -8092,6 +8106,8 @@ export default function WeeklyView() {
|
||||
if (touchLongPressRef.current) {
|
||||
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')}`;
|
||||
setSlotDragSelection({ dateStr: ds, startSlot: slotName, endSlot: slotName });
|
||||
}, 500);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user