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

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 (87) 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/animation/snap-to.js +62 -0
  5. package/lib/commonjs/shared/animation/snap-to.js.map +1 -0
  6. package/lib/commonjs/shared/constants.js +36 -10
  7. package/lib/commonjs/shared/constants.js.map +1 -1
  8. package/lib/commonjs/shared/hooks/animation/use-screen-animation.js +25 -18
  9. package/lib/commonjs/shared/hooks/animation/use-screen-animation.js.map +1 -1
  10. package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js +0 -25
  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 +74 -64
  13. package/lib/commonjs/shared/hooks/gestures/use-screen-gesture-handlers.js.map +1 -1
  14. package/lib/commonjs/shared/hooks/navigation/use-screen-state.js +2 -61
  15. package/lib/commonjs/shared/hooks/navigation/use-screen-state.js.map +1 -1
  16. package/lib/commonjs/shared/index.js +7 -0
  17. package/lib/commonjs/shared/index.js.map +1 -1
  18. package/lib/commonjs/shared/stores/animation.store.js +2 -1
  19. package/lib/commonjs/shared/stores/animation.store.js.map +1 -1
  20. package/lib/commonjs/shared/utils/animation/animate-to-progress.js +8 -2
  21. package/lib/commonjs/shared/utils/animation/animate-to-progress.js.map +1 -1
  22. package/lib/commonjs/shared/utils/gesture/check-gesture-activation.js +90 -23
  23. package/lib/commonjs/shared/utils/gesture/check-gesture-activation.js.map +1 -1
  24. package/lib/commonjs/shared/utils/logger.js +22 -0
  25. package/lib/commonjs/shared/utils/logger.js.map +1 -0
  26. package/lib/module/blank-stack/components/adjusted-screen.js +1 -1
  27. package/lib/module/blank-stack/components/adjusted-screen.js.map +1 -1
  28. package/lib/module/shared/animation/snap-to.js +59 -0
  29. package/lib/module/shared/animation/snap-to.js.map +1 -0
  30. package/lib/module/shared/constants.js +34 -9
  31. package/lib/module/shared/constants.js.map +1 -1
  32. package/lib/module/shared/hooks/animation/use-screen-animation.js +25 -18
  33. package/lib/module/shared/hooks/animation/use-screen-animation.js.map +1 -1
  34. package/lib/module/shared/hooks/gestures/use-build-gestures.js +0 -25
  35. package/lib/module/shared/hooks/gestures/use-build-gestures.js.map +1 -1
  36. package/lib/module/shared/hooks/gestures/use-screen-gesture-handlers.js +69 -59
  37. package/lib/module/shared/hooks/gestures/use-screen-gesture-handlers.js.map +1 -1
  38. package/lib/module/shared/hooks/navigation/use-screen-state.js +4 -63
  39. package/lib/module/shared/hooks/navigation/use-screen-state.js.map +1 -1
  40. package/lib/module/shared/index.js +1 -0
  41. package/lib/module/shared/index.js.map +1 -1
  42. package/lib/module/shared/stores/animation.store.js +2 -1
  43. package/lib/module/shared/stores/animation.store.js.map +1 -1
  44. package/lib/module/shared/utils/animation/animate-to-progress.js +8 -2
  45. package/lib/module/shared/utils/animation/animate-to-progress.js.map +1 -1
  46. package/lib/module/shared/utils/gesture/check-gesture-activation.js +90 -23
  47. package/lib/module/shared/utils/gesture/check-gesture-activation.js.map +1 -1
  48. package/lib/module/shared/utils/logger.js +17 -0
  49. package/lib/module/shared/utils/logger.js.map +1 -0
  50. package/lib/typescript/blank-stack/components/adjusted-screen.d.ts.map +1 -1
  51. package/lib/typescript/shared/animation/snap-to.d.ts +18 -0
  52. package/lib/typescript/shared/animation/snap-to.d.ts.map +1 -0
  53. package/lib/typescript/shared/constants.d.ts +9 -0
  54. package/lib/typescript/shared/constants.d.ts.map +1 -1
  55. package/lib/typescript/shared/hooks/animation/use-screen-animation.d.ts.map +1 -1
  56. package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts.map +1 -1
  57. package/lib/typescript/shared/hooks/gestures/use-screen-gesture-handlers.d.ts +1 -16
  58. package/lib/typescript/shared/hooks/gestures/use-screen-gesture-handlers.d.ts.map +1 -1
  59. package/lib/typescript/shared/hooks/navigation/use-screen-state.d.ts +0 -14
  60. package/lib/typescript/shared/hooks/navigation/use-screen-state.d.ts.map +1 -1
  61. package/lib/typescript/shared/index.d.ts +1 -0
  62. package/lib/typescript/shared/index.d.ts.map +1 -1
  63. package/lib/typescript/shared/stores/animation.store.d.ts +1 -0
  64. package/lib/typescript/shared/stores/animation.store.d.ts.map +1 -1
  65. package/lib/typescript/shared/types/animation.types.d.ts +9 -0
  66. package/lib/typescript/shared/types/animation.types.d.ts.map +1 -1
  67. package/lib/typescript/shared/utils/animation/animate-to-progress.d.ts.map +1 -1
  68. package/lib/typescript/shared/utils/gesture/check-gesture-activation.d.ts +4 -5
  69. package/lib/typescript/shared/utils/gesture/check-gesture-activation.d.ts.map +1 -1
  70. package/lib/typescript/shared/utils/logger.d.ts +6 -0
  71. package/lib/typescript/shared/utils/logger.d.ts.map +1 -0
  72. package/package.json +1 -1
  73. package/src/blank-stack/components/adjusted-screen.tsx +1 -1
  74. package/src/shared/__tests__/derivations.test.ts +1 -0
  75. package/src/shared/__tests__/gesture-activation.test.ts +29 -56
  76. package/src/shared/animation/snap-to.ts +62 -0
  77. package/src/shared/constants.ts +36 -9
  78. package/src/shared/hooks/animation/use-screen-animation.tsx +32 -21
  79. package/src/shared/hooks/gestures/use-build-gestures.tsx +2 -34
  80. package/src/shared/hooks/gestures/use-screen-gesture-handlers.ts +94 -92
  81. package/src/shared/hooks/navigation/use-screen-state.tsx +2 -106
  82. package/src/shared/index.ts +1 -0
  83. package/src/shared/stores/animation.store.ts +2 -0
  84. package/src/shared/types/animation.types.ts +10 -0
  85. package/src/shared/utils/animation/animate-to-progress.ts +7 -2
  86. package/src/shared/utils/gesture/check-gesture-activation.ts +74 -23
  87. package/src/shared/utils/logger.ts +15 -0
