52 lines
1.5 KiB
TypeScript
52 lines
1.5 KiB
TypeScript
import {useCallback, useRef} from 'react';
|
|
import type {TouchEvent} from 'react';
|
|
|
|
export type UseSwipeNavigateOptions = {
|
|
/** Minimum horizontal movement (px) to count as a swipe */
|
|
threshold?: number;
|
|
/** Ignore swipe if vertical movement exceeds this (px) — avoids fighting page scroll */
|
|
maxVerticalDelta?: number;
|
|
};
|
|
|
|
/**
|
|
* Touch swipe on a carousel: finger left → next, finger right → previous.
|
|
*/
|
|
export function useSwipeNavigate(
|
|
onPrev: () => void,
|
|
onNext: () => void,
|
|
options: UseSwipeNavigateOptions = {},
|
|
) {
|
|
const {threshold = 44, maxVerticalDelta = 100} = options;
|
|
const startX = useRef<number | null>(null);
|
|
const startY = useRef<number | null>(null);
|
|
|
|
const onTouchStart = useCallback((e: TouchEvent) => {
|
|
if (e.touches.length !== 1) return;
|
|
startX.current = e.touches[0].clientX;
|
|
startY.current = e.touches[0].clientY;
|
|
}, []);
|
|
|
|
const onTouchEnd = useCallback(
|
|
(e: TouchEvent) => {
|
|
const x0 = startX.current;
|
|
const y0 = startY.current;
|
|
startX.current = null;
|
|
startY.current = null;
|
|
if (x0 == null || y0 == null) return;
|
|
const t = e.changedTouches[0];
|
|
if (!t) return;
|
|
const dx = t.clientX - x0;
|
|
const dy = Math.abs(t.clientY - y0);
|
|
if (dy > maxVerticalDelta) return;
|
|
if (dx > threshold) {
|
|
onPrev();
|
|
} else if (dx < -threshold) {
|
|
onNext();
|
|
}
|
|
},
|
|
[maxVerticalDelta, onNext, onPrev, threshold],
|
|
);
|
|
|
|
return {onTouchStart, onTouchEnd};
|
|
}
|