|
| 1 | +import { useState, TouchEvent, useCallback } from 'react'; |
| 2 | +import { calculateSwipeDirection, calculateDistance } from '../utils/mobileUtils'; |
| 3 | + |
| 4 | +interface GestureHandlers { |
| 5 | + onSwipeLeft?: () => void; |
| 6 | + onSwipeRight?: () => void; |
| 7 | + onSwipeUp?: () => void; |
| 8 | + onSwipeDown?: () => void; |
| 9 | + onPinchIn?: () => void; |
| 10 | + onPinchOut?: () => void; |
| 11 | + onTap?: () => void; |
| 12 | + swipeThreshold?: number; |
| 13 | +} |
| 14 | + |
| 15 | +export const useMobileGestures = (handlers: GestureHandlers) => { |
| 16 | + const [touchStart, setTouchStart] = useState<{ x: number; y: number } | null>(null); |
| 17 | + const [initialPinchDistance, setInitialPinchDistance] = useState<number | null>(null); |
| 18 | + |
| 19 | + const handleTouchStart = useCallback((e: TouchEvent) => { |
| 20 | + if (e.touches.length === 1) { |
| 21 | + setTouchStart({ x: e.touches[0].clientX, y: e.touches[0].clientY }); |
| 22 | + } else if (e.touches.length === 2) { |
| 23 | + const dist = calculateDistance( |
| 24 | + e.touches[0].clientX, e.touches[0].clientY, |
| 25 | + e.touches[1].clientX, e.touches[1].clientY |
| 26 | + ); |
| 27 | + setInitialPinchDistance(dist); |
| 28 | + } |
| 29 | + }, []); |
| 30 | + |
| 31 | + const handleTouchEnd = useCallback((e: TouchEvent) => { |
| 32 | + if (e.changedTouches.length === 1 && touchStart) { |
| 33 | + const touchEnd = { x: e.changedTouches[0].clientX, y: e.changedTouches[0].clientY }; |
| 34 | + const isTap = Math.abs(touchEnd.x - touchStart.x) < 10 && Math.abs(touchEnd.y - touchStart.y) < 10; |
| 35 | + |
| 36 | + if (isTap && handlers.onTap) { |
| 37 | + handlers.onTap(); |
| 38 | + } else { |
| 39 | + const direction = calculateSwipeDirection( |
| 40 | + touchStart.x, touchStart.y, |
| 41 | + touchEnd.x, touchEnd.y, |
| 42 | + handlers.swipeThreshold || 50 |
| 43 | + ); |
| 44 | + |
| 45 | + if (direction === 'LEFT' && handlers.onSwipeLeft) handlers.onSwipeLeft(); |
| 46 | + if (direction === 'RIGHT' && handlers.onSwipeRight) handlers.onSwipeRight(); |
| 47 | + if (direction === 'UP' && handlers.onSwipeUp) handlers.onSwipeUp(); |
| 48 | + if (direction === 'DOWN' && handlers.onSwipeDown) handlers.onSwipeDown(); |
| 49 | + } |
| 50 | + } |
| 51 | + setTouchStart(null); |
| 52 | + setInitialPinchDistance(null); |
| 53 | + }, [touchStart, handlers]); |
| 54 | + |
| 55 | + const handleTouchMove = useCallback((e: TouchEvent) => { |
| 56 | + if (e.touches.length === 2 && initialPinchDistance !== null) { |
| 57 | + const currentDistance = calculateDistance( |
| 58 | + e.touches[0].clientX, e.touches[0].clientY, |
| 59 | + e.touches[1].clientX, e.touches[1].clientY |
| 60 | + ); |
| 61 | + |
| 62 | + const pinchThreshold = 20; |
| 63 | + if (currentDistance - initialPinchDistance > pinchThreshold && handlers.onPinchOut) { |
| 64 | + handlers.onPinchOut(); |
| 65 | + setInitialPinchDistance(currentDistance); // Reset to detect continuous pinch |
| 66 | + } else if (initialPinchDistance - currentDistance > pinchThreshold && handlers.onPinchIn) { |
| 67 | + handlers.onPinchIn(); |
| 68 | + setInitialPinchDistance(currentDistance); |
| 69 | + } |
| 70 | + } |
| 71 | + }, [initialPinchDistance, handlers]); |
| 72 | + |
| 73 | + return { |
| 74 | + onTouchStart: handleTouchStart, |
| 75 | + onTouchMove: handleTouchMove, |
| 76 | + onTouchEnd: handleTouchEnd, |
| 77 | + }; |
| 78 | +}; |
0 commit comments