@@ -1,19 +1,12 @@
1
1
  import type { Route } from "@react-navigation/native";
2
- import { useCallback, useMemo } from "react";
3
- import {
4
- runOnUI,
5
- type SharedValue,
6
- useDerivedValue,
7
- } from "react-native-reanimated";
8
- import { DefaultSnapSpec } from "../../configs/specs";
2
+ import { useMemo } from "react";
3
+ import { useDerivedValue } from "react-native-reanimated";
9
4
  import {
10
5
  type BaseDescriptor,
11
6
  useKeys,
12
7
  } from "../../providers/screen/keys.provider";
13
- import { AnimationStore } from "../../stores/animation.store";
14
8
  import type { ScreenTransitionConfig } from "../../types/screen.types";
15
9
  import type { BaseStackNavigation } from "../../types/stack.types";
16
- import { animateToProgress } from "../../utils/animation/animate-to-progress";
17
10
  import { useSharedValueState } from "../reanimated/use-shared-value-state";
18
11
  import { type StackContextValue, useStack } from "./use-stack";
19
12
 
@@ -54,21 +47,6 @@ export interface ScreenState<
54
47
  * Navigation object for this screen.
55
48
  */
56
49
  navigation: TNavigation;
57
-
58
- /**
59
- * Programmatically snap to a specific snap point by index.
60
- * Only works if the screen has snapPoints defined.
61
- *
62
- * @param index - The index of the snap point to snap to (0-based)
63
- */
64
- 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
50
  }
73
51
 
