react-native-screen-transitions 3.2.0 → 3.3.0-beta.0

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.
Files changed (152) hide show
  1. package/README.md +1 -1
  2. package/lib/commonjs/shared/components/create-transition-aware-component.js +8 -2
  3. package/lib/commonjs/shared/components/create-transition-aware-component.js.map +1 -1
  4. package/lib/commonjs/shared/components/{root-transition-aware.js → screen-container.js} +28 -12
  5. package/lib/commonjs/shared/components/screen-container.js.map +1 -0
  6. package/lib/commonjs/shared/configs/presets.js +3 -3
  7. package/lib/commonjs/shared/configs/presets.js.map +1 -1
  8. package/lib/commonjs/shared/configs/specs.js +6 -1
  9. package/lib/commonjs/shared/configs/specs.js.map +1 -1
  10. package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js +36 -188
  11. package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js.map +1 -1
  12. package/lib/commonjs/shared/hooks/gestures/use-screen-gesture-handlers.js +334 -0
  13. package/lib/commonjs/shared/hooks/gestures/use-screen-gesture-handlers.js.map +1 -0
  14. package/lib/commonjs/shared/hooks/gestures/use-scroll-registry.js +47 -4
  15. package/lib/commonjs/shared/hooks/gestures/use-scroll-registry.js.map +1 -1
  16. package/lib/commonjs/shared/hooks/lifecycle/use-close-transition.js +3 -3
  17. package/lib/commonjs/shared/hooks/lifecycle/use-close-transition.js.map +1 -1
  18. package/lib/commonjs/shared/hooks/lifecycle/use-open-transition.js +25 -3
  19. package/lib/commonjs/shared/hooks/lifecycle/use-open-transition.js.map +1 -1
  20. package/lib/commonjs/shared/hooks/navigation/use-screen-state.js +61 -2
  21. package/lib/commonjs/shared/hooks/navigation/use-screen-state.js.map +1 -1
  22. package/lib/commonjs/shared/hooks/use-backdrop-pointer-events.js +32 -0
  23. package/lib/commonjs/shared/hooks/use-backdrop-pointer-events.js.map +1 -0
  24. package/lib/commonjs/shared/providers/gestures.provider.js +4 -2
  25. package/lib/commonjs/shared/providers/gestures.provider.js.map +1 -1
  26. package/lib/commonjs/shared/providers/screen/screen-composer.js +2 -2
  27. package/lib/commonjs/shared/providers/screen/screen-composer.js.map +1 -1
  28. package/lib/commonjs/shared/providers/screen/styles.provider.js +41 -32
  29. package/lib/commonjs/shared/providers/screen/styles.provider.js.map +1 -1
  30. package/lib/commonjs/shared/utils/animation/{start-screen-transition.js → animate-to-progress.js} +11 -7
  31. package/lib/commonjs/shared/utils/animation/animate-to-progress.js.map +1 -0
  32. package/lib/commonjs/shared/utils/gesture/check-gesture-activation.js +71 -0
  33. package/lib/commonjs/shared/utils/gesture/check-gesture-activation.js.map +1 -1
  34. package/lib/commonjs/shared/utils/gesture/determine-snap-target.js +56 -0
  35. package/lib/commonjs/shared/utils/gesture/determine-snap-target.js.map +1 -0
  36. package/lib/commonjs/shared/utils/gesture/validate-snap-points.js +31 -0
  37. package/lib/commonjs/shared/utils/gesture/validate-snap-points.js.map +1 -0
  38. package/lib/commonjs/shared/utils/gesture/velocity.js +11 -0
  39. package/lib/commonjs/shared/utils/gesture/velocity.js.map +1 -1
  40. package/lib/module/shared/components/create-transition-aware-component.js +8 -2
  41. package/lib/module/shared/components/create-transition-aware-component.js.map +1 -1
  42. package/lib/module/shared/components/screen-container.js +64 -0
  43. package/lib/module/shared/components/screen-container.js.map +1 -0
  44. package/lib/module/shared/configs/presets.js +3 -3
  45. package/lib/module/shared/configs/presets.js.map +1 -1
  46. package/lib/module/shared/configs/specs.js +5 -0
  47. package/lib/module/shared/configs/specs.js.map +1 -1
  48. package/lib/module/shared/hooks/gestures/use-build-gestures.js +36 -187
  49. package/lib/module/shared/hooks/gestures/use-build-gestures.js.map +1 -1
  50. package/lib/module/shared/hooks/gestures/use-screen-gesture-handlers.js +328 -0
  51. package/lib/module/shared/hooks/gestures/use-screen-gesture-handlers.js.map +1 -0
  52. package/lib/module/shared/hooks/gestures/use-scroll-registry.js +47 -4
  53. package/lib/module/shared/hooks/gestures/use-scroll-registry.js.map +1 -1
  54. package/lib/module/shared/hooks/lifecycle/use-close-transition.js +3 -3
  55. package/lib/module/shared/hooks/lifecycle/use-close-transition.js.map +1 -1
  56. package/lib/module/shared/hooks/lifecycle/use-open-transition.js +25 -3
  57. package/lib/module/shared/hooks/lifecycle/use-open-transition.js.map +1 -1
  58. package/lib/module/shared/hooks/navigation/use-screen-state.js +63 -4
  59. package/lib/module/shared/hooks/navigation/use-screen-state.js.map +1 -1
  60. package/lib/module/shared/hooks/use-backdrop-pointer-events.js +28 -0
  61. package/lib/module/shared/hooks/use-backdrop-pointer-events.js.map +1 -0
  62. package/lib/module/shared/providers/gestures.provider.js +4 -2
  63. package/lib/module/shared/providers/gestures.provider.js.map +1 -1
  64. package/lib/module/shared/providers/screen/screen-composer.js +2 -2
  65. package/lib/module/shared/providers/screen/screen-composer.js.map +1 -1
  66. package/lib/module/shared/providers/screen/styles.provider.js +41 -32
  67. package/lib/module/shared/providers/screen/styles.provider.js.map +1 -1
  68. package/lib/module/shared/utils/animation/{start-screen-transition.js → animate-to-progress.js} +9 -5
  69. package/lib/module/shared/utils/animation/animate-to-progress.js.map +1 -0
  70. package/lib/module/shared/utils/gesture/check-gesture-activation.js +70 -0
  71. package/lib/module/shared/utils/gesture/check-gesture-activation.js.map +1 -1
  72. package/lib/module/shared/utils/gesture/determine-snap-target.js +52 -0
  73. package/lib/module/shared/utils/gesture/determine-snap-target.js.map +1 -0
  74. package/lib/module/shared/utils/gesture/validate-snap-points.js +26 -0
  75. package/lib/module/shared/utils/gesture/validate-snap-points.js.map +1 -0
  76. package/lib/module/shared/utils/gesture/velocity.js +11 -0
  77. package/lib/module/shared/utils/gesture/velocity.js.map +1 -1
  78. package/lib/typescript/shared/components/create-transition-aware-component.d.ts.map +1 -1
  79. package/lib/typescript/shared/components/screen-container.d.ts +6 -0
  80. package/lib/typescript/shared/components/screen-container.d.ts.map +1 -0
  81. package/lib/typescript/shared/configs/specs.d.ts +1 -0
  82. package/lib/typescript/shared/configs/specs.d.ts.map +1 -1
  83. package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts +1 -1
  84. package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts.map +1 -1
  85. package/lib/typescript/shared/hooks/gestures/use-screen-gesture-handlers.d.ts +34 -0
  86. package/lib/typescript/shared/hooks/gestures/use-screen-gesture-handlers.d.ts.map +1 -0
  87. package/lib/typescript/shared/hooks/gestures/use-scroll-registry.d.ts +5 -1
  88. package/lib/typescript/shared/hooks/gestures/use-scroll-registry.d.ts.map +1 -1
  89. package/lib/typescript/shared/hooks/lifecycle/use-open-transition.d.ts.map +1 -1
  90. package/lib/typescript/shared/hooks/navigation/use-screen-state.d.ts +14 -0
  91. package/lib/typescript/shared/hooks/navigation/use-screen-state.d.ts.map +1 -1
  92. package/lib/typescript/shared/hooks/use-backdrop-pointer-events.d.ts +15 -0
  93. package/lib/typescript/shared/hooks/use-backdrop-pointer-events.d.ts.map +1 -0
  94. package/lib/typescript/shared/providers/gestures.provider.d.ts +1 -0
  95. package/lib/typescript/shared/providers/gestures.provider.d.ts.map +1 -1
  96. package/lib/typescript/shared/providers/screen/styles.provider.d.ts.map +1 -1
  97. package/lib/typescript/shared/types/animation.types.d.ts +28 -2
  98. package/lib/typescript/shared/types/animation.types.d.ts.map +1 -1
  99. package/lib/typescript/shared/types/screen.types.d.ts +26 -0
  100. package/lib/typescript/shared/types/screen.types.d.ts.map +1 -1
  101. package/lib/typescript/shared/utils/animation/animate-to-progress.d.ts +19 -0
  102. package/lib/typescript/shared/utils/animation/animate-to-progress.d.ts.map +1 -0
  103. package/lib/typescript/shared/utils/gesture/check-gesture-activation.d.ts +24 -0
  104. package/lib/typescript/shared/utils/gesture/check-gesture-activation.d.ts.map +1 -1
  105. package/lib/typescript/shared/utils/gesture/determine-snap-target.d.ts +26 -0
  106. package/lib/typescript/shared/utils/gesture/determine-snap-target.d.ts.map +1 -0
  107. package/lib/typescript/shared/utils/gesture/validate-snap-points.d.ts +13 -0
  108. package/lib/typescript/shared/utils/gesture/validate-snap-points.d.ts.map +1 -0
  109. package/lib/typescript/shared/utils/gesture/velocity.d.ts +1 -0
  110. package/lib/typescript/shared/utils/gesture/velocity.d.ts.map +1 -1
  111. package/package.json +29 -2
  112. package/src/shared/__tests__/determine-snap-target.test.ts +268 -0
  113. package/src/shared/__tests__/gesture-activation.test.ts +247 -0
  114. package/src/shared/__tests__/validate-snap-points.test.ts +125 -0
  115. package/src/shared/components/create-transition-aware-component.tsx +11 -1
  116. package/src/shared/components/screen-container.tsx +65 -0
  117. package/src/shared/configs/presets.ts +3 -3
  118. package/src/shared/configs/specs.ts +6 -0
  119. package/src/shared/hooks/gestures/use-build-gestures.tsx +33 -253
  120. package/src/shared/hooks/gestures/use-screen-gesture-handlers.ts +436 -0
  121. package/src/shared/hooks/gestures/use-scroll-registry.tsx +52 -1
  122. package/src/shared/hooks/lifecycle/use-close-transition.ts +3 -3
  123. package/src/shared/hooks/lifecycle/use-open-transition.ts +27 -3
  124. package/src/shared/hooks/navigation/use-screen-state.tsx +106 -2
  125. package/src/shared/hooks/use-backdrop-pointer-events.ts +32 -0
  126. package/src/shared/providers/gestures.provider.tsx +3 -2
  127. package/src/shared/providers/screen/screen-composer.tsx +2 -2
  128. package/src/shared/providers/screen/styles.provider.tsx +40 -34
  129. package/src/shared/types/animation.types.ts +29 -2
  130. package/src/shared/types/screen.types.ts +29 -0
  131. package/src/shared/utils/animation/{start-screen-transition.ts → animate-to-progress.ts} +19 -7
  132. package/src/shared/utils/gesture/check-gesture-activation.ts +78 -0
  133. package/src/shared/utils/gesture/determine-snap-target.ts +75 -0
  134. package/src/shared/utils/gesture/validate-snap-points.ts +37 -0
  135. package/src/shared/utils/gesture/velocity.ts +10 -0
  136. package/lib/commonjs/shared/components/root-transition-aware.js.map +0 -1
  137. package/lib/commonjs/shared/hooks/use-stack-pointer-events.js +0 -23
  138. package/lib/commonjs/shared/hooks/use-stack-pointer-events.js.map +0 -1
  139. package/lib/commonjs/shared/utils/animation/start-screen-transition.js.map +0 -1
  140. package/lib/module/shared/components/root-transition-aware.js +0 -48
  141. package/lib/module/shared/components/root-transition-aware.js.map +0 -1
  142. package/lib/module/shared/hooks/use-stack-pointer-events.js +0 -20
  143. package/lib/module/shared/hooks/use-stack-pointer-events.js.map +0 -1
  144. package/lib/module/shared/utils/animation/start-screen-transition.js.map +0 -1
  145. package/lib/typescript/shared/components/root-transition-aware.d.ts +0 -6
  146. package/lib/typescript/shared/components/root-transition-aware.d.ts.map +0 -1
  147. package/lib/typescript/shared/hooks/use-stack-pointer-events.d.ts +0 -10
  148. package/lib/typescript/shared/hooks/use-stack-pointer-events.d.ts.map +0 -1
  149. package/lib/typescript/shared/utils/animation/start-screen-transition.d.ts +0 -13
  150. package/lib/typescript/shared/utils/animation/start-screen-transition.d.ts.map +0 -1
  151. package/src/shared/components/root-transition-aware.tsx +0 -49
  152. package/src/shared/hooks/use-stack-pointer-events.ts +0 -15
