diff --git a/src/app/globals.css b/src/app/globals.css index de99d2a..74c4867 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -789,6 +789,42 @@ scroll-behavior: smooth; } +/* Prevent native pull-to-refresh on mobile browsers */ +html, +body { + overscroll-behavior-y: none; + overscroll-behavior: none; + -webkit-overflow-scrolling: touch; +} + +/* Prevent pull-to-refresh on main content areas */ +main, +#main-content { + overscroll-behavior-y: none; + overscroll-behavior: none; +} + +/* Prevent unwanted touch behaviors that can trigger reloads */ +@media (max-width: 768px) { + body { + touch-action: pan-y; + -webkit-touch-callout: none; + -webkit-user-select: none; + user-select: none; + } + + /* Allow text selection in content areas */ + main, + article, + section, + p, + span, + div[contenteditable] { + -webkit-user-select: text; + user-select: text; + } +} + /* Prevent zoom on input focus on iOS */ @media screen and (max-width: 767px) { input[type="text"], diff --git a/src/components/features/dashboard/dashboard-view.tsx b/src/components/features/dashboard/dashboard-view.tsx index 8d332f3..33e9e9d 100644 --- a/src/components/features/dashboard/dashboard-view.tsx +++ b/src/components/features/dashboard/dashboard-view.tsx @@ -510,9 +510,10 @@ export default function GuestDashboard() { }, [isAttendingGuest]); // Pull to refresh hook with safe destructuring + // Increased threshold to prevent accidental triggers during normal scrolling const pullToRefreshResult = usePullToRefresh(() => { window.location.reload(); - }); + }, { pullThreshold: 120 }); // Increased from default 80 to 120px const pullContainerRef = pullToRefreshResult?.containerRef ?? React.useRef(null); const isRefreshing = pullToRefreshResult?.isRefreshing ?? false; diff --git a/src/hooks/use-mobile-gestures.ts b/src/hooks/use-mobile-gestures.ts index d73d759..0ac1f2e 100644 --- a/src/hooks/use-mobile-gestures.ts +++ b/src/hooks/use-mobile-gestures.ts @@ -101,10 +101,13 @@ export function useMobileGestures( } // Check for pull-to-refresh (only at top of scrollable area) + // Only enable if we're at the very top (within 5px) to prevent accidental triggers if (handlers.onPullToRefresh && elementRef.current) { const scrollTop = getEffectiveScrollTop(); - if (scrollTop <= 0) { + if (scrollTop <= 5) { isPullingRef.current = true; + } else { + isPullingRef.current = false; } } }, [handlers, longPressDelay, elementRef, shouldIgnoreTarget, getEffectiveScrollTop]); @@ -121,11 +124,34 @@ export function useMobileGestures( if (Math.abs(deltaX) > 10 || Math.abs(deltaY) > 10) { clearLongPress(); } + + // Prevent native pull-to-refresh when we're handling it + if (isPullingRef.current && handlers.onPullToRefresh && deltaY > 0) { + const currentScrollTop = getEffectiveScrollTop(); + if (currentScrollTop <= 10) { + e.preventDefault(); + } + } - // Handle pull-to-refresh - if (isPullingRef.current && handlers.onPullToRefresh && deltaY > pullThreshold) { - handlers.onPullToRefresh(); - isPullingRef.current = false; + // Handle pull-to-refresh - require more deliberate gesture + // Only trigger if: + // 1. We're still at the top (within 10px) + // 2. Vertical movement is significantly more than horizontal (to avoid diagonal swipes) + // 3. Movement exceeds threshold + if (isPullingRef.current && handlers.onPullToRefresh) { + const currentScrollTop = getEffectiveScrollTop(); + const isVerticalGesture = Math.abs(deltaY) > Math.abs(deltaX) * 1.5; // Vertical must be 1.5x horizontal + const isStillAtTop = currentScrollTop <= 10; + + if (isStillAtTop && isVerticalGesture && deltaY > pullThreshold) { + // Prevent default to stop native pull-to-refresh + e.preventDefault(); + handlers.onPullToRefresh(); + isPullingRef.current = false; + } else if (!isStillAtTop || Math.abs(deltaX) > 30) { + // Cancel if we've scrolled away or moved horizontally too much + isPullingRef.current = false; + } } }, [handlers, pullThreshold, clearLongPress, shouldIgnoreTarget]);