diff --git a/src/components/Calendar/Calendar.tsx b/src/components/Calendar/Calendar.tsx index bb8e1ec..dfd5db7 100644 --- a/src/components/Calendar/Calendar.tsx +++ b/src/components/Calendar/Calendar.tsx @@ -377,12 +377,11 @@ const CalendarApp: React.FC = ({ errorHandler: errorHandler.current }) - const { offsetX, isAnimating, onTouchStart, onTouchMove, onTouchEnd } = - useSwipeCalendar({ - calendarRef, - containerRef: calendarWrapperRef, - isMobile - }) + const { offsetX, isAnimating } = useSwipeCalendar({ + calendarRef, + containerRef: calendarWrapperRef, + isMobile + }) useEffect(() => { if (process.env.NODE_ENV === 'test') { @@ -441,10 +440,7 @@ const CalendarApp: React.FC = ({
{ setIsAnimating(true) - setOffsetX(targetOffset) + if (deltaX > 0) { + calendarRef.current?.prev() + } else { + calendarRef.current?.next() + } + + setOffsetX(0) setTimeout(() => { setIsAnimating(false) - setOffsetX(-targetOffset) - - // Apply date change - if (deltaX > 0) { - calendarRef.current?.prev() - } else { - calendarRef.current?.next() - } - - // Slide back to center (requesting animation state) - requestAnimationFrame(() => { - setIsAnimating(true) - setOffsetX(0) - }) - - // Immediate reset to preserve "snappy" behavior as per latest user tweak - setIsAnimating(false) - }, 100) + }, 300) }, [calendarRef] ) diff --git a/src/components/Calendar/hooks/useSwipeCalendar.ts b/src/components/Calendar/hooks/useSwipeCalendar.ts index eea7c37..ecbf2c2 100644 --- a/src/components/Calendar/hooks/useSwipeCalendar.ts +++ b/src/components/Calendar/hooks/useSwipeCalendar.ts @@ -1,10 +1,4 @@ -import { - useRef, - useState, - useCallback, - MutableRefObject, - RefObject -} from 'react' +import { useRef, useState, useEffect, MutableRefObject, RefObject } from 'react' import { CalendarApi } from '@fullcalendar/core' import { useSwipeAnimation } from './useSwipeAnimation' @@ -17,25 +11,70 @@ interface UseSwipeCalendarProps { interface SwipeCalendarReturn { offsetX: number isAnimating: boolean - onTouchStart: (e: React.TouchEvent) => void - onTouchMove: (e: React.TouchEvent) => void - onTouchEnd: () => void } -const isHorizontalSwipe = (deltaX: number, deltaY: number): boolean => { - return Math.abs(deltaX) > Math.abs(deltaY) || Math.abs(deltaX) > 10 -} +const isHorizontalSwipe = (dx: number, dy: number): boolean => + Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 10 -const getTargetOffset = ( - deltaX: number, - containerWidth: number, - threshold: number +const getSwipeTarget = ( + offsetX: number, + containerWidth: number ): number | null => { - if (Math.abs(deltaX) <= threshold) return null - return deltaX > 0 ? containerWidth : -containerWidth + if (Math.abs(offsetX) <= containerWidth * 0.1) return null + return offsetX > 0 ? containerWidth : -containerWidth } -const preventSwipping = (): void => {} +const handleTouchMove = ( + e: TouchEvent, + touchStartRef: React.MutableRefObject<{ x: number; y: number } | null>, + isHorizontalRef: React.MutableRefObject, + setOffsetX: (dx: number) => void +): void => { + if (!touchStartRef.current) return + const dx = e.touches[0].clientX - touchStartRef.current.x + const dy = e.touches[0].clientY - touchStartRef.current.y + if (!isHorizontalRef.current) + isHorizontalRef.current = isHorizontalSwipe(dx, dy) + if (isHorizontalRef.current && e.cancelable) { + e.preventDefault() + setOffsetX(dx) + } +} + +const handleTouchEnd = ({ + touchStartRef, + isHorizontalRef, + calendarRef, + offsetX, + containerWidth, + setOffsetX, + finalizeSwipe, + cancelSwipe +}: { + touchStartRef: React.MutableRefObject<{ x: number; y: number } | null> + isHorizontalRef: React.MutableRefObject + calendarRef: MutableRefObject + offsetX: number + containerWidth: number + setOffsetX: (dx: number) => void + finalizeSwipe: (target: number, currentOffset: number) => void + cancelSwipe: () => void +}): void => { + const hasValidTouch = touchStartRef.current && calendarRef.current + if (!hasValidTouch) { + setOffsetX(0) + touchStartRef.current = null + isHorizontalRef.current = false + return + } + if (isHorizontalRef.current) { + const target = getSwipeTarget(offsetX, containerWidth) + if (target !== null) finalizeSwipe(target, offsetX) + else cancelSwipe() + } + touchStartRef.current = null + isHorizontalRef.current = false +} export const useSwipeCalendar = ({ calendarRef, @@ -46,57 +85,55 @@ export const useSwipeCalendar = ({ useSwipeAnimation(calendarRef) const [containerWidth, setContainerWidth] = useState(0) const touchStartRef = useRef<{ x: number; y: number } | null>(null) + const isHorizontalRef = useRef(false) - const onTouchStart = useCallback( - (e: React.TouchEvent) => { - if (containerRef.current) - setContainerWidth(containerRef.current.getBoundingClientRect().width) + useEffect(() => { + const el = containerRef.current + if (!el || !isMobile) return + + const onTouchStart = (e: TouchEvent): void => { + setContainerWidth(el.getBoundingClientRect().width) touchStartRef.current = { x: e.touches[0].clientX, y: e.touches[0].clientY } - }, - [containerRef] - ) + isHorizontalRef.current = false + } - const onTouchMove = useCallback( - (e: React.TouchEvent) => { - if (!touchStartRef.current) return - const dx = e.touches[0].clientX - touchStartRef.current.x - const dy = e.touches[0].clientY - touchStartRef.current.y - if (isHorizontalSwipe(dx, dy)) setOffsetX(dx) - }, - [setOffsetX] - ) + const onTouchMove = (e: TouchEvent): void => + handleTouchMove(e, touchStartRef, isHorizontalRef, setOffsetX) - const onTouchEnd = useCallback(() => { - if (!touchStartRef.current || !calendarRef.current) return setOffsetX(0) - const target = getTargetOffset( - offsetX, - containerWidth, - containerWidth * 0.1 - ) - if (target !== null) finalizeSwipe(target, offsetX) - else cancelSwipe() - touchStartRef.current = null + const onTouchEnd = (): void => + handleTouchEnd({ + touchStartRef, + isHorizontalRef, + calendarRef, + offsetX, + containerWidth, + setOffsetX, + finalizeSwipe, + cancelSwipe + }) + + el.addEventListener('touchstart', onTouchStart, { passive: true }) + el.addEventListener('touchmove', onTouchMove, { passive: false }) + el.addEventListener('touchend', onTouchEnd, { passive: true }) + + return (): void => { + el.removeEventListener('touchstart', onTouchStart) + el.removeEventListener('touchmove', onTouchMove) + el.removeEventListener('touchend', onTouchEnd) + } }, [ - calendarRef, + containerRef, + isMobile, offsetX, containerWidth, + setOffsetX, finalizeSwipe, cancelSwipe, - setOffsetX + calendarRef ]) - if (!isMobile) { - return { - offsetX: 0, - isAnimating: false, - onTouchStart: preventSwipping, - onTouchMove: preventSwipping, - onTouchEnd: preventSwipping - } - } - - return { offsetX, isAnimating, onTouchStart, onTouchMove, onTouchEnd } + return { offsetX, isAnimating } }