@@ -0,0 +1,436 @@
1
+ import { useMemo } from "react";
2
+ import type {
3
+ GestureStateChangeEvent,
4
+ GestureTouchEvent,
5
+ GestureUpdateEvent,
6
+ PanGestureHandlerEventPayload,
7
+ } from "react-native-gesture-handler";
8
+ import type { GestureStateManagerType } from "react-native-gesture-handler/lib/typescript/handlers/gestures/gestureStateManager";
9
+ import { type SharedValue, useSharedValue } from "react-native-reanimated";
10
+ import { DefaultSnapSpec } from "../../configs/specs";
11
+ import { FALSE, TRUE } from "../../constants";
12
+ import type { ScrollConfig } from "../../providers/gestures.provider";
13
+ import type { AnimationStoreMap } from "../../stores/animation.store";
14
+ import type { GestureStoreMap } from "../../stores/gesture.store";
15
+ import type { TransitionSpec } from "../../types/animation.types";
16
+ import {
17
+ type GestureActivationArea,
18
+ type GestureDirection,
19
+ GestureOffsetState,
20
+ } from "../../types/gesture.types";
21
+ import type { Layout } from "../../types/screen.types";
22
+ import { animateToProgress } from "../../utils/animation/animate-to-progress";
23
+ import {
24
+ applyOffsetRules,
25
+ checkScrollAwareActivation,
26
+ } from "../../utils/gesture/check-gesture-activation";
27
+ import { determineDismissal } from "../../utils/gesture/determine-dismissal";
28
+ import { determineSnapTarget } from "../../utils/gesture/determine-snap-target";
29
+ import { mapGestureToProgress } from "../../utils/gesture/map-gesture-to-progress";
30
+ import { resetGestureValues } from "../../utils/gesture/reset-gesture-values";
31
+ import { validateSnapPoints } from "../../utils/gesture/validate-snap-points";
32
+ import { velocity } from "../../utils/gesture/velocity";
33
+ import useStableCallbackValue from "../use-stable-callback-value";
34
+
35
+ interface UseScreenGestureHandlersProps {
36
+ dimensions: Layout;
37
+ animations: AnimationStoreMap;
38
+ gestureAnimationValues: GestureStoreMap;
39
+ gestureDirection: GestureDirection | GestureDirection[];
40
+ gestureDrivesProgress: boolean;
41
+ gestureVelocityImpact: number;
42
+ scrollConfig: SharedValue<ScrollConfig | null>;
43
+ gestureActivationArea: GestureActivationArea;
44
+ gestureResponseDistance?: number;
45
+ ancestorIsDismissing?: SharedValue<number> | null;
46
+ snapPoints?: number[];
47
+ canDismiss: boolean;
48
+ transitionSpec?: TransitionSpec;
49
+ handleDismiss: () => void;
50
+ }
51
+
52
+ export const useScreenGestureHandlers = ({
53
+ dimensions,
54
+ animations,
55
+ gestureAnimationValues,
56
+ gestureDirection,
57
+ gestureDrivesProgress,
58
+ gestureVelocityImpact,
59
+ scrollConfig,
60
+ gestureActivationArea,
61
+ gestureResponseDistance,
62
+ ancestorIsDismissing,
63
+ snapPoints: rawSnapPoints,
64
+ canDismiss,
65
+ transitionSpec,
66
+ handleDismiss,
67
+ }: UseScreenGestureHandlersProps) => {
68
+ const { hasSnapPoints, snapPoints, minSnapPoint, maxSnapPoint } = useMemo(
69
+ () => validateSnapPoints({ snapPoints: rawSnapPoints, canDismiss }),
70
+ [rawSnapPoints, canDismiss],
71
+ );
72
+
73
+ const directions = useMemo(() => {
74
+ const directionsArray = Array.isArray(gestureDirection)
75
+ ? gestureDirection
76
+ : [gestureDirection];
77
+
78
+ const isBidirectional = directionsArray.includes("bidirectional");
79
+
80
+ const hasHorizontalDirection =
81
+ directionsArray.includes("horizontal") ||
82
+ directionsArray.includes("horizontal-inverted");
83
+
84
+ const isSnapAxisInverted = hasHorizontalDirection
85
+ ? directionsArray.includes("horizontal-inverted") &&
86
+ !directionsArray.includes("horizontal")
87
+ : directionsArray.includes("vertical-inverted") &&
88
+ !directionsArray.includes("vertical");
89
+
90
+ const enableBothVertical =
91
+ isBidirectional || (hasSnapPoints && !hasHorizontalDirection);
92
+ const enableBothHorizontal =
93
+ isBidirectional || (hasSnapPoints && hasHorizontalDirection);
94
+
95
+ return {
96
+ vertical: directionsArray.includes("vertical") || enableBothVertical,
97
+ verticalInverted:
98
+ directionsArray.includes("vertical-inverted") || enableBothVertical,
99
+ horizontal:
100
+ directionsArray.includes("horizontal") || enableBothHorizontal,
101
+ horizontalInverted:
102
+ directionsArray.includes("horizontal-inverted") || enableBothHorizontal,
103
+ snapAxisInverted: hasSnapPoints && isSnapAxisInverted,
104
+ };
105
+ }, [gestureDirection, hasSnapPoints]);
106
+
107
+ const snapAxis =
108
+ directions.horizontal || directions.horizontalInverted
109
+ ? "horizontal"
110
+ : "vertical";
111
+
112
+ const initialTouch = useSharedValue({ x: 0, y: 0 });
113
+ const gestureOffsetState = useSharedValue<GestureOffsetState>(
114
+ GestureOffsetState.PENDING,
115
+ );
116
+ const gestureStartProgress = useSharedValue(1);
117
+
118
+ const onTouchesDown = useStableCallbackValue((e: GestureTouchEvent) => {
119
+ "worklet";
120
+ const firstTouch = e.changedTouches[0];
121
+ initialTouch.value = { x: firstTouch.x, y: firstTouch.y };
122
+ gestureOffsetState.value = GestureOffsetState.PENDING;
123
+ });
124
+
125
+ const onTouchesMove = useStableCallbackValue(
126
+ (e: GestureTouchEvent, manager: GestureStateManagerType) => {
127
+ "worklet";
128
+
129
+ // If an ancestor navigator is already dismissing via gesture, block new gestures here.
130
+ if (ancestorIsDismissing?.value) {
131
+ gestureOffsetState.set(GestureOffsetState.FAILED);
132
+ manager.fail();
133
+ return;
134
+ }
135
+
136
+ const touch = e.changedTouches[0];
137
+
138
+ const { isSwipingDown, isSwipingUp, isSwipingRight, isSwipingLeft } =
139
+ applyOffsetRules({
140
+ touch,
141
+ directions,
142
+ manager,
143
+ dimensions,
144
+ gestureOffsetState,
145
+ initialTouch: initialTouch.value,
146
+ activationArea: gestureActivationArea,
147
+ responseDistance: gestureResponseDistance,
148
+ });
149
+
150
+ if (gestureOffsetState.value === GestureOffsetState.FAILED) {
151
+ manager.fail();
152
+ return;
153
+ }
154
+
155
+ // Keep pending until thresholds are met; no eager activation.
156
+ if (gestureAnimationValues.isDragging?.value) {
157
+ manager.activate();
158
+ return;
159
+ }
160
+
161
+ const recognizedDirection =
162
+ isSwipingDown || isSwipingUp || isSwipingRight || isSwipingLeft;
163
+
164
+ const scrollCfg = scrollConfig.value;
165
+ const isTouchingScrollView = scrollCfg?.isTouched ?? false;
166
+
167
+ if (!isTouchingScrollView) {
168
+ // Early return if gesture hasn't met activation criteria
169
+ const canActivate =
170
+ recognizedDirection &&
171
+ gestureOffsetState.value === GestureOffsetState.PASSED &&
172
+ !gestureAnimationValues.isDismissing?.value;
173
+
174
+ if (!canActivate) {
175
+ return;
176
+ }
177
+
178
+ if (isSwipingDown) {
179
+ gestureAnimationValues.direction.set("vertical");
180
+ } else if (isSwipingUp) {
181
+ gestureAnimationValues.direction.set("vertical-inverted");
182
+ } else if (isSwipingRight) {
183
+ gestureAnimationValues.direction.set("horizontal");
184
+ } else if (isSwipingLeft) {
185
+ gestureAnimationValues.direction.set("horizontal-inverted");
186
+ }
187
+
188
+ manager.activate();
189
+ return;
190
+ }
191
+
192
+ // Touch IS on ScrollView - apply scroll-aware rules
193
+ const scrollX = scrollCfg?.x ?? 0;
194
+ const scrollY = scrollCfg?.y ?? 0;
195
+ const maxScrollX = scrollCfg?.contentWidth
196
+ ? scrollCfg.contentWidth - scrollCfg.layoutWidth
197
+ : 0;
198
+ const maxScrollY = scrollCfg?.contentHeight
199
+ ? scrollCfg.contentHeight - scrollCfg.layoutHeight
200
+ : 0;
201
+
202
+ // Snap mode: determine if sheet can still expand
203
+ const canExpandMore =
204
+ hasSnapPoints && animations.progress.value < maxSnapPoint - 0.01;
205
+
206
+ const { shouldActivate, direction: activatedDirection } =
207
+ checkScrollAwareActivation({
208
+ swipeInfo: {
209
+ isSwipingDown,
210
+ isSwipingUp,
211
+ isSwipingRight,
212
+ isSwipingLeft,
213
+ },
214
+ directions,
215
+ scrollX,
216
+ scrollY,
217
+ maxScrollX,
218
+ maxScrollY,
219
+ hasSnapPoints,
220
+ canExpandMore,
221
+ });
222
+
223
+ if (recognizedDirection && !shouldActivate) {
224
+ manager.fail();
225
+ return;
226
+ }
227
+
228
+ if (
229
+ shouldActivate &&
230
+ gestureOffsetState.value === GestureOffsetState.PASSED &&
231
+ !gestureAnimationValues.isDismissing?.value
232
+ ) {
233
+ gestureAnimationValues.direction.value = activatedDirection;
234
+ manager.activate();
235
+ return;
236
+ }
237
+ },
238
+ );
239
+
240
+ const onStart = useStableCallbackValue(() => {
241
+ "worklet";
242
+ gestureAnimationValues.isDragging.value = TRUE;
243
+ gestureAnimationValues.isDismissing.value = FALSE;
244
+ gestureStartProgress.value = animations.progress.value;
245
+ });
246
+
247
+ const onUpdate = useStableCallbackValue(
248
+ (event: GestureUpdateEvent<PanGestureHandlerEventPayload>) => {
249
+ "worklet";
250
+
251
+ const { translationX, translationY } = event;
252
+ const { width, height } = dimensions;
253
+
254
+ // Update gesture values (shared across all modes)
255
+ gestureAnimationValues.x.value = translationX;
256
+ gestureAnimationValues.y.value = translationY;
257
+ gestureAnimationValues.normalizedX.value = velocity.normalizeTranslation(
258
+ translationX,
259
+ width,
260
+ );
261
+ gestureAnimationValues.normalizedY.value = velocity.normalizeTranslation(
262
+ translationY,
263
+ height,
264
+ );
265
+
266
+ if (hasSnapPoints && gestureDrivesProgress) {
267
+ // Snap mode: bidirectional tracking on snap axis
268
+ const isHorizontal = snapAxis === "horizontal";
269
+ const translation = isHorizontal ? translationX : translationY;
270
+ const dimension = isHorizontal ? width : height;
271
+
272
+ // Map translation to progress delta:
273
+ // - Positive translation (down/right) = decrease progress (dismiss)
274
+ // - Negative translation (up/left) = increase progress (expand)
275
+ // Inverted directions flip this behavior
276
+ const baseSign = -1;
277
+ const sign = directions.snapAxisInverted ? -baseSign : baseSign;
278
+ const progressDelta = (sign * translation) / dimension;
279
+
280
+ // Use pre-computed bounds (minSnapPoint already accounts for canDismiss)
281
+ animations.progress.value = Math.max(
282
+ minSnapPoint,
283
+ Math.min(maxSnapPoint, gestureStartProgress.value + progressDelta),
284
+ );
285
+ } else if (gestureDrivesProgress) {
286
+ // Standard mode: find max progress across allowed directions
287
+ const axes = [
288
+ {
289
+ enabled: directions.horizontal,
290
+ translation: translationX,
291
+ dimension: width,
292
+ sign: 1,
293
+ },
294
+ {
295
+ enabled: directions.horizontalInverted,
296
+ translation: translationX,
297
+ dimension: width,
298
+ sign: -1,
299
+ },
300
+ {
301
+ enabled: directions.vertical,
302
+ translation: translationY,
303
+ dimension: height,
304
+ sign: 1,
305
+ },
306
+ {
307
+ enabled: directions.verticalInverted,
308
+ translation: translationY,
309
+ dimension: height,
310
+ sign: -1,
311
+ },
312
+ ];
313
+
314
+ let maxProgress = 0;
315
+ for (const axis of axes) {
316
+ if (axis.enabled && axis.translation * axis.sign > 0) {
317
+ const progress = mapGestureToProgress(
318
+ Math.abs(axis.translation),
319
+ axis.dimension,
320
+ );
321
+ maxProgress = Math.max(maxProgress, progress);
322
+ }
323
+ }
324
+
325
+ animations.progress.value = Math.max(
326
+ 0,
327
+ Math.min(1, gestureStartProgress.value - maxProgress),
328
+ );
329
+ }
330
+ },
331
+ );
332
+
333
+ const onEnd = useStableCallbackValue(
334
+ (event: GestureStateChangeEvent<PanGestureHandlerEventPayload>) => {
335
+ "worklet";
336
+
337
+ if (hasSnapPoints) {
338
+ const isHorizontal = snapAxis === "horizontal";
339
+ const axisVelocity = isHorizontal ? event.velocityX : event.velocityY;
340
+ const axisDimension = isHorizontal
341
+ ? dimensions.width
342
+ : dimensions.height;
343
+
344
+ // determineSnapTarget expects positive velocity = toward dismiss (decreasing progress)
345
+ // Positive velocity (down/right) = dismiss for non-inverted
346
+ // Inverted directions need velocity flipped
347
+ const snapVelocity = directions.snapAxisInverted
348
+ ? -axisVelocity
349
+ : axisVelocity;
350
+
351
+ const result = determineSnapTarget({
352
+ currentProgress: animations.progress.value,
353
+ snapPoints,
354
+ velocity: snapVelocity,
355
+ dimension: axisDimension,
356
+ canDismiss: canDismiss,
357
+ });
358
+
359
+ const shouldDismiss = result.shouldDismiss;
360
+ const targetProgress = result.targetProgress;
361
+ const isSnapping = !shouldDismiss;
362
+
363
+ const spec = shouldDismiss
364
+ ? transitionSpec?.close
365
+ : transitionSpec?.open;
366
+
367
+ const effectiveSpec = isSnapping
368
+ ? {
369
+ open: transitionSpec?.expand ?? DefaultSnapSpec,
370
+ close: transitionSpec?.collapse ?? DefaultSnapSpec,
371
+ }
372
+ : transitionSpec;
373
+
374
+ resetGestureValues({
375
+ spec,
376
+ gestures: gestureAnimationValues,
377
+ shouldDismiss,
378
+ event,
379
+ dimensions,
380
+ });
381
+
382
+ // For snap transitions, velocity should match gesture direction
383
+ // Positive gesture velocity (down/right) = collapsing (negative progress velocity)
384
+ // Inverted directions flip this
385
+ const velocitySign = directions.snapAxisInverted ? 1 : -1;
386
+ const initialVelocity =
387
+ velocitySign * velocity.normalize(axisVelocity, axisDimension);
388
+
389
+ animateToProgress({
390
+ target: targetProgress,
391
+ onAnimationFinish: shouldDismiss ? handleDismiss : undefined,
392
+ spec: effectiveSpec,
393
+ animations,
394
+ initialVelocity,
395
+ });
396
+ } else {
397
+ // Standard mode: use determineDismissal
398
+ const result = determineDismissal({
399
+ event,
400
+ directions,
401
+ dimensions,
402
+ gestureVelocityImpact,
403
+ });
404
+
405
+ const shouldDismiss = result.shouldDismiss;
406
+ const targetProgress = shouldDismiss ? 0 : gestureStartProgress.value;
407
+
408
+ resetGestureValues({
409
+ spec: shouldDismiss ? transitionSpec?.close : transitionSpec?.open,
410
+ gestures: gestureAnimationValues,
411
+ shouldDismiss,
412
+ event,
413
+ dimensions,
414
+ });
415
+
416
+ const initialVelocity = velocity.calculateProgressVelocity({
417
+ animations,
418
+ shouldDismiss,
419
+ event,
420
+ dimensions,
421
+ directions,
422
+ });
423
+
424
+ animateToProgress({
425
+ target: targetProgress,
426
+ onAnimationFinish: shouldDismiss ? handleDismiss : undefined,
427
+ spec: transitionSpec,
428
+ animations,
429
+ initialVelocity,
430
+ });
431
+ }
432
+ },
433
+ );
434
+
435
+ return { onTouchesDown, onTouchesMove, onStart, onUpdate, onEnd };
436
+ };
@@ -1,7 +1,7 @@
1
1
  /** biome-ignore-all lint/style/noNonNullAssertion: <Will always consume context from GestureProvider> */
