155 lines
3.9 KiB
TypeScript
155 lines
3.9 KiB
TypeScript
/**
|
|
* Hook pour gérer les gestes tactiles (touch gestures)
|
|
* T0504: Create Playlist Mobile Responsive
|
|
*/
|
|
|
|
import { useRef, useCallback, useState } from 'react';
|
|
|
|
export interface SwipeDirection {
|
|
direction: 'left' | 'right' | 'up' | 'down' | null;
|
|
distance: number;
|
|
}
|
|
|
|
export interface TouchGestureHandlers {
|
|
onSwipeLeft?: () => void;
|
|
onSwipeRight?: () => void;
|
|
onSwipeUp?: () => void;
|
|
onSwipeDown?: () => void;
|
|
onLongPress?: () => void;
|
|
onTap?: () => void;
|
|
swipeThreshold?: number;
|
|
longPressDelay?: number;
|
|
}
|
|
|
|
export function useTouchGestures(handlers: TouchGestureHandlers = {}) {
|
|
const {
|
|
onSwipeLeft,
|
|
onSwipeRight,
|
|
onSwipeUp,
|
|
onSwipeDown,
|
|
onLongPress,
|
|
onTap,
|
|
swipeThreshold = 50,
|
|
longPressDelay = 500,
|
|
} = handlers;
|
|
|
|
const touchStartRef = useRef<{ x: number; y: number; time: number } | null>(
|
|
null,
|
|
);
|
|
const longPressTimerRef = useRef<NodeJS.Timeout | null>(null);
|
|
const [isLongPressing, setIsLongPressing] = useState(false);
|
|
|
|
const handleTouchStart = useCallback(
|
|
(e: React.TouchEvent) => {
|
|
const touch = e.touches[0];
|
|
touchStartRef.current = {
|
|
x: touch.clientX,
|
|
y: touch.clientY,
|
|
time: Date.now(),
|
|
};
|
|
|
|
// Démarrer le timer pour le long press
|
|
if (onLongPress) {
|
|
longPressTimerRef.current = setTimeout(() => {
|
|
setIsLongPressing(true);
|
|
onLongPress();
|
|
}, longPressDelay);
|
|
}
|
|
},
|
|
[onLongPress, longPressDelay],
|
|
);
|
|
|
|
const handleTouchMove = useCallback((_e: React.TouchEvent) => {
|
|
// Annuler le long press si l'utilisateur bouge
|
|
if (longPressTimerRef.current) {
|
|
clearTimeout(longPressTimerRef.current);
|
|
longPressTimerRef.current = null;
|
|
}
|
|
setIsLongPressing(false);
|
|
}, []);
|
|
|
|
const handleTouchEnd = useCallback(
|
|
(e: React.TouchEvent) => {
|
|
// Annuler le long press timer
|
|
if (longPressTimerRef.current) {
|
|
clearTimeout(longPressTimerRef.current);
|
|
longPressTimerRef.current = null;
|
|
}
|
|
|
|
if (!touchStartRef.current) return;
|
|
|
|
const touch = e.changedTouches[0];
|
|
const deltaX = touch.clientX - touchStartRef.current.x;
|
|
const deltaY = touch.clientY - touchStartRef.current.y;
|
|
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
const time = Date.now() - touchStartRef.current.time;
|
|
|
|
// Si c'était un long press, ne pas traiter comme un swipe
|
|
if (isLongPressing) {
|
|
setIsLongPressing(false);
|
|
touchStartRef.current = null;
|
|
return;
|
|
}
|
|
|
|
// Si la distance est trop petite, considérer comme un tap
|
|
if (distance < swipeThreshold) {
|
|
if (onTap && time < 300) {
|
|
onTap();
|
|
}
|
|
touchStartRef.current = null;
|
|
return;
|
|
}
|
|
|
|
// Déterminer la direction du swipe
|
|
const absX = Math.abs(deltaX);
|
|
const absY = Math.abs(deltaY);
|
|
|
|
if (absX > absY) {
|
|
// Swipe horizontal
|
|
if (deltaX > 0 && onSwipeRight) {
|
|
onSwipeRight();
|
|
} else if (deltaX < 0 && onSwipeLeft) {
|
|
onSwipeLeft();
|
|
}
|
|
} else {
|
|
// Swipe vertical
|
|
if (deltaY > 0 && onSwipeDown) {
|
|
onSwipeDown();
|
|
} else if (deltaY < 0 && onSwipeUp) {
|
|
onSwipeUp();
|
|
}
|
|
}
|
|
|
|
touchStartRef.current = null;
|
|
setIsLongPressing(false);
|
|
},
|
|
[
|
|
swipeThreshold,
|
|
onSwipeLeft,
|
|
onSwipeRight,
|
|
onSwipeUp,
|
|
onSwipeDown,
|
|
onTap,
|
|
isLongPressing,
|
|
],
|
|
);
|
|
|
|
const handleTouchCancel = useCallback(() => {
|
|
if (longPressTimerRef.current) {
|
|
clearTimeout(longPressTimerRef.current);
|
|
longPressTimerRef.current = null;
|
|
}
|
|
touchStartRef.current = null;
|
|
setIsLongPressing(false);
|
|
}, []);
|
|
|
|
return {
|
|
touchHandlers: {
|
|
onTouchStart: handleTouchStart,
|
|
onTouchMove: handleTouchMove,
|
|
onTouchEnd: handleTouchEnd,
|
|
onTouchCancel: handleTouchCancel,
|
|
},
|
|
isLongPressing,
|
|
};
|
|
}
|