1- import { ReactNode , useRef , useCallback } from 'react' ;
1+ import { ReactNode } from 'react' ;
22import { useAtomValue } from 'jotai' ;
33import { settingsAtom } from '$state/settings' ;
44import { useIsMobile } from '$hooks/useIsMobile' ;
5+ import { useDrag } from '@use-gesture/react' ;
56import { createLogger } from '$utils/debug' ;
67
78const log = createLogger ( 'SwipeableOverlayWrapper' ) ;
89
910const SWIPE_DISTANCE = 60 ;
10- const AXIS_LOCK_RATIO = 1.5 ;
1111const SWIPE_VELOCITY = 0.3 ;
12+ const AXIS_LOCK_RATIO = 1.5 ;
1213
1314interface SwipeableOverlayWrapperProps {
1415 children : ReactNode ;
@@ -23,85 +24,43 @@ export function SwipeableOverlayWrapper({
2324} : SwipeableOverlayWrapperProps ) {
2425 const settings = useAtomValue ( settingsAtom ) ;
2526 const isMobile = useIsMobile ( ) ;
27+ const gesturesEnabled = settings . mobileGestures && isMobile ;
2628
27- const startX = useRef < number | null > ( null ) ;
28- const startY = useRef < number | null > ( null ) ;
29- const startTime = useRef < number | null > ( null ) ;
30-
31- const handleTouchStart = useCallback (
32- ( e : React . TouchEvent ) => {
33- if ( ! settings . mobileGestures || ! isMobile ) return ;
34- const t = e . touches [ 0 ] ;
35- startX . current = t . clientX ;
36- startY . current = t . clientY ;
37- startTime . current = Date . now ( ) ;
38- } ,
39- [ settings . mobileGestures , isMobile ]
40- ) ;
41-
42- const handleTouchEnd = useCallback (
43- ( e : React . TouchEvent ) => {
44- if ( ! settings . mobileGestures || ! isMobile ) return ;
45- if ( startX . current === null || startY . current === null || startTime . current === null ) return ;
29+ const bind = useDrag (
30+ ( { active, movement : [ mx , my ] , velocity : [ vx ] , direction : [ dx ] } ) => {
31+ if ( active ) return ;
4632
47- const t = e . changedTouches [ 0 ] ;
48- const dx = t . clientX - startX . current ;
49- const dy = t . clientY - startY . current ;
50- const dt = Date . now ( ) - startTime . current ;
51- const velocity = Math . abs ( dx ) / dt ;
33+ const axisBlocked = Math . abs ( my ) * AXIS_LOCK_RATIO > Math . abs ( mx ) ;
34+ if ( axisBlocked ) return ;
5235
53- const axisBlocked = Math . abs ( dy ) * AXIS_LOCK_RATIO > Math . abs ( dx ) ;
54- const swipedLeft =
55- direction === 'left' &&
56- dx < 0 &&
57- ( Math . abs ( dx ) > SWIPE_DISTANCE || velocity > SWIPE_VELOCITY ) ;
58- const swipedRight =
59- direction === 'right' &&
60- dx > 0 &&
61- ( Math . abs ( dx ) > SWIPE_DISTANCE || velocity > SWIPE_VELOCITY ) ;
36+ const thresholdMet =
37+ direction === 'left'
38+ ? mx < - SWIPE_DISTANCE || ( vx > SWIPE_VELOCITY && dx < 0 && mx < 0 )
39+ : mx > SWIPE_DISTANCE || ( vx > SWIPE_VELOCITY && dx > 0 && mx > 0 ) ;
6240
63- log . log (
64- `touchend — dx:${ dx . toFixed ( 1 ) } dy:${ dy . toFixed ( 1 ) } dt:${ dt } ms vel:${ velocity . toFixed ( 3 ) } ` ,
65- `| axisBlocked:${ axisBlocked } swipedLeft:${ swipedLeft } swipedRight:${ swipedRight } ` ,
66- `| direction:${ direction } `
67- ) ;
68-
69- startX . current = null ;
70- startY . current = null ;
71- startTime . current = null ;
72-
73- if ( axisBlocked ) {
74- log . log ( 'axis blocked — ignoring (vertical scroll)' ) ;
75- return ;
76- }
77-
78- if ( swipedLeft || swipedRight ) {
41+ if ( thresholdMet ) {
7942 log . log ( 'swipe detected — calling onClose' ) ;
8043 onClose ( ) ;
81- } else {
82- log . log ( 'not a swipe — ignoring' ) ;
8344 }
8445 } ,
85- [ settings . mobileGestures , isMobile , direction , onClose ]
46+ {
47+ axis : 'x' ,
48+ filterTaps : true ,
49+ pointer : { capture : false } ,
50+ enabled : gesturesEnabled ,
51+ }
8652 ) ;
8753
88- const handleTouchCancel = useCallback ( ( ) => {
89- startX . current = null ;
90- startY . current = null ;
91- startTime . current = null ;
92- } , [ ] ) ;
93-
9454 return (
9555 < div
96- onTouchStart = { handleTouchStart }
97- onTouchEnd = { handleTouchEnd }
98- onTouchCancel = { handleTouchCancel }
56+ { ...( gesturesEnabled ? bind ( ) : { } ) }
9957 style = { {
10058 display : 'flex' ,
10159 flexDirection : 'column' ,
10260 flexGrow : 1 ,
10361 height : '100%' ,
10462 width : '100%' ,
63+ touchAction : 'pan-y' ,
10564 } }
10665 >
10766 { children }
0 commit comments