Files
stripstream/src/components/reader/hooks/useTouchNavigation.ts
Froidefond Julien d535f9f28e fix: respect RTL direction for reader arrow buttons and swipe navigation
Arrow buttons now swap next/previous in RTL mode. Swipe navigation
receives isRTL from parent state instead of creating its own independent
copy, so toggling direction takes effect immediately without reload.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 21:24:11 +01:00

141 lines
4.3 KiB
TypeScript

import { useCallback, useRef, useEffect } from "react";
interface UseTouchNavigationProps {
onPreviousPage: () => void;
onNextPage: () => void;
pswpRef: React.MutableRefObject<unknown>;
isRTL: boolean;
}
export function useTouchNavigation({
onPreviousPage,
onNextPage,
pswpRef,
isRTL,
}: UseTouchNavigationProps) {
const touchStartXRef = useRef<number | null>(null);
const touchStartYRef = useRef<number | null>(null);
const isPinchingRef = useRef(false);
// Helper pour vérifier si la page est zoomée (zoom natif du navigateur)
const isZoomed = useCallback(() => {
// Utiliser visualViewport.scale pour détecter le zoom natif
// Si scale > 1, la page est zoomée
if (window.visualViewport) {
return window.visualViewport.scale > 1;
}
// Fallback pour les navigateurs qui ne supportent pas visualViewport
// Comparer la taille de la fenêtre avec la taille réelle
return window.innerWidth !== window.screen.width;
}, []);
// Touch handlers for swipe navigation
const handleTouchStart = useCallback(
(e: TouchEvent) => {
// Ne pas gérer si Photoswipe est ouvert
if (pswpRef.current) return;
// Ne pas gérer si la page est zoomée (zoom natif)
if (isZoomed()) return;
// Détecter si c'est un pinch (2+ doigts)
if (e.touches.length > 1) {
isPinchingRef.current = true;
touchStartXRef.current = null;
touchStartYRef.current = null;
return;
}
// Un seul doigt - seulement si on n'était pas en train de pinch
// On réinitialise isPinchingRef seulement ici, quand on commence un nouveau geste à 1 doigt
if (e.touches.length === 1) {
isPinchingRef.current = false;
touchStartXRef.current = e.touches[0].clientX;
touchStartYRef.current = e.touches[0].clientY;
}
},
[pswpRef, isZoomed]
);
const handleTouchMove = useCallback((e: TouchEvent) => {
// Détecter le pinch pendant le mouvement
if (e.touches.length > 1) {
isPinchingRef.current = true;
touchStartXRef.current = null;
touchStartYRef.current = null;
}
}, []);
const handleTouchEnd = useCallback(
(e: TouchEvent) => {
// Si on était en mode pinch, ne JAMAIS traiter le swipe
if (isPinchingRef.current) {
touchStartXRef.current = null;
touchStartYRef.current = null;
// Ne PAS réinitialiser isPinchingRef ici, on le fera au prochain touchstart
return;
}
// Vérifier qu'on a bien des coordonnées de départ
if (touchStartXRef.current === null || touchStartYRef.current === null) return;
// Ne pas gérer si Photoswipe est ouvert
if (pswpRef.current) return;
// Ne pas gérer si la page est zoomée (zoom natif)
if (isZoomed()) return;
const touchEndX = e.changedTouches[0].clientX;
const touchEndY = e.changedTouches[0].clientY;
const deltaX = touchEndX - touchStartXRef.current;
const deltaY = touchEndY - touchStartYRef.current;
// Si le déplacement vertical est plus important, on ignore (scroll)
if (Math.abs(deltaY) > Math.abs(deltaX)) {
touchStartXRef.current = null;
touchStartYRef.current = null;
return;
}
// Seuil de 50px pour changer de page
if (Math.abs(deltaX) > 50) {
if (deltaX > 0) {
// Swipe vers la droite
if (isRTL) {
onNextPage();
} else {
onPreviousPage();
}
} else {
// Swipe vers la gauche
if (isRTL) {
onPreviousPage();
} else {
onNextPage();
}
}
}
touchStartXRef.current = null;
touchStartYRef.current = null;
},
[onNextPage, onPreviousPage, isRTL, pswpRef, isZoomed]
);
// Setup touch event listeners
useEffect(() => {
window.addEventListener("touchstart", handleTouchStart);
window.addEventListener("touchmove", handleTouchMove);
window.addEventListener("touchend", handleTouchEnd);
return () => {
window.removeEventListener("touchstart", handleTouchStart);
window.removeEventListener("touchmove", handleTouchMove);
window.removeEventListener("touchend", handleTouchEnd);
};
}, [handleTouchStart, handleTouchMove, handleTouchEnd]);
return {
handleTouchStart,
handleTouchMove,
handleTouchEnd,
};
}