1- import { ReactNode , useEffect , useRef } from 'react' ;
1+ import { ReactNode , useCallback , useEffect , useRef } from 'react' ;
22import { animate , motion , useMotionValue } from 'motion/react' ;
33import { useDrag } from '@use-gesture/react' ;
44import { useAtomValue } from 'jotai' ;
@@ -13,6 +13,7 @@ const SWIPE_DISTANCE = 80;
1313const SWIPE_VELOCITY = 0.4 ;
1414const SNAP_SPRING = { type : 'spring' as const , stiffness : 600 , damping : 50 , mass : 0.6 } ;
1515const TRANSITION_SPRING = { type : 'spring' as const , stiffness : 380 , damping : 36 } ;
16+ const CAPTURE_THRESHOLD = 8 ;
1617
1718export function MobileRoomOverlay ( { children } : { children : ReactNode } ) {
1819 const settings = useAtomValue ( settingsAtom ) ;
@@ -25,10 +26,73 @@ export function MobileRoomOverlay({ children }: { children: ReactNode }) {
2526 animate ( x , 0 , TRANSITION_SPRING ) ;
2627 } , [ x ] ) ;
2728
28- const navigateBack = ( ) => {
29+ // Sync fixed call embed transform with overlay position
30+ useEffect ( ( ) => {
31+ const unsub = x . on ( 'change' , ( val ) => {
32+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
33+ const embed = document . querySelector ( '[data-call-embed-container]' ) as HTMLElement | null ;
34+ if ( embed ) embed . style . transform = `translateX(${ val } px)` ;
35+ } ) ;
36+ return ( ) => {
37+ unsub ( ) ;
38+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
39+ const embed = document . querySelector ( '[data-call-embed-container]' ) as HTMLElement | null ;
40+ if ( embed ) embed . style . transform = '' ;
41+ } ;
42+ } , [ x ] ) ;
43+
44+ // Disable pointer events on the call embed during rightward swipes so
45+ // useDrag on the overlay can receive the gesture instead of the iframe
46+ useEffect ( ( ) => {
47+ if ( ! settings . mobileGestures ) return undefined ;
48+
49+ let startX = 0 ;
50+ let startY = 0 ;
51+ let disabled = false ;
52+
53+ const getEmbed = ( ) => document . querySelector ( '[data-call-embed-container]' ) ;
54+
55+ const onTouchStart = ( e : TouchEvent ) => {
56+ startX = e . touches [ 0 ] ?. clientX ?? 0 ;
57+ startY = e . touches [ 0 ] ?. clientY ?? 0 ;
58+ disabled = false ;
59+ } ;
60+
61+ const onTouchMove = ( e : TouchEvent ) => {
62+ if ( disabled ) return ;
63+ const dx = ( e . touches [ 0 ] ?. clientX ?? 0 ) - startX ;
64+ const dy = ( e . touches [ 0 ] ?. clientY ?? 0 ) - startY ;
65+ // Only disable for clearly rightward, non-vertical gestures
66+ if ( dx > CAPTURE_THRESHOLD && Math . abs ( dy ) < Math . abs ( dx ) * 1.5 ) {
67+ const embed = getEmbed ( ) ;
68+ if ( embed ) embed . style . pointerEvents = 'none' ;
69+ disabled = true ;
70+ }
71+ } ;
72+
73+ const onTouchEnd = ( ) => {
74+ if ( disabled ) {
75+ const embed = getEmbed ( ) ;
76+ if ( embed ) embed . style . pointerEvents = '' ;
77+ disabled = false ;
78+ }
79+ } ;
80+
81+ window . addEventListener ( 'touchstart' , onTouchStart , { passive : true } ) ;
82+ window . addEventListener ( 'touchmove' , onTouchMove , { passive : true } ) ;
83+ window . addEventListener ( 'touchend' , onTouchEnd , { passive : true } ) ;
84+ window . addEventListener ( 'touchcancel' , onTouchEnd , { passive : true } ) ;
85+
86+ return ( ) => {
87+ window . removeEventListener ( 'touchstart' , onTouchStart ) ;
88+ window . removeEventListener ( 'touchmove' , onTouchMove ) ;
89+ window . removeEventListener ( 'touchend' , onTouchEnd ) ;
90+ window . removeEventListener ( 'touchcancel' , onTouchEnd ) ;
91+ } ;
92+ } , [ settings . mobileGestures ] ) ;
93+
94+ const navigateBack = useCallback ( ( ) => {
2995 log . log ( 'navigateBack — disabling pointer events, starting exit animation' ) ;
30- // Disable hit-testing immediately so the nav beneath becomes interactive
31- // before the animation completes and the component unmounts.
3296 if ( divRef . current ) divRef . current . style . pointerEvents = 'none' ;
3397 animate ( x , window . innerWidth , {
3498 ...TRANSITION_SPRING ,
@@ -37,12 +101,11 @@ export function MobileRoomOverlay({ children }: { children: ReactNode }) {
37101 goBack ( ) ;
38102 } ,
39103 } ) ;
40- } ;
104+ } , [ x , goBack ] ) ;
41105
42106 const bind = useDrag (
43107 ( { active, movement : [ mx ] , velocity : [ vx ] , direction : [ dx ] } ) => {
44108 if ( ! settings . mobileGestures || ! mobileOrTablet ( ) ) return ;
45-
46109 if ( active ) {
47110 x . set ( Math . max ( 0 , mx ) ) ;
48111 } else {
0 commit comments