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

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 (66) hide show
  1. package/README.md +104 -9
  2. package/lib/commonjs/blank-stack/components/adjusted-screen.js +2 -2
  3. package/lib/commonjs/blank-stack/components/adjusted-screen.js.map +1 -1
  4. package/lib/commonjs/shared/constants.js +36 -10
  5. package/lib/commonjs/shared/constants.js.map +1 -1
  6. package/lib/commonjs/shared/hooks/animation/use-screen-animation.js +25 -18
  7. package/lib/commonjs/shared/hooks/animation/use-screen-animation.js.map +1 -1
  8. package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js +0 -25
  9. package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js.map +1 -1
  10. package/lib/commonjs/shared/hooks/gestures/use-screen-gesture-handlers.js +71 -63
  11. package/lib/commonjs/shared/hooks/gestures/use-screen-gesture-handlers.js.map +1 -1
  12. package/lib/commonjs/shared/hooks/navigation/use-screen-state.js +9 -37
  13. package/lib/commonjs/shared/hooks/navigation/use-screen-state.js.map +1 -1
  14. package/lib/commonjs/shared/utils/animation/animate-to-progress.js +5 -1
  15. package/lib/commonjs/shared/utils/animation/animate-to-progress.js.map +1 -1
  16. package/lib/commonjs/shared/utils/gesture/check-gesture-activation.js +90 -23
  17. package/lib/commonjs/shared/utils/gesture/check-gesture-activation.js.map +1 -1
  18. package/lib/commonjs/shared/utils/logger.js +22 -0
  19. package/lib/commonjs/shared/utils/logger.js.map +1 -0
  20. package/lib/module/blank-stack/components/adjusted-screen.js +1 -1
  21. package/lib/module/blank-stack/components/adjusted-screen.js.map +1 -1
  22. package/lib/module/shared/constants.js +34 -9
  23. package/lib/module/shared/constants.js.map +1 -1
  24. package/lib/module/shared/hooks/animation/use-screen-animation.js +25 -18
  25. package/lib/module/shared/hooks/animation/use-screen-animation.js.map +1 -1
  26. package/lib/module/shared/hooks/gestures/use-build-gestures.js +0 -25
  27. package/lib/module/shared/hooks/gestures/use-build-gestures.js.map +1 -1
  28. package/lib/module/shared/hooks/gestures/use-screen-gesture-handlers.js +66 -58
  29. package/lib/module/shared/hooks/gestures/use-screen-gesture-handlers.js.map +1 -1
  30. package/lib/module/shared/hooks/navigation/use-screen-state.js +9 -37
  31. package/lib/module/shared/hooks/navigation/use-screen-state.js.map +1 -1
  32. package/lib/module/shared/utils/animation/animate-to-progress.js +5 -1
  33. package/lib/module/shared/utils/animation/animate-to-progress.js.map +1 -1
  34. package/lib/module/shared/utils/gesture/check-gesture-activation.js +90 -23
  35. package/lib/module/shared/utils/gesture/check-gesture-activation.js.map +1 -1
  36. package/lib/module/shared/utils/logger.js +17 -0
  37. package/lib/module/shared/utils/logger.js.map +1 -0
  38. package/lib/typescript/blank-stack/components/adjusted-screen.d.ts.map +1 -1
  39. package/lib/typescript/shared/constants.d.ts +9 -0
  40. package/lib/typescript/shared/constants.d.ts.map +1 -1
  41. package/lib/typescript/shared/hooks/animation/use-screen-animation.d.ts.map +1 -1
  42. package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts.map +1 -1
  43. package/lib/typescript/shared/hooks/gestures/use-screen-gesture-handlers.d.ts +1 -16
  44. package/lib/typescript/shared/hooks/gestures/use-screen-gesture-handlers.d.ts.map +1 -1
  45. package/lib/typescript/shared/hooks/navigation/use-screen-state.d.ts +0 -7
  46. package/lib/typescript/shared/hooks/navigation/use-screen-state.d.ts.map +1 -1
  47. package/lib/typescript/shared/types/animation.types.d.ts +9 -0
  48. package/lib/typescript/shared/types/animation.types.d.ts.map +1 -1
  49. package/lib/typescript/shared/utils/animation/animate-to-progress.d.ts.map +1 -1
  50. package/lib/typescript/shared/utils/gesture/check-gesture-activation.d.ts +4 -5
  51. package/lib/typescript/shared/utils/gesture/check-gesture-activation.d.ts.map +1 -1
  52. package/lib/typescript/shared/utils/logger.d.ts +6 -0
  53. package/lib/typescript/shared/utils/logger.d.ts.map +1 -0
  54. package/package.json +1 -1
  55. package/src/blank-stack/components/adjusted-screen.tsx +1 -1
  56. package/src/shared/__tests__/derivations.test.ts +1 -0
  57. package/src/shared/__tests__/gesture-activation.test.ts +29 -56
  58. package/src/shared/constants.ts +36 -9
  59. package/src/shared/hooks/animation/use-screen-animation.tsx +32 -21
  60. package/src/shared/hooks/gestures/use-build-gestures.tsx +2 -34
  61. package/src/shared/hooks/gestures/use-screen-gesture-handlers.ts +89 -91
  62. package/src/shared/hooks/navigation/use-screen-state.tsx +9 -56
  63. package/src/shared/types/animation.types.ts +10 -0
  64. package/src/shared/utils/animation/animate-to-progress.ts +4 -1
  65. package/src/shared/utils/gesture/check-gesture-activation.ts +74 -23
  66. package/src/shared/utils/logger.ts +15 -0
