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 { describe, expect, it } from "bun:test";
2
+ import type { ScrollConfig } from "../providers/gestures.provider";
2
3
  import {
3
4
  normalizeSides,
4
5
  computeEdgeConstraints,
@@ -7,6 +8,20 @@ import {
7
8
  checkScrollAwareActivation,
8
9
  } from "../utils/gesture/check-gesture-activation";
9
10
 
11
+ /** Helper to create ScrollConfig with sensible defaults */
12
+ const createScrollConfig = (
13
+ overrides: Partial<ScrollConfig> = {},
14
+ ): ScrollConfig => ({
15
+ x: 0,
16
+ y: 0,
17
+ contentWidth: 0,
18
+ contentHeight: 0,
19
+ layoutWidth: 0,
20
+ layoutHeight: 0,
21
+ isTouched: true,
22
+ ...overrides,
23
+ });
24
+
10
25
  describe("normalizeSides", () => {
11
26
  it("returns all sides as 'screen' when no area provided", () => {
12
27
  const result = normalizeSides();
@@ -271,10 +286,7 @@ describe("checkScrollAwareActivation", () => {
271
286
  const result = checkScrollAwareActivation({
272
287
  swipeInfo: { ...noSwipe, isSwipingDown: true },
273
288
  directions: baseDirections,
274
- scrollX: 0,
275
- scrollY: 0,
276
- maxScrollX: 0,
277
- maxScrollY: 500,
289
+ scrollConfig: createScrollConfig({ contentHeight: 500, layoutHeight: 0 }),
278
290
  });
279
291
  expect(result.shouldActivate).toBe(true);
280
292
  expect(result.direction).toBe("vertical");
@@ -284,10 +296,7 @@ describe("checkScrollAwareActivation", () => {
284
296
  const result = checkScrollAwareActivation({
285
297
  swipeInfo: { ...noSwipe, isSwipingDown: true },
286
298
  directions: baseDirections,
287
- scrollX: 0,
288
- scrollY: 100,
289
- maxScrollX: 0,
290
- maxScrollY: 500,
299
+ scrollConfig: createScrollConfig({ y: 100, contentHeight: 500, layoutHeight: 0 }),
291
300
  });
292
301
  expect(result.shouldActivate).toBe(false);
293
302
  });
@@ -296,10 +305,7 @@ describe("checkScrollAwareActivation", () => {
296
305
  const result = checkScrollAwareActivation({
297
306
  swipeInfo: { ...noSwipe, isSwipingUp: true },
298
307
  directions: baseDirections,
299
- scrollX: 0,
300
- scrollY: 0,
301
- maxScrollX: 0,
302
- maxScrollY: 500,
308
+ scrollConfig: createScrollConfig({ contentHeight: 500, layoutHeight: 0 }),
303
309
  });
304
310
  expect(result.shouldActivate).toBe(false);
305
311
  });
@@ -317,10 +323,7 @@ describe("checkScrollAwareActivation", () => {
317
323
  const result = checkScrollAwareActivation({
318
324
  swipeInfo: { ...noSwipe, isSwipingUp: true },
319
325
  directions: invertedDirections,
320
- scrollX: 0,
321
- scrollY: 500,
322
- maxScrollX: 0,
323
- maxScrollY: 500,
326
+ scrollConfig: createScrollConfig({ y: 500, contentHeight: 500, layoutHeight: 0 }),
324
327
  });
325
328
  expect(result.shouldActivate).toBe(true);
326
329
  expect(result.direction).toBe("vertical-inverted");
@@ -330,10 +333,7 @@ describe("checkScrollAwareActivation", () => {
330
333
  const result = checkScrollAwareActivation({
331
334
  swipeInfo: { ...noSwipe, isSwipingUp: true },
332
335
  directions: invertedDirections,
333
- scrollX: 0,
334
- scrollY: 200,
335
- maxScrollX: 0,
336
- maxScrollY: 500,
336
+ scrollConfig: createScrollConfig({ y: 200, contentHeight: 500, layoutHeight: 0 }),
337
337
  });
338
338
  expect(result.shouldActivate).toBe(false);
339
339
  });
@@ -351,10 +351,7 @@ describe("checkScrollAwareActivation", () => {
351
351
  const result = checkScrollAwareActivation({
352
352
  swipeInfo: { ...noSwipe, isSwipingRight: true },
353
353
  directions: horizontalDirections,
354
- scrollX: 0,
355
- scrollY: 0,
356
- maxScrollX: 500,
357
- maxScrollY: 0,
354
+ scrollConfig: createScrollConfig({ contentWidth: 500, layoutWidth: 0 }),
358
355
  });
359
356
  expect(result.shouldActivate).toBe(true);
360
357
  expect(result.direction).toBe("horizontal");
@@ -364,10 +361,7 @@ describe("checkScrollAwareActivation", () => {
364
361
  const result = checkScrollAwareActivation({
365
362
  swipeInfo: { ...noSwipe, isSwipingRight: true },
366
363
  directions: horizontalDirections,
367
- scrollX: 100,
368
- scrollY: 0,
369
- maxScrollX: 500,
370
- maxScrollY: 0,
364
+ scrollConfig: createScrollConfig({ x: 100, contentWidth: 500, layoutWidth: 0 }),
371
365
  });
372
366
  expect(result.shouldActivate).toBe(false);
373
367
  });
@@ -385,10 +379,7 @@ describe("checkScrollAwareActivation", () => {
385
379
  const result = checkScrollAwareActivation({
386
380
  swipeInfo: { ...noSwipe, isSwipingLeft: true },
387
381
  directions: invertedHorizontalDirections,
388
- scrollX: 500,
389
- scrollY: 0,
390
- maxScrollX: 500,
391
- maxScrollY: 0,
382
+ scrollConfig: createScrollConfig({ x: 500, contentWidth: 500, layoutWidth: 0 }),
392
383
  });
393
384
  expect(result.shouldActivate).toBe(true);
394
385
  expect(result.direction).toBe("horizontal-inverted");
@@ -400,10 +391,7 @@ describe("checkScrollAwareActivation", () => {
400
391
  const result = checkScrollAwareActivation({
401
392
  swipeInfo: { ...noSwipe, isSwipingUp: true },
402
393
  directions: baseDirections,
403
- scrollX: 0,
404
- scrollY: 0,
405
- maxScrollX: 0,
406
- maxScrollY: 500,
394
+ scrollConfig: createScrollConfig({ contentHeight: 500, layoutHeight: 0 }),
407
395
  hasSnapPoints: true,
408
396
  canExpandMore: true,
409
397
  });
@@ -415,10 +403,7 @@ describe("checkScrollAwareActivation", () => {
415
403
  const result = checkScrollAwareActivation({
416
404
  swipeInfo: { ...noSwipe, isSwipingUp: true },
417
405
  directions: baseDirections,
418
- scrollX: 0,
419
- scrollY: 0,
420
- maxScrollX: 0,
421
- maxScrollY: 500,
406
+ scrollConfig: createScrollConfig({ contentHeight: 500, layoutHeight: 0 }),
422
407
  hasSnapPoints: true,
423
408
  canExpandMore: false,
424
409
  });
@@ -429,10 +414,7 @@ describe("checkScrollAwareActivation", () => {
429
414
  const result = checkScrollAwareActivation({
430
415
  swipeInfo: { ...noSwipe, isSwipingUp: true },
431
416
  directions: baseDirections,
432
- scrollX: 0,
433
- scrollY: 100,
434
- maxScrollX: 0,
435
- maxScrollY: 500,
417
+ scrollConfig: createScrollConfig({ y: 100, contentHeight: 500, layoutHeight: 0 }),
436
418
  hasSnapPoints: true,
437
419
  canExpandMore: true,
438
420
  });
@@ -450,10 +432,7 @@ describe("checkScrollAwareActivation", () => {
450
432
  const result = checkScrollAwareActivation({
451
433
  swipeInfo: { ...noSwipe, isSwipingLeft: true },
452
434
  directions: horizontalDirections,
453
- scrollX: 0,
454
- scrollY: 0,
455
- maxScrollX: 500,
456
- maxScrollY: 0,
435
+ scrollConfig: createScrollConfig({ contentWidth: 500, layoutWidth: 0 }),
457
436
  hasSnapPoints: true,
458
437
  canExpandMore: true,
459
438
  });
@@ -467,10 +446,7 @@ describe("checkScrollAwareActivation", () => {
467
446
  const result = checkScrollAwareActivation({
468
447
  swipeInfo: noSwipe,
469
448
  directions: baseDirections,
470
- scrollX: 0,
471
- scrollY: 0,
472
- maxScrollX: 0,
473
- maxScrollY: 500,
449
+ scrollConfig: createScrollConfig({ contentHeight: 500, layoutHeight: 0 }),
474
450
  });
475
451
  expect(result.shouldActivate).toBe(false);
476
452
  expect(result.direction).toBe(null);
@@ -487,10 +463,7 @@ describe("checkScrollAwareActivation", () => {
487
463
  const result = checkScrollAwareActivation({
488
464
  swipeInfo: { ...noSwipe, isSwipingDown: true },
489
465
  directions: disabledDirections,
490
- scrollX: 0,
491
- scrollY: 0,
492
- maxScrollX: 0,
493
- maxScrollY: 500,
466
+ scrollConfig: createScrollConfig({ contentHeight: 500, layoutHeight: 0 }),
494
467
  });
495
468
  expect(result.shouldActivate).toBe(false);
496
469
  });
@@ -4,6 +4,7 @@ import type { MeasuredDimensions } from "react-native-reanimated";
4
4
  import type { ScreenTransitionState } from "./types/animation.types";
5
5
  import type { ActivationArea } from "./types/gesture.types";
6
6
  import type { Layout } from "./types/screen.types";
7
+ import type { BaseStackRoute } from "./types/stack.types";
7
8
 
8
9
  /**
9
10
  * Masked view integration
@@ -16,6 +17,35 @@ export const CONTAINER_STYLE_ID = "_ROOT_CONTAINER";
16
17
  */
17
18
  export const NO_STYLES = Object.freeze({});
18
19
 
20
+ /**
21
+ * Default gesture values
22
+ */
23
+ const DEFAULT_GESTURE_VALUES = {
24
+ x: 0,
25
+ y: 0,
26
+ normalizedX: 0,
27
+ normalizedY: 0,
28
+ isDismissing: 0,
29
+ isDragging: 0,
30
+ direction: null,
31
+ } as const;
32
+
33
+ /**
34
+ * Creates a new screen transition state object
35
+ */
36
+ export const createScreenTransitionState = (
37
+ route: BaseStackRoute,
38
+ meta?: Record<string, unknown>,
39
+ ): ScreenTransitionState => ({
40
+ progress: 0,
41
+ closing: 0,
42
+ animating: 0,
43
+ entering: 1,
44
+ gesture: { ...DEFAULT_GESTURE_VALUES },
45
+ route,
46
+ meta,
47
+ });
48
+
19
49
  /**
20
50
  * Default screen transition state
21
51
  */
@@ -25,15 +55,7 @@ export const DEFAULT_SCREEN_TRANSITION_STATE: ScreenTransitionState =
25
55
  closing: 0,
26
56
  animating: 0,
27
57
  entering: 1,
28
- gesture: {
29
- x: 0,
30
- y: 0,
31
- normalizedX: 0,
32
- normalizedY: 0,
33
- isDismissing: 0,
34
- isDragging: 0,
35
- direction: null,
36
- },
58
+ gesture: DEFAULT_GESTURE_VALUES,
37
59
  route: {} as RouteProp<ParamListBase>,
38
60
  });
39
61
 
@@ -79,3 +101,8 @@ export const IS_WEB = Platform.OS === "web";
79
101
 
80
102
  export const TRUE = 1;
81
103
  export const FALSE = 0;
104
+
105
+ /**
106
+ * Small value for floating-point comparisons to handle animation/interpolation imprecision
107
+ */
108
+ export const EPSILON = 1e-5;
@@ -3,7 +3,10 @@ import { useWindowDimensions } from "react-native";
3
3
  import { type SharedValue, useDerivedValue } from "react-native-reanimated";
4
4
  import { useSafeAreaInsets } from "react-native-safe-area-context";
5
5
  import type { NativeStackScreenTransitionConfig } from "../../../native-stack/types";
6
- import { DEFAULT_SCREEN_TRANSITION_STATE } from "../../constants";
6
+ import {
7
+ createScreenTransitionState,
8
+ DEFAULT_SCREEN_TRANSITION_STATE,
9
+ } from "../../constants";
7
10
  import {
8
11
  type BaseDescriptor,
9
12
  useKeys,
@@ -31,26 +34,26 @@ type BuiltState = {
31
34
  unwrapped: ScreenTransitionState;
32
35
  };
33
36
 
34
- const createScreenTransitionState = (
35
- route: BaseStackRoute,
36
- meta?: Record<string, unknown>,
37
- ): ScreenTransitionState => ({
38
- progress: 0,
39
- closing: 0,
40
- animating: 0,
41
- entering: 1,
42
- gesture: {
43
- x: 0,
44
- y: 0,
45
- normalizedX: 0,
46
- normalizedY: 0,
47
- isDismissing: 0,
48
- isDragging: 0,
49
- direction: null,
50
- },
51
- route,
52
- meta,
53
- });
37
+ /**
38
+ * Computes the animated snap index based on progress and snap points.
39
+ * Returns -1 if no snap points, otherwise interpolates between indices.
40
+ */
41
+ const computeSnapIndex = (progress: number, snapPoints: number[]): number => {
42
+ "worklet";
43
+ if (snapPoints.length === 0) return -1;
44
+ if (progress <= snapPoints[0]) return 0;
45
+ if (progress >= snapPoints[snapPoints.length - 1])
46
+ return snapPoints.length - 1;
47
+
48
+ for (let i = 0; i < snapPoints.length - 1; i++) {
49
+ if (progress <= snapPoints[i + 1]) {
50
+ const t =
51
+ (progress - snapPoints[i]) / (snapPoints[i + 1] - snapPoints[i]);
52
+ return i + t;
53
+ }
54
+ }
55
+ return snapPoints.length - 1;
56
+ };
54
57
 
55
58
  const unwrapInto = (s: BuiltState): ScreenTransitionState => {
56
59
  "worklet";
@@ -123,6 +126,11 @@ export function _useScreenAnimation() {
123
126
  const currentRouteKey = currentDescriptor?.route?.key;
124
127
  const currentIndex = routeKeys.indexOf(currentRouteKey);
125
128
 
129
+ const sortedSnapPoints = useMemo(() => {
130
+ const points = currentDescriptor?.options?.snapPoints;
131
+ return points ? [...points].sort((a, b) => a - b) : [];
132
+ }, [currentDescriptor?.options?.snapPoints]);
133
+
126
134
  const screenInterpolatorProps = useDerivedValue<
127
135
  Omit<ScreenInterpolationProps, "bounds">
128
136
  >(() => {
@@ -152,6 +160,8 @@ export function _useScreenAnimation() {
152
160
  const stackProgress =
153
161
  currentIndex >= 0 ? rootStackProgress.value - currentIndex : progress;
154
162
 
163
+ const snapIndex = computeSnapIndex(current.progress, sortedSnapPoints);
164
+
155
165
  return {
156
166
  layouts: { screen: dimensions },
157
167
  insets,
@@ -160,6 +170,7 @@ export function _useScreenAnimation() {
160
170
  next,
161
171
  progress,
162
172
  stackProgress,
173
+ snapIndex,
163
174
  ...helpers,
164
175
  };
165
176
  });
@@ -1,20 +1,12 @@
1
1
  import { StackActions } from "@react-navigation/native";
2
2
  import { useCallback, useMemo, useRef } from "react";
3
- import { useWindowDimensions } from "react-native";
4
3
  import { Gesture, type GestureType } from "react-native-gesture-handler";
5
4
  import type { SharedValue } from "react-native-reanimated";
6
- import {
7
- DEFAULT_GESTURE_ACTIVATION_AREA,
8
- DEFAULT_GESTURE_DIRECTION,
9
- DEFAULT_GESTURE_DRIVES_PROGRESS,
10
- GESTURE_VELOCITY_IMPACT,
11
- } from "../../constants";
12
5
  import type {
13
6
  GestureContextType,
14
7
  ScrollConfig,
15
8
  } from "../../providers/gestures.provider";
16
9
  import { useKeys } from "../../providers/screen/keys.provider";
17
- import { AnimationStore } from "../../stores/animation.store";
18
10
  import { GestureStore, type GestureStoreMap } from "../../stores/gesture.store";
19
11
  import { useScreenGestureHandlers } from "./use-screen-gesture-handlers";
20
12
 
@@ -32,8 +24,6 @@ export const useBuildGestures = ({
32
24
  nativeGesture: GestureType;
33
25
  gestureAnimationValues: GestureStoreMap;
34
26
  } => {
35
- const dimensions = useWindowDimensions();
36
-
37
27
  const { current } = useKeys();
38
28
 
39
29
  const navState = current.navigation.getState();
@@ -48,17 +38,8 @@ export const useBuildGestures = ({
48
38
  const gestureAnimationValues = GestureStore.getRouteGestures(
49
39
  current.route.key,
50
40
  );
51
- const animations = AnimationStore.getAll(current.route.key);
52
-
53
- const {
54
- gestureDirection = DEFAULT_GESTURE_DIRECTION,
55
- gestureVelocityImpact = GESTURE_VELOCITY_IMPACT,
56
- gestureDrivesProgress = DEFAULT_GESTURE_DRIVES_PROGRESS,
57
- gestureActivationArea = DEFAULT_GESTURE_ACTIVATION_AREA,
58
- gestureResponseDistance,
59
- transitionSpec,
60
- snapPoints,
61
- } = current.options;
41
+
42
+ const { snapPoints } = current.options;
62
43
 
63
44
  // Dismiss gesture is controlled by gestureEnabled (disabled for first screen)
64
45
  const canDismiss = Boolean(
@@ -97,26 +78,13 @@ export const useBuildGestures = ({
97
78
 
98
79
  const { onTouchesDown, onTouchesMove, onStart, onUpdate, onEnd } =
99
80
  useScreenGestureHandlers({
100
- dimensions,
101
- animations,
102
- gestureAnimationValues,
103
- gestureDirection,
104
- gestureDrivesProgress,
105
- gestureVelocityImpact,
106
81
  scrollConfig,
107
- gestureActivationArea,
108
- gestureResponseDistance,
109
- snapPoints,
110
82
  canDismiss,
111
- transitionSpec,
112
83
  handleDismiss,
113
84
  ancestorIsDismissing:
114
85
  ancestorContext?.gestureAnimationValues.isDismissing,
115
86
  });
116
87
 
117
- // Memoize gestures to keep stable references - critical for RNGH
118
- // Child gestures reference ancestor's pan via requireExternalGestureToFail,
119
- // so the pan gesture MUST be stable or children will reference stale objects
120
88
  return useMemo(() => {
121
89
  const panGesture = Gesture.Pan()
122
90
  .withRef(panGestureRef)