import { useEffect, useState } from "react"; export interface TouchMoveDistance { x: number; y: number; } /** * Custom hook to track touch movement distances on a given HTML element. * * @param ref - A React ref object pointing to the HTML element to monitor for touch events. * @param options - Configuration options for the hook. * @param options.enable - A boolean flag to enable or disable the touch tracking. * @param options.enableMouse - A boolean flag to enable or disable the mouse tracking. * @param options.handleTouchEnd - An optional callback function invoked on touch end, * receiving the touch move distance and a setter function. * * @returns An object containing the x and y distances of the touch movement, * and a setter function to manually update the touch move distance. */ export function useTouchMoveDistance( ref: React.RefObject, { enable = true, enableMouse = false, handleTouchEnd, }: { enable?: boolean; enableMouse?: boolean; handleTouchEnd?: ( touchMoveDistance: TouchMoveDistance, setTouchMoveDistance: (touchMoveDistance: TouchMoveDistance) => void, ) => void; }, ): TouchMoveDistance & { set: (touchMoveDistance: TouchMoveDistance) => void } { const [touchStart, setTouchStart] = useState<{ x: number; y: number } | null>( null, ); const [mouseStart, setMouseStart] = useState<{ x: number; y: number } | null>( null, ); const [touchMoveDistance, setTouchMoveDistance] = useState( { x: 0, y: 0 }, ); useEffect(() => { if (!enable) return; const element = ref.current; if (!element) return; function onTouchStart(event: TouchEvent) { const touch = event.touches[0]; setTouchStart({ x: touch.clientX, y: touch.clientY }); } function onTouchMove(event: TouchEvent) { if (!touchStart) return; const touch = event.touches[0]; const distanceX = touch.clientX - touchStart.x; const distanceY = touch.clientY - touchStart.y; setTouchMoveDistance({ x: distanceX, y: distanceY }); } function onTouchEnd(event: TouchEvent) { if (!touchStart) return; const touch = event.changedTouches[0]; const distanceX = touch.clientX - touchStart.x; const distanceY = touch.clientY - touchStart.y; setTouchMoveDistance({ x: distanceX, y: distanceY }); handleTouchEnd?.({ x: distanceX, y: distanceY }, setTouchMoveDistance); setTouchStart(null); setTouchMoveDistance({ x: 0, y: 0 }); } function onMouseDown(event: MouseEvent) { setMouseStart({ x: event.clientX, y: event.clientY }); } function onMouseMove(event: MouseEvent) { if (!mouseStart) return; const distanceX = event.clientX - mouseStart.x; const distanceY = event.clientY - mouseStart.y; setTouchMoveDistance({ x: distanceX, y: distanceY }); } function onMouseUp(event: MouseEvent) { if (!mouseStart) return; const distanceX = event.clientX - mouseStart.x; const distanceY = event.clientY - mouseStart.y; setTouchMoveDistance({ x: distanceX, y: distanceY }); handleTouchEnd?.({ x: distanceX, y: distanceY }, setTouchMoveDistance); setMouseStart(null); setTouchMoveDistance({ x: 0, y: 0 }); } element.addEventListener("touchstart", onTouchStart); element.addEventListener("touchmove", onTouchMove); element.addEventListener("touchend", onTouchEnd); enableMouse && element.addEventListener("mousedown", onMouseDown); enableMouse && element.addEventListener("mousemove", onMouseMove); enableMouse && element.addEventListener("mouseup", onMouseUp); return () => { element.removeEventListener("touchstart", onTouchStart); element.removeEventListener("touchmove", onTouchMove); element.removeEventListener("touchend", onTouchEnd); enableMouse && element.removeEventListener("mousedown", onMouseDown); enableMouse && element.removeEventListener("mousemove", onMouseMove); enableMouse && element.removeEventListener("mouseup", onMouseUp); }; }, [enable, enableMouse, handleTouchEnd, mouseStart, ref, touchStart]); return { x: touchMoveDistance.x, y: touchMoveDistance.y, set: setTouchMoveDistance, }; }