@@ -1,4 +1,5 @@
1
1
  import { useMemo } from "react";
2
+ import { useWindowDimensions } from "react-native";
2
3
  import type {
3
4
  GestureStateChangeEvent,
4
5
  GestureTouchEvent,
@@ -8,17 +9,20 @@ import type {
8
9
  import type { GestureStateManagerType } from "react-native-gesture-handler/lib/typescript/handlers/gestures/gestureStateManager";
9
10
  import { type SharedValue, useSharedValue } from "react-native-reanimated";
10
11
  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
12
  import {
17
- type GestureActivationArea,
18
- type GestureDirection,
19
- GestureOffsetState,
20
- } from "../../types/gesture.types";
21
- import type { Layout } from "../../types/screen.types";
13
+ DEFAULT_GESTURE_ACTIVATION_AREA,
14
+ DEFAULT_GESTURE_DIRECTION,
15
+ DEFAULT_GESTURE_DRIVES_PROGRESS,
16
+ EPSILON,
17
+ FALSE,
18
+ GESTURE_VELOCITY_IMPACT,
19
+ TRUE,
20
+ } from "../../constants";
21
+ import type { ScrollConfig } from "../../providers/gestures.provider";
22
+ import { useKeys } from "../../providers/screen/keys.provider";
23
+ import { AnimationStore } from "../../stores/animation.store";
24
+ import { GestureStore } from "../../stores/gesture.store";
25
+ import { GestureOffsetState } from "../../types/gesture.types";
22
26
  import { animateToProgress } from "../../utils/animation/animate-to-progress";
23
27
  import {
24
28
  applyOffsetRules,
@@ -30,50 +34,71 @@ import { mapGestureToProgress } from "../../utils/gesture/map-gesture-to-progres
30
34
  import { resetGestureValues } from "../../utils/gesture/reset-gesture-values";
31
35
  import { validateSnapPoints } from "../../utils/gesture/validate-snap-points";
32
36
  import { velocity } from "../../utils/gesture/velocity";
37
+ import { logger } from "../../utils/logger";
33
38
  import useStableCallbackValue from "../use-stable-callback-value";
34
39
 
35
40
  interface UseScreenGestureHandlersProps {
36
- dimensions: Layout;
37
- animations: AnimationStoreMap;
38
- gestureAnimationValues: GestureStoreMap;
39
- gestureDirection: GestureDirection | GestureDirection[];
40
- gestureDrivesProgress: boolean;
41
- gestureVelocityImpact: number;
42
41
  scrollConfig: SharedValue<ScrollConfig | null>;
43
- gestureActivationArea: GestureActivationArea;
44
- gestureResponseDistance?: number;
45
42
  ancestorIsDismissing?: SharedValue<number> | null;
46
- snapPoints?: number[];
47
43
  canDismiss: boolean;
48
- transitionSpec?: TransitionSpec;
49
44
  handleDismiss: () => void;
50
45
  }
51
46
 
52
47
  export const useScreenGestureHandlers = ({
53
- dimensions,
54
- animations,
55
- gestureAnimationValues,
56
- gestureDirection,
57
- gestureDrivesProgress,
58
- gestureVelocityImpact,
59
48
  scrollConfig,
60
- gestureActivationArea,
61
- gestureResponseDistance,
62
49
  ancestorIsDismissing,
63
- snapPoints: rawSnapPoints,
64
50
  canDismiss,
65
- transitionSpec,
66
51
  handleDismiss,
67
52
  }: UseScreenGestureHandlersProps) => {
53
+ const dimensions = useWindowDimensions();
54
+ const { current } = useKeys();
55
+
56
+ const animations = AnimationStore.getAll(current.route.key);
57
+ const gestureAnimationValues = GestureStore.getRouteGestures(
58
+ current.route.key,
59
+ );
60
+
61
+ const {
62
+ gestureDirection = DEFAULT_GESTURE_DIRECTION,
63
+ gestureDrivesProgress = DEFAULT_GESTURE_DRIVES_PROGRESS,
64
+ gestureVelocityImpact = GESTURE_VELOCITY_IMPACT,
65
+ gestureActivationArea = DEFAULT_GESTURE_ACTIVATION_AREA,
66
+ gestureResponseDistance,
67
+ transitionSpec,
68
+ snapPoints: rawSnapPoints,
69
+ } = current.options;
70
+
68
71
  const { hasSnapPoints, snapPoints, minSnapPoint, maxSnapPoint } = useMemo(
69
72
  () => validateSnapPoints({ snapPoints: rawSnapPoints, canDismiss }),
70
73
  [rawSnapPoints, canDismiss],
71
74
  );
72
75
 
73
76
  const directions = useMemo(() => {
74
- const directionsArray = Array.isArray(gestureDirection)
75
- ? gestureDirection
76
- : [gestureDirection];
77
+ if (hasSnapPoints && Array.isArray(gestureDirection)) {
78
+ /**
79
+ * Unsure if this behavior will change in the future, as I cannot find a use case as to why
80
+ * you would want multiple gesture dismisals for a sheet.
81
+ *
82
+ * e.g. When defining a snap point with a gesture of vertical ( default ), the system
83
+ * assumes that the inverse ( vertical-inverted ), will grow the sheet.
84
+ */
85
+ logger.warn(
86
+ `gestureDirection array is not supported with snapPoints. ` +
87
+ `Only the first direction "${gestureDirection[0]}" will be used. ` +
88
+ `Snap points define a single axis of movement, so only one gesture direction is needed.`,
89
+ );
90
+ }
91
+
92
+ // When snap points are defined, use only the first direction from the array
93
+ const effectiveDirection = hasSnapPoints
94
+ ? Array.isArray(gestureDirection)
95
+ ? gestureDirection[0]
96
+ : gestureDirection
97
+ : gestureDirection;
98
+
99
+ const directionsArray = Array.isArray(effectiveDirection)
100
+ ? effectiveDirection
101
+ : [effectiveDirection];
77
102
 
78
103
  const isBidirectional = directionsArray.includes("bidirectional");
79
104
 
@@ -128,7 +153,7 @@ export const useScreenGestureHandlers = ({
128
153
 
129
154
  // If an ancestor navigator is already dismissing via gesture, block new gestures here.
130
155
  if (ancestorIsDismissing?.value) {
131
- gestureOffsetState.set(GestureOffsetState.FAILED);
156
+ gestureOffsetState.value = GestureOffsetState.FAILED;
132
157
  manager.fail();
133
158
  return;
134
159
  }
@@ -176,13 +201,13 @@ export const useScreenGestureHandlers = ({
176
201
  }
177
202
 
178
203
  if (isSwipingDown) {
179
- gestureAnimationValues.direction.set("vertical");
204
+ gestureAnimationValues.direction.value = "vertical";
180
205
  } else if (isSwipingUp) {
181
- gestureAnimationValues.direction.set("vertical-inverted");
206
+ gestureAnimationValues.direction.value = "vertical-inverted";
182
207
  } else if (isSwipingRight) {
183
- gestureAnimationValues.direction.set("horizontal");
208
+ gestureAnimationValues.direction.value = "horizontal";
184
209
  } else if (isSwipingLeft) {
185
- gestureAnimationValues.direction.set("horizontal-inverted");
210
+ gestureAnimationValues.direction.value = "horizontal-inverted";
186
211
  }
187
212
 
188
213
  manager.activate();
@@ -190,18 +215,9 @@ export const useScreenGestureHandlers = ({
190
215
  }
191
216
 
192
217
  // 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
218
  // Snap mode: determine if sheet can still expand
203
219
  const canExpandMore =
204
- hasSnapPoints && animations.progress.value < maxSnapPoint - 0.01;
220
+ hasSnapPoints && animations.progress.value < maxSnapPoint - EPSILON;
205
221
 
206
222
  const { shouldActivate, direction: activatedDirection } =
207
223
  checkScrollAwareActivation({
@@ -212,10 +228,7 @@ export const useScreenGestureHandlers = ({
212
228
  isSwipingLeft,
213
229
  },
214
230
  directions,
215
- scrollX,
216
- scrollY,
217
- maxScrollX,
218
- maxScrollY,
231
+ scrollConfig: scrollCfg,
219
232
  hasSnapPoints,
220
233
  canExpandMore,
221
234
  });
@@ -242,6 +255,7 @@ export const useScreenGestureHandlers = ({
242
255
  gestureAnimationValues.isDragging.value = TRUE;
243
256
  gestureAnimationValues.isDismissing.value = FALSE;
244
257
  gestureStartProgress.value = animations.progress.value;
258
+ animations.animating.value = TRUE;
245
259
  });
246
260
 
247
261
  const onUpdate = useStableCallbackValue(
@@ -251,7 +265,6 @@ export const useScreenGestureHandlers = ({
251
265
  const { translationX, translationY } = event;
252
266
  const { width, height } = dimensions;
253
267
 
254
- // Update gesture values (shared across all modes)
255
268
  gestureAnimationValues.x.value = translationX;
256
269
  gestureAnimationValues.y.value = translationY;
257
270
  gestureAnimationValues.normalizedX.value = velocity.normalizeTranslation(
@@ -264,7 +277,6 @@ export const useScreenGestureHandlers = ({
264
277
  );
265
278
 
266
279
  if (hasSnapPoints && gestureDrivesProgress) {
267
- // Snap mode: bidirectional tracking on snap axis
268
280
  const isHorizontal = snapAxis === "horizontal";
269
281
  const translation = isHorizontal ? translationX : translationY;
270
282
  const dimension = isHorizontal ? width : height;
@@ -283,43 +295,30 @@ export const useScreenGestureHandlers = ({
283
295
  Math.min(maxSnapPoint, gestureStartProgress.value + progressDelta),
284
296
  );
285
297
  } 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
298
  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
- }
299
+
300
+ // Horizontal swipe right (positive X)
301
+ if (directions.horizontal && translationX > 0) {
302
+ const progress = mapGestureToProgress(translationX, width);
303
+ maxProgress = Math.max(maxProgress, progress);
304
+ }
305
+
306
+ // Horizontal inverted swipe left (negative X)
307
+ if (directions.horizontalInverted && translationX < 0) {
308
+ const progress = mapGestureToProgress(-translationX, width);
309
+ maxProgress = Math.max(maxProgress, progress);
310
+ }
311
+
312
+ // Vertical swipe down (positive Y)
313
+ if (directions.vertical && translationY > 0) {
314
+ const progress = mapGestureToProgress(translationY, height);
315
+ maxProgress = Math.max(maxProgress, progress);
316
+ }
317
+
318
+ // Vertical inverted swipe up (negative Y)
319
+ if (directions.verticalInverted && translationY < 0) {
320
+ const progress = mapGestureToProgress(-translationY, height);
321
+ maxProgress = Math.max(maxProgress, progress);
323
322
  }
324
323
 
325
324
  animations.progress.value = Math.max(
@@ -394,7 +393,6 @@ export const useScreenGestureHandlers = ({
394
393
  initialVelocity,
395
394
  });
396
395
  } else {
397
- // Standard mode: use determineDismissal
398
396
  const result = determineDismissal({
399
397
  event,
400
398
  directions,
@@ -1,10 +1,6 @@
1
1
  import type { Route } from "@react-navigation/native";
2
2
  import { useCallback, useMemo } from "react";
3
- import {
4
- runOnUI,
5
- type SharedValue,
6
- useDerivedValue,
7
- } from "react-native-reanimated";
3
+ import { runOnUI, useDerivedValue } from "react-native-reanimated";
8
4
  import { DefaultSnapSpec } from "../../configs/specs";
9
5
  import {
10
6
  type BaseDescriptor,
@@ -62,13 +58,6 @@ export interface ScreenState<
62
58
  * @param index - The index of the snap point to snap to (0-based)
63
59
  */
64
60
  snapTo: (index: number) => void;
65
-
66
- /**
67
- * Animated value representing the current snap point index.
68
- * Interpolates between indices during gestures (e.g., 0.5 means halfway between snap 0 and 1).
69
- * Returns -1 if no snap points are defined.
70
- */
71
- animatedSnapIndex: SharedValue<number>;
72
61
  }
73
62
 
74
63
  /**
@@ -100,63 +89,29 @@ export function useScreenState<
100
89
  }, [scenes, focusedIndex]);
101
90
 
102
91
  const currentOptions = current.options;
103
- const snapPoints = currentOptions?.snapPoints;
104
92
  const animations = useMemo(
105
93
  () => AnimationStore.getAll(current.route.key),
106
94
  [current.route.key],
107
95
  );
108
96
 
109
- // Pre-sort snap points for the derived value (avoids sorting in worklet)
110
- const sortedSnapPoints = useMemo(
111
- () => (snapPoints ? [...snapPoints].sort((a, b) => a - b) : []),
112
- [snapPoints],
113
- );
114
-
115
- const animatedSnapIndex = useDerivedValue(() => {
116
- if (sortedSnapPoints.length === 0) {
117
- return -1;
118
- }
119
-
120
- const progress = animations.progress.value;
121
-
122
- // Below first snap point
123
- if (progress <= sortedSnapPoints[0]) {
124
- return 0;
125
- }
126
-
127
- // Above last snap point
128
- if (progress >= sortedSnapPoints[sortedSnapPoints.length - 1]) {
129
- return sortedSnapPoints.length - 1;
130
- }
131
-
132
- // Find segment and interpolate
133
- for (let i = 0; i < sortedSnapPoints.length - 1; i++) {
134
- if (progress <= sortedSnapPoints[i + 1]) {
135
- const t =
136
- (progress - sortedSnapPoints[i]) /
137
- (sortedSnapPoints[i + 1] - sortedSnapPoints[i]);
138
- return i + t;
139
- }
140
- }
141
-
142
- return sortedSnapPoints.length - 1;
143
- });
144
-
145
97
  const snapTo = useCallback(
146
98
  (targetIndex: number) => {
147
- if (!sortedSnapPoints || sortedSnapPoints.length === 0) {
99
+ const points = currentOptions?.snapPoints;
100
+ if (!points || points.length === 0) {
148
101
  console.warn("snapTo called but no snapPoints defined");
149
102
  return;
150
103
  }
151
104
 
152
- if (targetIndex < 0 || targetIndex >= sortedSnapPoints.length) {
105
+ const sorted = [...points].sort((a, b) => a - b);
106
+
107
+ if (targetIndex < 0 || targetIndex >= sorted.length) {
153
108
  console.warn(
154
- `snapTo index ${targetIndex} out of bounds (0-${sortedSnapPoints.length - 1})`,
109
+ `snapTo index ${targetIndex} out of bounds (0-${sorted.length - 1})`,
155
110
  );
156
111
  return;
157
112
  }
158
113
 
159
- const targetProgress = sortedSnapPoints[targetIndex];
114
+ const targetProgress = sorted[targetIndex];
160
115
 
161
116
  runOnUI(() => {
162
117
  "worklet";
@@ -174,7 +129,7 @@ export function useScreenState<
174
129
  });
175
130
  })();
176
131
  },
177
- [sortedSnapPoints, animations, focusedScene],
132
+ [currentOptions?.snapPoints, animations, focusedScene],
178
133
  );
179
134
 
180
135
  return useMemo(
@@ -187,7 +142,6 @@ export function useScreenState<
187
142
  meta: focusedScene?.descriptor?.options?.meta,
188
143
  navigation: current.navigation as TNavigation,
189
144
  snapTo,
190
- animatedSnapIndex,
191
145
  }),
192
146
  [
193
147
  index,
@@ -197,7 +151,6 @@ export function useScreenState<
197
151
  current.navigation,
198
152
  current.route,
199
153
  snapTo,
200
- animatedSnapIndex,
201
154
  ],
202
155
  );
203
156
  }
@@ -142,6 +142,16 @@ export interface ScreenInterpolationProps {
142
142
  */
143
143
  stackProgress: number;
144
144
 
145
+ /**
146
+ * Animated index of the current snap point.
147
+ * Interpolates between indices during gestures/animations.
148
+ * - Returns -1 if no snap points are defined
149
+ * - Returns 0 when at or below first snap point
150
+ * - Returns fractional values between snap points (e.g., 1.5 = halfway between snap 1 and 2)
151
+ * - Returns length-1 when at or above last snap point
152
+ */
153
+ snapIndex: number;
154
+
145
155
  /**
146
156
  * Function that provides access to bounds builders for creating shared element transitions.
147
157
  */
@@ -73,7 +73,10 @@ export const animateToProgress = ({
73
73
  runOnJS(onAnimationFinish)(finished);
74
74
  }
75
75
 
76
- animating.set(FALSE);
76
+ // Delay setting animating=FALSE by one frame to ensure final frame is painted
77
+ requestAnimationFrame(() => {
78
+ animating.set(FALSE);
79
+ });
77
80
  }),
78
81
  );
79
82
  };
@@ -1,5 +1,6 @@
1
1
  import type { GestureStateManagerType } from "react-native-gesture-handler/lib/typescript/handlers/gestures/gestureStateManager";
2
2
  import type { SharedValue } from "react-native-reanimated";
3
+ import type { ScrollConfig } from "../../providers/gestures.provider";
3
4
  import {
4
5
  type ActivationArea,
5
6
  type GestureActivationArea,
@@ -13,6 +14,7 @@ type Directions = {
13
14
  verticalInverted: boolean;
14
15
  horizontal: boolean;
15
16
  horizontalInverted: boolean;
17
+ snapAxisInverted?: boolean;
16
18
  };
17
19
 
18
20
  interface CheckGestureActivationProps {
@@ -317,10 +319,7 @@ interface ScrollAwareActivationParams {
317
319
  isSwipingLeft: boolean;
318
320
  };
319
321
  directions: Directions;
320
- scrollX: number;
321
- scrollY: number;
322
- maxScrollX: number;
323
- maxScrollY: number;
322
+ scrollConfig: ScrollConfig | null;
324
323
  hasSnapPoints?: boolean;
325
324
  canExpandMore?: boolean;
326
325
  }
@@ -338,10 +337,7 @@ type GestureDirection =
338
337
  export function checkScrollAwareActivation({
339
338
  swipeInfo,
340
339
  directions,
341
- scrollX,
342
- scrollY,
343
- maxScrollX,
344
- maxScrollY,
340
+ scrollConfig,
345
341
  hasSnapPoints,
346
342
  canExpandMore,
347
343
  }: ScrollAwareActivationParams): {
@@ -353,6 +349,76 @@ export function checkScrollAwareActivation({
353
349
  const { isSwipingDown, isSwipingUp, isSwipingRight, isSwipingLeft } =
354
350
  swipeInfo;
355
351
 
352
+ // Extract scroll values from config
353
+ const scrollX = scrollConfig?.x ?? 0;
354
+ const scrollY = scrollConfig?.y ?? 0;
355
+ const maxScrollX = scrollConfig
356
+ ? scrollConfig.contentWidth - scrollConfig.layoutWidth
357
+ : 0;
358
+ const maxScrollY = scrollConfig
359
+ ? scrollConfig.contentHeight - scrollConfig.layoutHeight
360
+ : 0;
361
+ const snapAxisInverted = directions.snapAxisInverted;
362
+
363
+ // With snap points, gestures should only activate based on the PRIMARY scroll edge
364
+ // (the edge where the sheet originates from), not the opposite edge.
365
+ // This prevents the auto-enabled opposite direction from hijacking scrolls.
366
+ if (hasSnapPoints) {
367
+ const isVerticalAxis = directions.vertical || directions.verticalInverted;
368
+ const isHorizontalAxis =
369
+ directions.horizontal || directions.horizontalInverted;
370
+
371
+ if (isVerticalAxis) {
372
+ if (snapAxisInverted) {
373
+ // Sheet from TOP (vertical-inverted): only activate at scroll BOTTOM
374
+ if (scrollY >= maxScrollY) {
375
+ if (isSwipingUp) {
376
+ return { shouldActivate: true, direction: "vertical-inverted" };
377
+ }
378
+ if (isSwipingDown && canExpandMore) {
379
+ return { shouldActivate: true, direction: "vertical" };
380
+ }
381
+ }
382
+ } else {
383
+ // Sheet from BOTTOM (vertical): only activate at scroll TOP
384
+ if (scrollY <= 0) {
385
+ if (isSwipingDown) {
386
+ return { shouldActivate: true, direction: "vertical" };
387
+ }
388
+ if (isSwipingUp && canExpandMore) {
389
+ return { shouldActivate: true, direction: "vertical-inverted" };
390
+ }
391
+ }
392
+ }
393
+ }
394
+
395
+ if (isHorizontalAxis) {
396
+ if (snapAxisInverted) {
397
+ // Sheet from LEFT (horizontal-inverted): only activate at scroll RIGHT
398
+ if (scrollX >= maxScrollX) {
399
+ if (isSwipingLeft) {
400
+ return { shouldActivate: true, direction: "horizontal-inverted" };
401
+ }
402
+ if (isSwipingRight && canExpandMore) {
403
+ return { shouldActivate: true, direction: "horizontal" };
404
+ }
405
+ }
406
+ } else {
407
+ // Sheet from RIGHT (horizontal): only activate at scroll LEFT
408
+ if (scrollX <= 0) {
409
+ if (isSwipingRight) {
410
+ return { shouldActivate: true, direction: "horizontal" };
411
+ }
412
+ if (isSwipingLeft && canExpandMore) {
413
+ return { shouldActivate: true, direction: "horizontal-inverted" };
414
+ }
415
+ }
416
+ }
417
+ }
418
+
419
+ return { shouldActivate: false, direction: null };
420
+ }
421
+
356
422
  if (directions.vertical && isSwipingDown && scrollY <= 0) {
357
423
  return { shouldActivate: true, direction: "vertical" };
358
424
  }
@@ -369,20 +435,5 @@ export function checkScrollAwareActivation({
369
435
  return { shouldActivate: true, direction: "horizontal-inverted" };
370
436
  }
371
437
 
372
- if (hasSnapPoints && canExpandMore) {
373
- // Vertical sheet: swipe up at scroll top → expand
374
- const enabledYAxis = directions.vertical || directions.verticalInverted;
375
- const enabledXAxis = directions.horizontal || directions.horizontalInverted;
376
-
377
- if (enabledYAxis && isSwipingUp && scrollY <= 0) {
378
- return { shouldActivate: true, direction: "vertical-inverted" };
379
- }
380
-
381
- // Horizontal sheet: swipe left at scroll left → expand
382
- if (enabledXAxis && isSwipingLeft && scrollX <= 0) {
383
- return { shouldActivate: true, direction: "horizontal-inverted" };
384
- }
385
- }
386
-
387
438
  return { shouldActivate: false, direction: null };
388
439
  }
@@ -0,0 +1,15 @@
1
+ const LIBRARY_NAME = "react-native-screen-transitions";
2
+
3
+ export const logger = {
4
+ error(message: string) {
5
+ "worklet";
6
+ console.error(`[${LIBRARY_NAME}] ${message}`);
7
+ },
8
+ warn(message: string) {
9
+ "worklet";
10
+ console.warn(`[${LIBRARY_NAME}] ${message}`);
11
+ },
12
+ };
13
+
14
+ export const error = (message: string) =>
15
+ new Error(`[${LIBRARY_NAME}] ${message}`);