2
2
 
3
3
  import { useMemo } from "react";
4
- import type { LayoutChangeEvent } from "react-native";
4
+ import type { GestureResponderEvent, LayoutChangeEvent } from "react-native";
5
5
  import { useAnimatedScrollHandler } from "react-native-reanimated";
6
6
  import type { ReanimatedScrollEvent } from "react-native-reanimated/lib/typescript/hook/commonTypes";
7
7
  import { useGestureContext } from "../../providers/gestures.provider";
@@ -11,6 +11,8 @@ interface ScrollProgressHookProps {
11
11
  onScroll?: (event: ReanimatedScrollEvent) => void;
12
12
  onContentSizeChange?: (width: number, height: number) => void;
13
13
  onLayout?: (event: LayoutChangeEvent) => void;
14
+ onTouchStart?: (event: GestureResponderEvent) => void;
15
+ onTouchEnd?: (event: GestureResponderEvent) => void;
14
16
  }
15
17
 
16
18
  export const useScrollRegistry = (props: ScrollProgressHookProps) => {
@@ -43,6 +45,7 @@ export const useScrollRegistry = (props: ScrollProgressHookProps) => {
43
45
  contentWidth: 0,
44
46
  layoutHeight: 0,
45
47
  layoutWidth: 0,
48
+ isTouched: true,
46
49
  };
47
50
  }
