@tamagui/sheet 2.0.0-1769200478218 → 2.0.0-1769256574467
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/GestureDetectorWrapper.cjs +48 -0
- package/dist/cjs/GestureDetectorWrapper.js +29 -0
- package/dist/cjs/GestureDetectorWrapper.js.map +6 -0
- package/dist/cjs/GestureDetectorWrapper.native.js +53 -0
- package/dist/cjs/GestureDetectorWrapper.native.js.map +1 -0
- package/dist/cjs/GestureSheetContext.cjs +52 -0
- package/dist/cjs/GestureSheetContext.js +43 -0
- package/dist/cjs/GestureSheetContext.js.map +6 -0
- package/dist/cjs/GestureSheetContext.native.js +56 -0
- package/dist/cjs/GestureSheetContext.native.js.map +1 -0
- package/dist/cjs/SheetImplementationCustom.cjs +85 -40
- package/dist/cjs/SheetImplementationCustom.js +81 -58
- package/dist/cjs/SheetImplementationCustom.js.map +1 -1
- package/dist/cjs/SheetImplementationCustom.native.js +105 -50
- package/dist/cjs/SheetImplementationCustom.native.js.map +1 -1
- package/dist/cjs/SheetScrollView.cjs +80 -8
- package/dist/cjs/SheetScrollView.js +76 -10
- package/dist/cjs/SheetScrollView.js.map +1 -1
- package/dist/cjs/SheetScrollView.native.js +94 -10
- package/dist/cjs/SheetScrollView.native.js.map +1 -1
- package/dist/cjs/gestureState.cjs +31 -0
- package/dist/cjs/gestureState.js +24 -0
- package/dist/cjs/gestureState.js.map +6 -0
- package/dist/cjs/gestureState.native.js +34 -0
- package/dist/cjs/gestureState.native.js.map +1 -0
- package/dist/cjs/setupGestureHandler.cjs +43 -0
- package/dist/cjs/setupGestureHandler.js +35 -0
- package/dist/cjs/setupGestureHandler.js.map +6 -0
- package/dist/cjs/setupGestureHandler.native.js +47 -0
- package/dist/cjs/setupGestureHandler.native.js.map +1 -0
- package/dist/cjs/useGestureHandlerPan.cjs +125 -0
- package/dist/cjs/useGestureHandlerPan.js +116 -0
- package/dist/cjs/useGestureHandlerPan.js.map +6 -0
- package/dist/cjs/useGestureHandlerPan.native.js +134 -0
- package/dist/cjs/useGestureHandlerPan.native.js.map +1 -0
- package/dist/esm/GestureDetectorWrapper.js +15 -0
- package/dist/esm/GestureDetectorWrapper.js.map +6 -0
- package/dist/esm/GestureDetectorWrapper.mjs +25 -0
- package/dist/esm/GestureDetectorWrapper.mjs.map +1 -0
- package/dist/esm/GestureDetectorWrapper.native.js +27 -0
- package/dist/esm/GestureDetectorWrapper.native.js.map +1 -0
- package/dist/esm/GestureSheetContext.js +28 -0
- package/dist/esm/GestureSheetContext.js.map +6 -0
- package/dist/esm/GestureSheetContext.mjs +28 -0
- package/dist/esm/GestureSheetContext.mjs.map +1 -0
- package/dist/esm/GestureSheetContext.native.js +29 -0
- package/dist/esm/GestureSheetContext.native.js.map +1 -0
- package/dist/esm/SheetImplementationCustom.js +83 -57
- package/dist/esm/SheetImplementationCustom.js.map +1 -1
- package/dist/esm/SheetImplementationCustom.mjs +85 -40
- package/dist/esm/SheetImplementationCustom.mjs.map +1 -1
- package/dist/esm/SheetImplementationCustom.native.js +105 -50
- package/dist/esm/SheetImplementationCustom.native.js.map +1 -1
- package/dist/esm/SheetScrollView.js +77 -9
- package/dist/esm/SheetScrollView.js.map +1 -1
- package/dist/esm/SheetScrollView.mjs +80 -8
- package/dist/esm/SheetScrollView.mjs.map +1 -1
- package/dist/esm/SheetScrollView.native.js +94 -10
- package/dist/esm/SheetScrollView.native.js.map +1 -1
- package/dist/esm/gestureState.js +13 -0
- package/dist/esm/gestureState.js.map +6 -0
- package/dist/esm/gestureState.mjs +5 -0
- package/dist/esm/gestureState.mjs.map +1 -0
- package/dist/esm/gestureState.native.js +5 -0
- package/dist/esm/gestureState.native.js.map +1 -0
- package/dist/esm/setupGestureHandler.js +19 -0
- package/dist/esm/setupGestureHandler.js.map +6 -0
- package/dist/esm/setupGestureHandler.mjs +19 -0
- package/dist/esm/setupGestureHandler.mjs.map +1 -0
- package/dist/esm/setupGestureHandler.native.js +20 -0
- package/dist/esm/setupGestureHandler.native.js.map +1 -0
- package/dist/esm/useGestureHandlerPan.js +101 -0
- package/dist/esm/useGestureHandlerPan.js.map +6 -0
- package/dist/esm/useGestureHandlerPan.mjs +102 -0
- package/dist/esm/useGestureHandlerPan.mjs.map +1 -0
- package/dist/esm/useGestureHandlerPan.native.js +108 -0
- package/dist/esm/useGestureHandlerPan.native.js.map +1 -0
- package/dist/jsx/GestureDetectorWrapper.js +15 -0
- package/dist/jsx/GestureDetectorWrapper.js.map +6 -0
- package/dist/jsx/GestureDetectorWrapper.mjs +25 -0
- package/dist/jsx/GestureDetectorWrapper.mjs.map +1 -0
- package/dist/jsx/GestureDetectorWrapper.native.js +53 -0
- package/dist/jsx/GestureDetectorWrapper.native.js.map +1 -0
- package/dist/jsx/GestureSheetContext.js +28 -0
- package/dist/jsx/GestureSheetContext.js.map +6 -0
- package/dist/jsx/GestureSheetContext.mjs +28 -0
- package/dist/jsx/GestureSheetContext.mjs.map +1 -0
- package/dist/jsx/GestureSheetContext.native.js +56 -0
- package/dist/jsx/GestureSheetContext.native.js.map +1 -0
- package/dist/jsx/SheetImplementationCustom.js +83 -57
- package/dist/jsx/SheetImplementationCustom.js.map +1 -1
- package/dist/jsx/SheetImplementationCustom.mjs +85 -40
- package/dist/jsx/SheetImplementationCustom.mjs.map +1 -1
- package/dist/jsx/SheetImplementationCustom.native.js +105 -50
- package/dist/jsx/SheetImplementationCustom.native.js.map +1 -1
- package/dist/jsx/SheetScrollView.js +77 -9
- package/dist/jsx/SheetScrollView.js.map +1 -1
- package/dist/jsx/SheetScrollView.mjs +80 -8
- package/dist/jsx/SheetScrollView.mjs.map +1 -1
- package/dist/jsx/SheetScrollView.native.js +94 -10
- package/dist/jsx/SheetScrollView.native.js.map +1 -1
- package/dist/jsx/gestureState.js +13 -0
- package/dist/jsx/gestureState.js.map +6 -0
- package/dist/jsx/gestureState.mjs +5 -0
- package/dist/jsx/gestureState.mjs.map +1 -0
- package/dist/jsx/gestureState.native.js +34 -0
- package/dist/jsx/gestureState.native.js.map +1 -0
- package/dist/jsx/setupGestureHandler.js +19 -0
- package/dist/jsx/setupGestureHandler.js.map +6 -0
- package/dist/jsx/setupGestureHandler.mjs +19 -0
- package/dist/jsx/setupGestureHandler.mjs.map +1 -0
- package/dist/jsx/setupGestureHandler.native.js +47 -0
- package/dist/jsx/setupGestureHandler.native.js.map +1 -0
- package/dist/jsx/useGestureHandlerPan.js +101 -0
- package/dist/jsx/useGestureHandlerPan.js.map +6 -0
- package/dist/jsx/useGestureHandlerPan.mjs +102 -0
- package/dist/jsx/useGestureHandlerPan.mjs.map +1 -0
- package/dist/jsx/useGestureHandlerPan.native.js +134 -0
- package/dist/jsx/useGestureHandlerPan.native.js.map +1 -0
- package/package.json +48 -21
- package/src/GestureDetectorWrapper.tsx +41 -0
- package/src/GestureSheetContext.tsx +62 -0
- package/src/SheetImplementationCustom.tsx +124 -57
- package/src/SheetScrollView.tsx +157 -8
- package/src/gestureState.ts +17 -0
- package/src/setupGestureHandler.ts +32 -0
- package/src/types.tsx +15 -0
- package/src/useGestureHandlerPan.tsx +299 -0
- package/types/GestureDetectorWrapper.d.ts +14 -0
- package/types/GestureDetectorWrapper.d.ts.map +1 -0
- package/types/GestureDetectorWrapper.native.d.ts +14 -0
- package/types/GestureDetectorWrapper.native.d.ts.map +1 -0
- package/types/GestureSheetContext.d.ts +36 -0
- package/types/GestureSheetContext.d.ts.map +1 -0
- package/types/SheetImplementationCustom.d.ts.map +1 -1
- package/types/SheetScrollView.d.ts.map +1 -1
- package/types/gestureState.d.ts +9 -0
- package/types/gestureState.d.ts.map +1 -0
- package/types/gestureState.native.d.ts +12 -0
- package/types/gestureState.native.d.ts.map +1 -0
- package/types/setupGestureHandler.d.ts +12 -0
- package/types/setupGestureHandler.d.ts.map +1 -0
- package/types/setupGestureHandler.native.d.ts +41 -0
- package/types/setupGestureHandler.native.d.ts.map +1 -0
- package/types/types.d.ts +8 -0
- package/types/types.d.ts.map +1 -1
- package/types/useGestureHandlerPan.d.ts +43 -0
- package/types/useGestureHandlerPan.d.ts.map +1 -0
- package/types/useGestureHandlerPan.native.d.ts +33 -0
- package/types/useGestureHandlerPan.native.d.ts.map +1 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type React from 'react'
|
|
2
|
+
import { createContext, useContext, type RefObject } from 'react'
|
|
3
|
+
|
|
4
|
+
export interface GestureSheetContextValue {
|
|
5
|
+
/**
|
|
6
|
+
* The sheet's pan gesture object, used for simultaneousHandlers
|
|
7
|
+
* in ScrollView to coordinate gestures
|
|
8
|
+
*/
|
|
9
|
+
panGesture: any | null
|
|
10
|
+
/**
|
|
11
|
+
* Ref to the pan gesture for simultaneousHandlers prop
|
|
12
|
+
*/
|
|
13
|
+
panGestureRef: RefObject<any> | null
|
|
14
|
+
/**
|
|
15
|
+
* Whether the sheet is currently being dragged by the user
|
|
16
|
+
*/
|
|
17
|
+
isDragging: boolean
|
|
18
|
+
/**
|
|
19
|
+
* Set whether panning should be blocked (e.g., when scrolling)
|
|
20
|
+
*/
|
|
21
|
+
setBlockPan: (blocked: boolean) => void
|
|
22
|
+
/**
|
|
23
|
+
* Whether pan gesture is currently blocked
|
|
24
|
+
*/
|
|
25
|
+
blockPan: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const GestureSheetContext = createContext<GestureSheetContextValue | null>(null)
|
|
29
|
+
|
|
30
|
+
export function useGestureSheetContext(): GestureSheetContextValue | null {
|
|
31
|
+
return useContext(GestureSheetContext)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface GestureSheetProviderProps {
|
|
35
|
+
children: React.ReactNode
|
|
36
|
+
isDragging: boolean
|
|
37
|
+
blockPan: boolean
|
|
38
|
+
setBlockPan: (blocked: boolean) => void
|
|
39
|
+
panGesture: any | null
|
|
40
|
+
panGestureRef: RefObject<any> | null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function GestureSheetProvider({
|
|
44
|
+
children,
|
|
45
|
+
isDragging,
|
|
46
|
+
blockPan,
|
|
47
|
+
setBlockPan,
|
|
48
|
+
panGesture,
|
|
49
|
+
panGestureRef,
|
|
50
|
+
}: GestureSheetProviderProps) {
|
|
51
|
+
const value: GestureSheetContextValue = {
|
|
52
|
+
panGesture,
|
|
53
|
+
panGestureRef,
|
|
54
|
+
isDragging,
|
|
55
|
+
blockPan,
|
|
56
|
+
setBlockPan,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<GestureSheetContext.Provider value={value}>{children}</GestureSheetContext.Provider>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
@@ -26,9 +26,12 @@ import type {
|
|
|
26
26
|
} from 'react-native'
|
|
27
27
|
import { Dimensions, Keyboard, PanResponder, View } from 'react-native'
|
|
28
28
|
import { ParentSheetContext, SheetInsideSheetContext } from './contexts'
|
|
29
|
+
import { GestureDetectorWrapper } from './GestureDetectorWrapper'
|
|
30
|
+
import { GestureSheetProvider } from './GestureSheetContext'
|
|
29
31
|
import { resisted } from './helpers'
|
|
30
32
|
import { SheetProvider } from './SheetContext'
|
|
31
33
|
import type { SheetProps, SnapPointsMode } from './types'
|
|
34
|
+
import { useGestureHandlerPan } from './useGestureHandlerPan'
|
|
32
35
|
import { useSheetOpenState } from './useSheetOpenState'
|
|
33
36
|
import { useSheetProviderProps } from './useSheetProviderProps'
|
|
34
37
|
|
|
@@ -131,18 +134,16 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
131
134
|
}
|
|
132
135
|
}, [open, frameSize])
|
|
133
136
|
|
|
137
|
+
// use stableFrameSize when closing to prevent position jumps during exit animation
|
|
138
|
+
// but when opening, always use the current frameSize so positions update correctly
|
|
139
|
+
const effectiveFrameSize = open ? frameSize : stableFrameSize.current || frameSize
|
|
140
|
+
|
|
134
141
|
const positions = React.useMemo(
|
|
135
142
|
() =>
|
|
136
143
|
snapPoints.map((point) =>
|
|
137
|
-
|
|
138
|
-
getYPositions(
|
|
139
|
-
snapPointsMode,
|
|
140
|
-
point,
|
|
141
|
-
screenSize,
|
|
142
|
-
open ? frameSize : stableFrameSize.current
|
|
143
|
-
)
|
|
144
|
+
getYPositions(snapPointsMode, point, screenSize, effectiveFrameSize)
|
|
144
145
|
),
|
|
145
|
-
[screenSize,
|
|
146
|
+
[screenSize, effectiveFrameSize, snapPoints, snapPointsMode]
|
|
146
147
|
)
|
|
147
148
|
|
|
148
149
|
const { useAnimatedNumber, useAnimatedNumberStyle, useAnimatedNumberReaction } =
|
|
@@ -182,8 +183,25 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
182
183
|
(value) => {
|
|
183
184
|
at.current = value
|
|
184
185
|
scrollBridge.paneY = value
|
|
186
|
+
// update isAtTop for scroll enable/disable
|
|
187
|
+
// positions[0] is the top snap point (minY)
|
|
188
|
+
const minY = positions[0]
|
|
189
|
+
const wasAtTop = scrollBridge.isAtTop
|
|
190
|
+
const nowAtTop = value <= minY + 5
|
|
191
|
+
if (wasAtTop !== nowAtTop) {
|
|
192
|
+
scrollBridge.isAtTop = nowAtTop
|
|
193
|
+
// when reaching top, enable scroll; when leaving top, disable scroll
|
|
194
|
+
// this preemptively sets scroll state before any gestures start
|
|
195
|
+
if (nowAtTop) {
|
|
196
|
+
scrollBridge.scrollLockY = undefined
|
|
197
|
+
scrollBridge.setScrollEnabled?.(true)
|
|
198
|
+
} else {
|
|
199
|
+
scrollBridge.scrollLockY = 0
|
|
200
|
+
scrollBridge.setScrollEnabled?.(false)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
185
203
|
},
|
|
186
|
-
[animationDriver]
|
|
204
|
+
[animationDriver, positions]
|
|
187
205
|
)
|
|
188
206
|
)
|
|
189
207
|
|
|
@@ -251,11 +269,26 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
251
269
|
scrollBridge.scrollLock = false
|
|
252
270
|
scrollBridge.scrollStartY = -1
|
|
253
271
|
}
|
|
272
|
+
|
|
273
|
+
// set initial isAtTop state when sheet opens
|
|
274
|
+
// position 0 = top snap point, so isAtTop = true
|
|
275
|
+
if (open && position >= 0) {
|
|
276
|
+
const isTopPosition = position === 0
|
|
277
|
+
scrollBridge.isAtTop = isTopPosition
|
|
278
|
+
if (isTopPosition) {
|
|
279
|
+
scrollBridge.scrollLockY = undefined
|
|
280
|
+
scrollBridge.setScrollEnabled?.(true)
|
|
281
|
+
} else {
|
|
282
|
+
scrollBridge.scrollLockY = 0
|
|
283
|
+
scrollBridge.setScrollEnabled?.(false)
|
|
284
|
+
}
|
|
285
|
+
}
|
|
254
286
|
}, [hasntMeasured, disableAnimation, isHidden, frameSize, screenSize, open, position])
|
|
255
287
|
|
|
256
288
|
const disableDrag = props.disableDrag ?? controller?.disableDrag
|
|
257
289
|
const themeName = useThemeName()
|
|
258
290
|
const [isDragging, setIsDragging] = React.useState(false)
|
|
291
|
+
const [blockPan, setBlockPan] = React.useState(false)
|
|
259
292
|
|
|
260
293
|
const panResponder = React.useMemo(() => {
|
|
261
294
|
if (disableDrag) return
|
|
@@ -414,6 +447,26 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
414
447
|
})
|
|
415
448
|
}, [disableDrag, isShowingInnerSheet, animateTo, frameSize, positions, setPosition])
|
|
416
449
|
|
|
450
|
+
// gesture handler hook for RNGH-based gesture coordination
|
|
451
|
+
const { panGesture, panGestureRef, gestureHandlerEnabled } = useGestureHandlerPan({
|
|
452
|
+
positions,
|
|
453
|
+
frameSize,
|
|
454
|
+
setPosition,
|
|
455
|
+
animateTo,
|
|
456
|
+
stopSpring,
|
|
457
|
+
scrollBridge,
|
|
458
|
+
setIsDragging,
|
|
459
|
+
getCurrentPosition: () => at.current,
|
|
460
|
+
resisted,
|
|
461
|
+
disableDrag,
|
|
462
|
+
isShowingInnerSheet,
|
|
463
|
+
setAnimatedPosition: (val: number) => {
|
|
464
|
+
// directly set the animated value for smooth dragging
|
|
465
|
+
// console.warn('[RNGH-Sheet] setAnimatedPosition:', val.toFixed(1))
|
|
466
|
+
animatedNumber.setValue(val, { type: 'direct' })
|
|
467
|
+
},
|
|
468
|
+
})
|
|
469
|
+
|
|
417
470
|
const handleAnimationViewLayout = React.useCallback(
|
|
418
471
|
(e: LayoutChangeEvent) => {
|
|
419
472
|
// FIX: Don't update frameSize during exit animation to prevent position jumps
|
|
@@ -523,52 +576,65 @@ export const SheetImplementationCustom = React.forwardRef<View, SheetProps>(
|
|
|
523
576
|
<LayoutMeasurementController disable={!open}>
|
|
524
577
|
<ParentSheetContext.Provider value={nextParentContext}>
|
|
525
578
|
<SheetProvider {...providerProps} setHasScrollView={setHasScrollView}>
|
|
526
|
-
<
|
|
527
|
-
{
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
style={{
|
|
533
|
-
opacity: 0,
|
|
534
|
-
position: 'absolute',
|
|
535
|
-
top: 0,
|
|
536
|
-
left: 0,
|
|
537
|
-
right: 0,
|
|
538
|
-
bottom: 0,
|
|
539
|
-
pointerEvents: 'none',
|
|
540
|
-
}}
|
|
541
|
-
onLayout={handleMaxContentViewLayout}
|
|
542
|
-
/>
|
|
543
|
-
)}
|
|
544
|
-
|
|
545
|
-
<AnimatedView
|
|
546
|
-
ref={ref}
|
|
547
|
-
{...panResponder?.panHandlers}
|
|
548
|
-
onLayout={handleAnimationViewLayout}
|
|
549
|
-
// @ts-ignore for CSS driver this is necessary to attach the transition
|
|
550
|
-
// also motion driver at least though i suspect all drivers?
|
|
551
|
-
transition={isDragging || disableAnimation ? null : transition}
|
|
552
|
-
// @ts-ignore
|
|
553
|
-
disableClassName
|
|
554
|
-
style={[
|
|
555
|
-
{
|
|
556
|
-
position: 'absolute',
|
|
557
|
-
zIndex,
|
|
558
|
-
width: '100%',
|
|
559
|
-
height: forcedContentHeight,
|
|
560
|
-
minHeight: forcedContentHeight,
|
|
561
|
-
opacity: !shouldHideParentSheet ? opacity : 0,
|
|
562
|
-
...((shouldHideParentSheet || !open) && {
|
|
563
|
-
pointerEvents: 'none',
|
|
564
|
-
}),
|
|
565
|
-
},
|
|
566
|
-
animatedStyle,
|
|
567
|
-
]}
|
|
579
|
+
<GestureSheetProvider
|
|
580
|
+
isDragging={isDragging}
|
|
581
|
+
blockPan={blockPan}
|
|
582
|
+
setBlockPan={setBlockPan}
|
|
583
|
+
panGesture={panGesture}
|
|
584
|
+
panGestureRef={panGestureRef}
|
|
568
585
|
>
|
|
569
|
-
{
|
|
570
|
-
|
|
571
|
-
|
|
586
|
+
<AnimatePresence custom={{ open }}>
|
|
587
|
+
{shouldHideParentSheet || !open ? null : overlayComponent}
|
|
588
|
+
</AnimatePresence>
|
|
589
|
+
|
|
590
|
+
{snapPointsMode !== 'percent' && (
|
|
591
|
+
<View
|
|
592
|
+
style={{
|
|
593
|
+
opacity: 0,
|
|
594
|
+
position: 'absolute',
|
|
595
|
+
top: 0,
|
|
596
|
+
left: 0,
|
|
597
|
+
right: 0,
|
|
598
|
+
bottom: 0,
|
|
599
|
+
pointerEvents: 'none',
|
|
600
|
+
}}
|
|
601
|
+
onLayout={handleMaxContentViewLayout}
|
|
602
|
+
/>
|
|
603
|
+
)}
|
|
604
|
+
|
|
605
|
+
<AnimatedView
|
|
606
|
+
ref={ref}
|
|
607
|
+
{...(!gestureHandlerEnabled && panResponder?.panHandlers)}
|
|
608
|
+
onLayout={handleAnimationViewLayout}
|
|
609
|
+
// @ts-ignore for CSS driver this is necessary to attach the transition
|
|
610
|
+
// also motion driver at least though i suspect all drivers?
|
|
611
|
+
transition={isDragging || disableAnimation ? null : transition}
|
|
612
|
+
// @ts-ignore
|
|
613
|
+
disableClassName
|
|
614
|
+
style={[
|
|
615
|
+
{
|
|
616
|
+
position: 'absolute',
|
|
617
|
+
zIndex,
|
|
618
|
+
width: '100%',
|
|
619
|
+
height: forcedContentHeight,
|
|
620
|
+
minHeight: forcedContentHeight,
|
|
621
|
+
opacity: !shouldHideParentSheet ? opacity : 0,
|
|
622
|
+
...((shouldHideParentSheet || !open) && {
|
|
623
|
+
pointerEvents: 'none',
|
|
624
|
+
}),
|
|
625
|
+
},
|
|
626
|
+
animatedStyle,
|
|
627
|
+
]}
|
|
628
|
+
>
|
|
629
|
+
{gestureHandlerEnabled && panGesture ? (
|
|
630
|
+
<GestureDetectorWrapper gesture={panGesture} style={{ flex: 1 }}>
|
|
631
|
+
{props.children}
|
|
632
|
+
</GestureDetectorWrapper>
|
|
633
|
+
) : (
|
|
634
|
+
props.children
|
|
635
|
+
)}
|
|
636
|
+
</AnimatedView>
|
|
637
|
+
</GestureSheetProvider>
|
|
572
638
|
</SheetProvider>
|
|
573
639
|
</ParentSheetContext.Provider>
|
|
574
640
|
</LayoutMeasurementController>
|
|
@@ -623,7 +689,9 @@ function getYPositions(
|
|
|
623
689
|
screenSize?: number,
|
|
624
690
|
frameSize?: number
|
|
625
691
|
) {
|
|
626
|
-
if (!screenSize || !frameSize)
|
|
692
|
+
if (!screenSize || !frameSize) {
|
|
693
|
+
return 0
|
|
694
|
+
}
|
|
627
695
|
|
|
628
696
|
if (mode === 'mixed') {
|
|
629
697
|
if (typeof point === 'number') {
|
|
@@ -638,8 +706,7 @@ function getYPositions(
|
|
|
638
706
|
console.warn('Invalid snapPoint percentage string')
|
|
639
707
|
return 0
|
|
640
708
|
}
|
|
641
|
-
|
|
642
|
-
return next
|
|
709
|
+
return Math.round(screenSize - pct * screenSize)
|
|
643
710
|
}
|
|
644
711
|
console.warn('Invalid snapPoint unknown value')
|
|
645
712
|
return 0
|
package/src/SheetScrollView.tsx
CHANGED
|
@@ -5,10 +5,12 @@ import { ScrollView } from '@tamagui/scroll-view'
|
|
|
5
5
|
import { useControllableState } from '@tamagui/use-controllable-state'
|
|
6
6
|
import React, { useEffect, useRef, useState } from 'react'
|
|
7
7
|
import type { ScrollView as RNScrollView } from 'react-native'
|
|
8
|
+
import { useGestureSheetContext } from './GestureSheetContext'
|
|
9
|
+
import { getGestureHandlerState, isGestureHandlerEnabled } from './gestureState'
|
|
8
10
|
import { useSheetContext } from './SheetContext'
|
|
9
11
|
import type { SheetScopedProps } from './types'
|
|
10
12
|
|
|
11
|
-
//
|
|
13
|
+
// uses react-native-gesture-handler's simultaneousWithExternalGesture for native-quality coordination
|
|
12
14
|
|
|
13
15
|
/* -------------------------------------------------------------------------------------------------
|
|
14
16
|
* SheetScrollView
|
|
@@ -33,6 +35,7 @@ export const SheetScrollView = React.forwardRef<
|
|
|
33
35
|
ref
|
|
34
36
|
) => {
|
|
35
37
|
const context = useSheetContext(SHEET_SCROLL_VIEW_NAME, __scopeSheet)
|
|
38
|
+
const gestureContext = useGestureSheetContext()
|
|
36
39
|
const { scrollBridge, setHasScrollView } = context
|
|
37
40
|
const [scrollEnabled, setScrollEnabled_] = useControllableState({
|
|
38
41
|
prop: scrollEnabledProp,
|
|
@@ -40,16 +43,71 @@ export const SheetScrollView = React.forwardRef<
|
|
|
40
43
|
})
|
|
41
44
|
const scrollRef = React.useRef<RNScrollView | null>(null)
|
|
42
45
|
|
|
46
|
+
// get the pan gesture ref for simultaneousHandlers
|
|
47
|
+
// react-native-actions-sheet pattern: pass panGestureRef to ScrollView's simultaneousHandlers
|
|
48
|
+
const panGestureRef = gestureContext?.panGestureRef
|
|
49
|
+
|
|
50
|
+
// get RNGH ScrollView from gestureState (passed via setupGestureHandler to avoid double registration)
|
|
51
|
+
const { ScrollView: RNGHScrollView } = getGestureHandlerState()
|
|
52
|
+
|
|
53
|
+
// determine which ScrollView component to use - need RNGH ScrollView for simultaneousHandlers
|
|
54
|
+
const useRNGHScrollView = isGestureHandlerEnabled() && RNGHScrollView && panGestureRef
|
|
55
|
+
|
|
56
|
+
// console.warn('[RNGH-Scroll] Setup:', {
|
|
57
|
+
// enabled: isGestureHandlerEnabled(),
|
|
58
|
+
// hasRNGHScrollView: !!RNGHScrollView,
|
|
59
|
+
// hasPanRef: !!panGestureRef,
|
|
60
|
+
// useRNGHScrollView
|
|
61
|
+
// })
|
|
62
|
+
|
|
43
63
|
// could make it so it has negative bottom margin and then pads the bottom content
|
|
44
64
|
// to avoid clipping effect when resizing smaller
|
|
45
65
|
// or more ideally could use context to register if it has a scrollview and change behavior
|
|
46
66
|
// const offscreenSize = useSheetOffscreenSize(context)
|
|
47
67
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
68
|
+
// actions-sheet pattern: track scroll offset continuously via ref
|
|
69
|
+
// this is updated on EVERY scroll event so it's always current
|
|
70
|
+
const currentScrollOffset = useRef(0)
|
|
71
|
+
// the position to lock at when scroll is disabled
|
|
72
|
+
const lockedScrollY = useRef(0)
|
|
73
|
+
|
|
74
|
+
const setScrollEnabled = (next: boolean, lockTo?: number) => {
|
|
75
|
+
// console.warn('[RNGH-Scroll] setScrollEnabled', next, 'currentOffset:', currentScrollOffset.current, 'lockTo:', lockTo)
|
|
76
|
+
if (!next) {
|
|
77
|
+
// locking scroll - freeze at specified position or CURRENT position
|
|
78
|
+
// if lockTo is provided (e.g., 0), use that; otherwise freeze at current
|
|
79
|
+
// key insight: we use currentScrollOffset which is updated every onScroll
|
|
80
|
+
// this ensures we freeze at the actual position, not a stale value
|
|
81
|
+
const lockY = lockTo ?? currentScrollOffset.current
|
|
82
|
+
lockedScrollY.current = lockY
|
|
83
|
+
scrollBridge.scrollLockY = lockY
|
|
84
|
+
// console.warn('[RNGH-Scroll] LOCKING at', lockY)
|
|
85
|
+
// immediately scroll to lock position
|
|
86
|
+
scrollRef.current?.scrollTo?.({
|
|
87
|
+
x: 0,
|
|
88
|
+
y: lockY,
|
|
89
|
+
animated: false,
|
|
90
|
+
})
|
|
91
|
+
} else {
|
|
92
|
+
// unlocking - save current position before clearing lock
|
|
93
|
+
lockedScrollY.current = currentScrollOffset.current
|
|
94
|
+
scrollBridge.scrollLockY = undefined
|
|
95
|
+
// console.warn('[RNGH-Scroll] UNLOCKING at', lockedScrollY.current)
|
|
96
|
+
}
|
|
97
|
+
// NOTE: intentionally NOT using setNativeProps or scrollEnabled state
|
|
98
|
+
// because that kills the RNGH scroll gesture mid-touch, breaking handoff.
|
|
99
|
+
// This is the react-native-actions-sheet pattern: both gestures run,
|
|
100
|
+
// we use scrollLockY + scrollTo to "freeze" scroll position during pan.
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// force scroll to specific position (called from pan gesture to compensate for async props)
|
|
104
|
+
const forceScrollTo = (y: number) => {
|
|
105
|
+
// console.warn('[RNGH-Scroll] forceScrollTo:', y)
|
|
106
|
+
scrollRef.current?.scrollTo?.({
|
|
107
|
+
x: 0,
|
|
108
|
+
y,
|
|
109
|
+
animated: false,
|
|
51
110
|
})
|
|
52
|
-
setScrollEnabled_(next)
|
|
53
111
|
}
|
|
54
112
|
|
|
55
113
|
const state = React.useRef({
|
|
@@ -62,8 +120,21 @@ export const SheetScrollView = React.forwardRef<
|
|
|
62
120
|
|
|
63
121
|
useEffect(() => {
|
|
64
122
|
setHasScrollView(true)
|
|
123
|
+
|
|
124
|
+
// register setScrollEnabled on scrollBridge for RNGH coordination
|
|
125
|
+
if (isGestureHandlerEnabled()) {
|
|
126
|
+
scrollBridge.setScrollEnabled = setScrollEnabled
|
|
127
|
+
scrollBridge.forceScrollTo = forceScrollTo
|
|
128
|
+
}
|
|
129
|
+
|
|
65
130
|
return () => {
|
|
66
131
|
setHasScrollView(false)
|
|
132
|
+
if (scrollBridge.setScrollEnabled) {
|
|
133
|
+
scrollBridge.setScrollEnabled = undefined
|
|
134
|
+
}
|
|
135
|
+
if (scrollBridge.forceScrollTo) {
|
|
136
|
+
scrollBridge.forceScrollTo = undefined
|
|
137
|
+
}
|
|
67
138
|
}
|
|
68
139
|
}, [])
|
|
69
140
|
|
|
@@ -149,7 +220,84 @@ export const SheetScrollView = React.forwardRef<
|
|
|
149
220
|
scrollBridge.hasScrollableContent = hasScrollableContent
|
|
150
221
|
}, [hasScrollableContent])
|
|
151
222
|
|
|
152
|
-
|
|
223
|
+
// Use RNGH ScrollView with simultaneousHandlers for native-quality gesture coordination
|
|
224
|
+
// Pattern from react-native-actions-sheet: pass panGestureRef to simultaneousHandlers
|
|
225
|
+
if (useRNGHScrollView && RNGHScrollView && panGestureRef) {
|
|
226
|
+
const RNGHComponent = RNGHScrollView as any
|
|
227
|
+
// console.warn('[RNGH-Scroll] RENDER with simultaneousHandlers')
|
|
228
|
+
return (
|
|
229
|
+
<RNGHComponent
|
|
230
|
+
ref={composeRefs(scrollRef as any, ref)}
|
|
231
|
+
style={{ flex: 1 }}
|
|
232
|
+
scrollEventThrottle={1}
|
|
233
|
+
scrollEnabled={scrollable}
|
|
234
|
+
simultaneousHandlers={[panGestureRef]}
|
|
235
|
+
onLayout={(e: any) => {
|
|
236
|
+
parentHeight.current = Math.ceil(e.nativeEvent.layout.height)
|
|
237
|
+
setIsScrollable()
|
|
238
|
+
}}
|
|
239
|
+
onScroll={(e: any) => {
|
|
240
|
+
const { y } = e.nativeEvent.contentOffset
|
|
241
|
+
|
|
242
|
+
// actions-sheet pattern: ALWAYS track current offset
|
|
243
|
+
// this ensures setScrollEnabled knows the exact current position
|
|
244
|
+
currentScrollOffset.current = y
|
|
245
|
+
|
|
246
|
+
// if scroll is locked, force it back to lock position
|
|
247
|
+
if (scrollBridge.scrollLockY !== undefined) {
|
|
248
|
+
if (y !== scrollBridge.scrollLockY) {
|
|
249
|
+
scrollRef.current?.scrollTo?.({
|
|
250
|
+
x: 0,
|
|
251
|
+
y: scrollBridge.scrollLockY,
|
|
252
|
+
animated: false,
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
// still update bridge y to the lock position for consistency
|
|
256
|
+
scrollBridge.y = scrollBridge.scrollLockY
|
|
257
|
+
// fire callback but with locked position (for UI updates)
|
|
258
|
+
const lockedEvent = {
|
|
259
|
+
...e,
|
|
260
|
+
nativeEvent: {
|
|
261
|
+
...e.nativeEvent,
|
|
262
|
+
contentOffset: {
|
|
263
|
+
...e.nativeEvent.contentOffset,
|
|
264
|
+
y: scrollBridge.scrollLockY,
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
}
|
|
268
|
+
onScroll?.(lockedEvent)
|
|
269
|
+
return
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// console.warn('[RNGH-Scroll] y:', y)
|
|
273
|
+
scrollBridge.y = y
|
|
274
|
+
if (y > 0) {
|
|
275
|
+
scrollBridge.scrollStartY = -1
|
|
276
|
+
}
|
|
277
|
+
onScroll?.(e)
|
|
278
|
+
}}
|
|
279
|
+
contentContainerStyle={{ minHeight: '100%' }}
|
|
280
|
+
bounces={false}
|
|
281
|
+
{...props}
|
|
282
|
+
>
|
|
283
|
+
{/* content height measurer */}
|
|
284
|
+
<View
|
|
285
|
+
position="absolute"
|
|
286
|
+
inset={0}
|
|
287
|
+
pointerEvents="none"
|
|
288
|
+
zIndex={-1}
|
|
289
|
+
onLayout={(e) => {
|
|
290
|
+
contentHeight.current = Math.floor(e.nativeEvent.layout.height)
|
|
291
|
+
setIsScrollable()
|
|
292
|
+
}}
|
|
293
|
+
/>
|
|
294
|
+
{children}
|
|
295
|
+
</RNGHComponent>
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Fallback: regular Tamagui ScrollView for web or when RNGH not available
|
|
300
|
+
const content = (
|
|
153
301
|
<ScrollView
|
|
154
302
|
onLayout={(e) => {
|
|
155
303
|
parentHeight.current = Math.ceil(e.nativeEvent.layout.height)
|
|
@@ -157,7 +305,7 @@ export const SheetScrollView = React.forwardRef<
|
|
|
157
305
|
}}
|
|
158
306
|
ref={composeRefs(scrollRef as any, ref)}
|
|
159
307
|
flex={1}
|
|
160
|
-
scrollEventThrottle={
|
|
308
|
+
scrollEventThrottle={1}
|
|
161
309
|
onResponderRelease={release}
|
|
162
310
|
className="_ovs-contain"
|
|
163
311
|
scrollEnabled={scrollable}
|
|
@@ -175,7 +323,6 @@ export const SheetScrollView = React.forwardRef<
|
|
|
175
323
|
scrollBridge.scrollStartY = -1
|
|
176
324
|
}
|
|
177
325
|
|
|
178
|
-
// Process the "onScroll" prop here if any
|
|
179
326
|
onScroll?.(e)
|
|
180
327
|
|
|
181
328
|
// This assures that we do not skip the "scrollBridge" values processing
|
|
@@ -272,5 +419,7 @@ export const SheetScrollView = React.forwardRef<
|
|
|
272
419
|
{children}
|
|
273
420
|
</ScrollView>
|
|
274
421
|
)
|
|
422
|
+
|
|
423
|
+
return content
|
|
275
424
|
}
|
|
276
425
|
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-export gesture state from @tamagui/native.
|
|
3
|
+
* Sheet uses this for backward compatibility with existing code.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
isGestureHandlerEnabled,
|
|
8
|
+
getGestureHandlerState,
|
|
9
|
+
} from '@tamagui/native'
|
|
10
|
+
|
|
11
|
+
export type { GestureState as GestureHandlerState } from '@tamagui/native'
|
|
12
|
+
|
|
13
|
+
// re-export setGestureHandlerState for backward compat with setupGestureHandler
|
|
14
|
+
export { setGestureHandlerState } from '@tamagui/native'
|
|
15
|
+
|
|
16
|
+
// alias for backward compatibility
|
|
17
|
+
export { setGestureHandlerState as setGestureState } from '@tamagui/native'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Legacy setup - prefer `import '@tamagui/native/setup-gesture-handler'` instead.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { setGestureHandlerState, isGestureHandlerEnabled } from '@tamagui/native'
|
|
6
|
+
|
|
7
|
+
export { isGestureHandlerEnabled }
|
|
8
|
+
|
|
9
|
+
export interface SetupGestureHandlerConfig {
|
|
10
|
+
Gesture: any
|
|
11
|
+
GestureDetector: any
|
|
12
|
+
ScrollView?: any
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function setupGestureHandler(config: SetupGestureHandlerConfig): void {
|
|
16
|
+
const g = globalThis as any
|
|
17
|
+
if (g.__tamagui_sheet_gesture_handler_setup) {
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
g.__tamagui_sheet_gesture_handler_setup = true
|
|
21
|
+
|
|
22
|
+
const { Gesture, GestureDetector, ScrollView } = config
|
|
23
|
+
|
|
24
|
+
if (Gesture && GestureDetector) {
|
|
25
|
+
setGestureHandlerState({
|
|
26
|
+
enabled: true,
|
|
27
|
+
Gesture,
|
|
28
|
+
GestureDetector,
|
|
29
|
+
ScrollView: ScrollView || null,
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/types.tsx
CHANGED
|
@@ -109,4 +109,19 @@ export type ScrollBridge = {
|
|
|
109
109
|
onParentDragging: (props: (val: boolean) => void) => () => void
|
|
110
110
|
setParentDragging: (val: boolean) => void
|
|
111
111
|
onFinishAnimate?: () => void
|
|
112
|
+
// gesture handler state for RNGH integration
|
|
113
|
+
blockPan?: boolean
|
|
114
|
+
initialPosition?: number
|
|
115
|
+
isScrollablePositionLocked?: boolean
|
|
116
|
+
// control scroll enabled state for RNGH coordination
|
|
117
|
+
// lockTo parameter: when disabling, lock scroll to this position (undefined = current position)
|
|
118
|
+
setScrollEnabled?: (enabled: boolean, lockTo?: number) => void
|
|
119
|
+
// track touch position for direction detection in RNGH
|
|
120
|
+
_lastTouchY?: number
|
|
121
|
+
// scroll lock position for forcing scroll back when pan handles
|
|
122
|
+
scrollLockY?: number
|
|
123
|
+
// force scroll to position (compensates for async setNativeProps)
|
|
124
|
+
forceScrollTo?: (y: number) => void
|
|
125
|
+
// whether sheet is at top position (for scroll enable/disable)
|
|
126
|
+
isAtTop?: boolean
|
|
112
127
|
}
|