74
52
  /**
@@ -99,84 +77,6 @@ export function useScreenState<
99
77
  return scenes[focusedIndex] ?? scenes[scenes.length - 1];
100
78
  }, [scenes, focusedIndex]);
101
79
 
102
- const currentOptions = current.options;
103
- const snapPoints = currentOptions?.snapPoints;
104
- const animations = useMemo(
105
- () => AnimationStore.getAll(current.route.key),
106
- [current.route.key],
107
- );
108
-
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
- const snapTo = useCallback(
146
- (targetIndex: number) => {
147
- if (!sortedSnapPoints || sortedSnapPoints.length === 0) {
148
- console.warn("snapTo called but no snapPoints defined");
149
- return;
150
- }
151
-
152
- if (targetIndex < 0 || targetIndex >= sortedSnapPoints.length) {
153
- console.warn(
154
- `snapTo index ${targetIndex} out of bounds (0-${sortedSnapPoints.length - 1})`,
155
- );
156
- return;
157
- }
158
-
159
- const targetProgress = sortedSnapPoints[targetIndex];
160
-
161
- runOnUI(() => {
162
- "worklet";
163
- animateToProgress({
164
- target: targetProgress,
165
- animations,
166
- spec: {
167
- open:
168
- focusedScene.descriptor.options.transitionSpec?.expand ??
169
- DefaultSnapSpec,
170
- close:
171
- focusedScene.descriptor.options.transitionSpec?.collapse ??
172
- DefaultSnapSpec,
173
- },
174
- });
175
- })();
176
- },
177
- [sortedSnapPoints, animations, focusedScene],
178
- );
179
-
180
80
  return useMemo(
181
81
  () => ({
182
82
  index,
@@ -186,8 +86,6 @@ export function useScreenState<
186
86
  focusedIndex,
187
87
  meta: focusedScene?.descriptor?.options?.meta,
188
88
  navigation: current.navigation as TNavigation,
189
- snapTo,
190
- animatedSnapIndex,
191
89
  }),
192
90
  [
193
91
  index,
@@ -196,8 +94,6 @@ export function useScreenState<
196
94
  focusedIndex,
197
95
  current.navigation,
198
96
  current.route,
199
- snapTo,
200
- animatedSnapIndex,
201
97
  ],
202
98
  );
203
99
  }
@@ -18,6 +18,7 @@ export default {
18
18
  Specs,
19
19
  };
20
20
 
21
+ export { snapTo } from "./animation/snap-to";
21
22
  export { useScreenAnimation } from "./hooks/animation/use-screen-animation";
22
23
  export { useScreenGesture } from "./hooks/gestures/use-screen-gesture";
23
24
  export { useHistory } from "./hooks/navigation/use-history";
@@ -6,6 +6,7 @@ export type AnimationStoreMap = {
6
6
  animating: SharedValue<number>;
7
7
  closing: SharedValue<number>;
8
8
  entering: SharedValue<number>;
9
+ targetProgress: SharedValue<number>;
9
10
  };
10
11
 
11
12
  const store: Record<ScreenKey, AnimationStoreMap> = {};
@@ -18,6 +19,7 @@ const ensure = (key: ScreenKey) => {
18
19
  closing: makeMutable(0),
19
20
  animating: makeMutable(0),
20
21
  entering: makeMutable(1),
22
+ targetProgress: makeMutable(1),
21
23
  } satisfies AnimationStoreMap;
22
24
  store[key] = bag;
23
25
  }
@@ -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
  */
@@ -44,7 +44,9 @@ export const animateToProgress = ({
44
44
  ? { ...config, velocity: initialVelocity }
45
45
  : config;
46
46
 
47
- const { progress, animating, closing, entering } = animations;
47
+ const { progress, animating, closing, entering, targetProgress } = animations;
48
+
49
+ targetProgress.set(value);
48
50
 
49
51
  if (isClosing) {
50
52
  closing.set(TRUE);
@@ -73,7 +75,10 @@ export const animateToProgress = ({
73
75
  runOnJS(onAnimationFinish)(finished);
74
76
  }
75
77
 
76
- animating.set(FALSE);
78
+ // Delay setting animating=FALSE by one frame to ensure final frame is painted
79
+ requestAnimationFrame(() => {
80
+ animating.set(FALSE);
81
+ });
77
82
  }),
78
83
  );
79
84
  };
@@ -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}`);