48
51
  v.x = event.contentOffset.x;
@@ -73,6 +76,7 @@ export const useScrollRegistry = (props: ScrollProgressHookProps) => {
73
76
  layoutWidth: 0,
74
77
  contentWidth: width,
75
78
  contentHeight: height,
79
+ isTouched: false,
76
80
  };
77
81
  }
78
82
  v.contentWidth = width;
@@ -102,6 +106,7 @@ export const useScrollRegistry = (props: ScrollProgressHookProps) => {
102
106
  contentWidth: 0,
103
107
  layoutHeight: height,
104
108
  layoutWidth: width,
109
+ isTouched: false,
105
110
  };
106
111
  }
107
112
  v.layoutHeight = height;
@@ -116,9 +121,55 @@ export const useScrollRegistry = (props: ScrollProgressHookProps) => {
116
121
  }
117
122
  });
118
123
 
124
+ const onTouchStart = useStableCallback((event: GestureResponderEvent) => {
125
+ props.onTouchStart?.(event);
126
+
127
+ const setTouched = (v: any) => {
128
+ "worklet";
129
+ if (v === null) {
130
+ return {
131
+ x: 0,
132
+ y: 0,
133
+ contentHeight: 0,
134
+ contentWidth: 0,
135
+ layoutHeight: 0,
136
+ layoutWidth: 0,
137
+ isTouched: true,
138
+ };
139
+ }
140
+ v.isTouched = true;
141
+ return v;
142
+ };
143
+
144
+ scrollConfig.modify(setTouched);
145
+
146
+ for (const ancestorConfig of ancestorScrollConfigs) {
147
+ ancestorConfig.modify(setTouched);
148
+ }
149
+ });
150
+
151
+ const onTouchEnd = useStableCallback((event: GestureResponderEvent) => {
152
+ props.onTouchEnd?.(event);
153
+
154
+ const clearTouched = (v: any) => {
155
+ "worklet";
156
+ if (v === null) return v;
157
+ v.isTouched = false;
158
+ return v;
159
+ };
160
+
161
+ scrollConfig.modify(clearTouched);
162
+
163
+ for (const ancestorConfig of ancestorScrollConfigs) {
164
+ ancestorConfig.modify(clearTouched);
165
+ }
166
+ });
167
+
119
168
  return {
120
169
  scrollHandler,
121
170
  onContentSizeChange,
122
171
  onLayout,
172
+ onTouchStart,
173
+ onTouchEnd,
123
174
  };
