feat: Improve mobile pull-to-refresh handling and prevent accidental triggers

- Add CSS rules to prevent native pull-to-refresh behavior on mobile browsers.
- Update pull-to-refresh hook to increase the threshold for triggering refresh from 80px to 120px.
- Enhance mobile gesture handling to only allow pull-to-refresh at the very top of scrollable areas and require more deliberate gestures to activate it.
- Implement touch-action properties to improve user experience and prevent unwanted touch behaviors.
This commit is contained in:
2026-01-26 23:08:34 +02:00
parent 7dbf4f7ad9
commit 8032a3cee7
3 changed files with 69 additions and 6 deletions

View File

@@ -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"],

View File

@@ -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<HTMLDivElement>(null);
const isRefreshing = pullToRefreshResult?.isRefreshing ?? false;

View File

@@ -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]);