124
175
  };
@@ -10,7 +10,7 @@ import { useStackCoreContext } from "../../providers/stack/core.provider";
10
10
  import { useManagedStackContext } from "../../providers/stack/managed.provider";
11
11
  import type { AnimationStoreMap } from "../../stores/animation.store";
12
12
  import { StackType } from "../../types/stack.types";
13
- import { startScreenTransition } from "../../utils/animation/start-screen-transition";
13
+ import { animateToProgress } from "../../utils/animation/animate-to-progress";
14
14
  import { resetStoresForScreen } from "../../utils/reset-stores-for-screen";
15
15
  import { useSharedValueState } from "../reanimated/use-shared-value-state";
16
16
  import useStableCallback from "../use-stable-callback";
@@ -49,7 +49,7 @@ const useManagedClose = ({
49
49
  if (!keys?.includes(current.route.key)) return;
50
50
 
51
51
  runOnJS(activate)();
52
- startScreenTransition({
52
+ animateToProgress({
53
53
  target: "close",
54
54
  spec: current.options.transitionSpec,
55
55
  animations,
@@ -96,7 +96,7 @@ const useNativeStackClose = ({
96
96
  e.preventDefault();
97
97
  activate();
98
98
 
99
- startScreenTransition({
99
+ animateToProgress({
100
100
  target: "close",
101
101
  spec: current.options.transitionSpec,
102
102
  animations,
@@ -1,9 +1,30 @@
1
1
  import { useLayoutEffect } from "react";
2
2
  import type { BaseDescriptor } from "../../providers/screen/keys.provider";
3
3
  import type { AnimationStoreMap } from "../../stores/animation.store";
4
- import { startScreenTransition } from "../../utils/animation/start-screen-transition";
4
+ import { animateToProgress } from "../../utils/animation/animate-to-progress";
5
5
  import { useHighRefreshRate } from "../animation/use-high-refresh-rate";
6
6
 
7
+ /**
8
+ * Calculates the initial progress value based on snap points configuration.
9
+ */
10
+ function getInitialProgress({
11
+ snapPoints,
12
+ initialSnapIndex,
13
+ }: {
14
+ snapPoints?: number[];
15
+ initialSnapIndex: number;
16
+ }): number | undefined {
17
+ if (!snapPoints) {
18
+ return undefined;
19
+ }
20
+
21
+ const clampedIndex = Math.min(
22
+ Math.max(0, initialSnapIndex),
23
+ snapPoints.length - 1,
24
+ );
25
+ return snapPoints[clampedIndex];
26
+ }
27
+
7
28
  /**
8
29
  * Handles opening animation on mount.
9
30
  * Returns activate/deactivate functions for high refresh rate.
@@ -17,9 +38,12 @@ export function useOpenTransition(
17
38
 
18
39
  // biome-ignore lint/correctness/useExhaustiveDependencies: Must only run once on mount
19
40
  useLayoutEffect(() => {
41
+ const { snapPoints, initialSnapIndex = 0 } = current.options;
42
+ const targetProgress = getInitialProgress({ snapPoints, initialSnapIndex });
43
+
20
44
  activateHighRefreshRate();
21
- startScreenTransition({
22
- target: "open",
45
+ animateToProgress({
46
+ target: targetProgress ?? "open",
23
47
  spec: current.options.transitionSpec,
24
48
  animations,
25
49
  onAnimationFinish: deactivateHighRefreshRate,