react-native-screen-transitions 2.3.2 → 2.3.3

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 (70) hide show
  1. package/README.md +50 -12
  2. package/lib/commonjs/__configs__/presets.js +7 -3
  3. package/lib/commonjs/__configs__/presets.js.map +1 -1
  4. package/lib/commonjs/__configs__/specs.js +1 -0
  5. package/lib/commonjs/__configs__/specs.js.map +1 -1
  6. package/lib/commonjs/hooks/bounds/use-bound-registry.js +0 -1
  7. package/lib/commonjs/hooks/bounds/use-bound-registry.js.map +1 -1
  8. package/lib/commonjs/hooks/gestures/use-build-gestures.js +18 -28
  9. package/lib/commonjs/hooks/gestures/use-build-gestures.js.map +1 -1
  10. package/lib/commonjs/integrations/native-stack/views/NativeStackView.native.js.map +1 -1
  11. package/lib/commonjs/utils/animation/start-screen-transition.js +8 -6
  12. package/lib/commonjs/utils/animation/start-screen-transition.js.map +1 -1
  13. package/lib/commonjs/utils/gesture/determine-dismissal.js +7 -55
  14. package/lib/commonjs/utils/gesture/determine-dismissal.js.map +1 -1
  15. package/lib/commonjs/utils/gesture/reset-gesture-values.js +46 -0
  16. package/lib/commonjs/utils/gesture/reset-gesture-values.js.map +1 -0
  17. package/lib/commonjs/utils/gesture/velocity.js +89 -0
  18. package/lib/commonjs/utils/gesture/velocity.js.map +1 -0
  19. package/lib/commonjs/utils/reanimated/version.js +12 -0
  20. package/lib/commonjs/utils/reanimated/version.js.map +1 -0
  21. package/lib/module/__configs__/presets.js +7 -3
  22. package/lib/module/__configs__/presets.js.map +1 -1
  23. package/lib/module/__configs__/specs.js +1 -0
  24. package/lib/module/__configs__/specs.js.map +1 -1
  25. package/lib/module/hooks/bounds/use-bound-registry.js +0 -1
  26. package/lib/module/hooks/bounds/use-bound-registry.js.map +1 -1
  27. package/lib/module/hooks/gestures/use-build-gestures.js +18 -28
  28. package/lib/module/hooks/gestures/use-build-gestures.js.map +1 -1
  29. package/lib/module/integrations/native-stack/views/NativeStackView.native.js.map +1 -1
  30. package/lib/module/utils/animation/start-screen-transition.js +8 -6
  31. package/lib/module/utils/animation/start-screen-transition.js.map +1 -1
  32. package/lib/module/utils/gesture/determine-dismissal.js +7 -55
  33. package/lib/module/utils/gesture/determine-dismissal.js.map +1 -1
  34. package/lib/module/utils/gesture/reset-gesture-values.js +41 -0
  35. package/lib/module/utils/gesture/reset-gesture-values.js.map +1 -0
  36. package/lib/module/utils/gesture/velocity.js +85 -0
  37. package/lib/module/utils/gesture/velocity.js.map +1 -0
  38. package/lib/module/utils/reanimated/version.js +7 -0
  39. package/lib/module/utils/reanimated/version.js.map +1 -0
  40. package/lib/typescript/__configs__/index.d.ts +1 -1
  41. package/lib/typescript/__configs__/presets.d.ts.map +1 -1
  42. package/lib/typescript/__configs__/specs.d.ts.map +1 -1
  43. package/lib/typescript/components/create-transition-aware-component.d.ts +6 -12
  44. package/lib/typescript/components/create-transition-aware-component.d.ts.map +1 -1
  45. package/lib/typescript/hooks/bounds/use-bound-registry.d.ts +0 -4
  46. package/lib/typescript/hooks/bounds/use-bound-registry.d.ts.map +1 -1
  47. package/lib/typescript/hooks/gestures/use-build-gestures.d.ts.map +1 -1
  48. package/lib/typescript/index.d.ts +98 -62
  49. package/lib/typescript/index.d.ts.map +1 -1
  50. package/lib/typescript/utils/animation/start-screen-transition.d.ts +3 -2
  51. package/lib/typescript/utils/animation/start-screen-transition.d.ts.map +1 -1
  52. package/lib/typescript/utils/gesture/determine-dismissal.d.ts +0 -1
  53. package/lib/typescript/utils/gesture/determine-dismissal.d.ts.map +1 -1
  54. package/lib/typescript/utils/gesture/reset-gesture-values.d.ts +16 -0
  55. package/lib/typescript/utils/gesture/reset-gesture-values.d.ts.map +1 -0
  56. package/lib/typescript/utils/gesture/velocity.d.ts +25 -0
  57. package/lib/typescript/utils/gesture/velocity.d.ts.map +1 -0
  58. package/lib/typescript/utils/reanimated/version.d.ts +2 -0
  59. package/lib/typescript/utils/reanimated/version.d.ts.map +1 -0
  60. package/package.json +3 -3
  61. package/src/__configs__/presets.ts +7 -1
  62. package/src/__configs__/specs.ts +1 -0
  63. package/src/hooks/bounds/use-bound-registry.tsx +0 -1
  64. package/src/hooks/gestures/use-build-gestures.tsx +20 -15
  65. package/src/integrations/native-stack/views/NativeStackView.native.tsx +1 -1
  66. package/src/utils/animation/start-screen-transition.ts +13 -4
  67. package/src/utils/gesture/determine-dismissal.ts +18 -69
  68. package/src/utils/gesture/reset-gesture-values.ts +48 -0
  69. package/src/utils/gesture/velocity.ts +144 -0
  70. package/src/utils/reanimated/version.ts +5 -0
@@ -246,11 +246,13 @@ export const SharedIGImage = (
246
246
  normX,
247
247
  [-1, 0, 1],
248
248
  [-width * 0.7, 0, width * 0.7],
249
+ "clamp",
249
250
  );
250
251
  const dragY = interpolate(
251
252
  normY,
252
253
  [-1, 0, 1],
253
254
  [-height * 0.4, 0, height * 0.4],
255
+ "clamp",
254
256
  );
255
257
  const dragXScale = interpolate(normX, [0, 1], [1, 0.8]);
256
258
  const dragYScale = interpolate(normY, [0, 1], [1, 0.8]);
@@ -328,6 +330,7 @@ export const SharedIGImage = (
328
330
  damping: 1000,
329
331
  mass: 3,
330
332
  overshootClamping: true,
333
+ //@ts-expect-error
331
334
  restSpeedThreshold: 0.02,
332
335
  },
333
336
  close: {
@@ -335,6 +338,7 @@ export const SharedIGImage = (
335
338
  damping: 1000,
336
339
  mass: 3,
337
340
  overshootClamping: true,
341
+ //@ts-expect-error
338
342
  restSpeedThreshold: 0.02,
339
343
  },
340
344
  },
@@ -507,13 +511,15 @@ export const SharedAppleMusic = (
507
511
  damping: 500,
508
512
  mass: 3,
509
513
  overshootClamping: true,
514
+ //@ts-expect-error
510
515
  restSpeedThreshold: 0.02,
511
516
  },
512
517
  close: {
513
518
  stiffness: 600,
514
519
  damping: 60,
515
- mass: 3,
520
+ mass: 4,
516
521
  overshootClamping: false,
522
+ //@ts-expect-error
517
523
  restSpeedThreshold: 0.02,
518
524
  restDisplacementThreshold: 0.002,
519
525
  },
@@ -5,5 +5,6 @@ export const DefaultSpec: WithSpringConfig = {
5
5
  damping: 500,
6
6
  mass: 3,
7
7
  overshootClamping: true,
8
+ // @ts-expect-error
8
9
  restSpeedThreshold: 0.02,
9
10
  };
@@ -152,7 +152,6 @@ export const useBoundsRegistry = ({
152
152
  );
153
153
 
154
154
  return {
155
- maybeMeasureAndStore,
156
155
  handleTransitionLayout,
157
156
  captureActiveOnPress,
158
157
  MeasurementSyncProvider,
@@ -27,11 +27,12 @@ import { Animations } from "../../stores/animations";
27
27
  import { Gestures } from "../../stores/gestures";
28
28
  import { NavigatorDismissState } from "../../stores/navigator-dismiss-state";
29
29
  import { GestureOffsetState } from "../../types/gesture";
30
- import { animate } from "../../utils/animation/animate";
31
30
  import { startScreenTransition } from "../../utils/animation/start-screen-transition";
32
31
  import { applyOffsetRules } from "../../utils/gesture/check-gesture-activation";
33
32
  import { determineDismissal } from "../../utils/gesture/determine-dismissal";
34
33
  import { mapGestureToProgress } from "../../utils/gesture/map-gesture-to-progress";
34
+ import { resetGestureValues } from "../../utils/gesture/reset-gesture-values";
35
+ import { velocity } from "../../utils/gesture/velocity";
35
36
  import useStableCallback from "../use-stable-callback";
36
37
 
37
38
  interface BuildGesturesHookProps {
@@ -51,6 +52,7 @@ export const useBuildGestures = ({
51
52
  x: 0,
52
53
  y: 0,
53
54
  });
55
+
54
56
  const gestureOffsetState = useSharedValue<GestureOffsetState>(
55
57
  GestureOffsetState.PENDING,
56
58
  );
@@ -268,7 +270,7 @@ export const useBuildGestures = ({
268
270
  (event: GestureStateChangeEvent<PanGestureHandlerEventPayload>) => {
269
271
  "worklet";
270
272
 
271
- const { shouldDismiss, velocity } = determineDismissal({
273
+ const { shouldDismiss } = determineDismissal({
272
274
  event,
273
275
  directions,
274
276
  dimensions,
@@ -277,29 +279,32 @@ export const useBuildGestures = ({
277
279
 
278
280
  const spec = shouldDismiss ? transitionSpec?.close : transitionSpec?.open;
279
281
 
280
- gestures.isDismissing.value = Number(shouldDismiss);
281
-
282
- // Provide per-axis velocities so drag return can bounce naturally
283
- const vxPx = event.velocityX;
284
- const vyPx = event.velocityY;
285
- const vxNorm = vxPx / Math.max(1, dimensions.width);
286
- const vyNorm = vyPx / Math.max(1, dimensions.height);
287
- gestures.x.value = animate(0, { ...spec, velocity: vxPx });
288
- gestures.y.value = animate(0, { ...spec, velocity: vyPx });
289
- gestures.normalizedX.value = animate(0, { ...spec, velocity: vxNorm });
290
- gestures.normalizedY.value = animate(0, { ...spec, velocity: vyNorm });
291
- gestures.isDragging.value = 0;
282
+ resetGestureValues({
283
+ spec,
284
+ gestures,
285
+ shouldDismiss,
286
+ event,
287
+ dimensions,
288
+ });
292
289
 
293
290
  if (shouldDismiss) {
294
291
  runOnJS(setNavigatorDismissal)();
295
292
  }
296
293
 
294
+ const initialVelocity = velocity.calculateProgressVelocity({
295
+ animations,
296
+ shouldDismiss,
297
+ event,
298
+ dimensions,
299
+ directions,
300
+ });
301
+
297
302
  startScreenTransition({
298
303
  target: shouldDismiss ? "close" : "open",
299
304
  onAnimationFinish: shouldDismiss ? handleDismiss : undefined,
300
305
  spec: transitionSpec,
301
- velocity,
302
306
  animations,
307
+ initialVelocity,
303
308
  });
304
309
  },
305
310
  [
@@ -267,12 +267,12 @@ const SceneView = ({
267
267
  }, [canGoBack, backTitle]);
268
268
 
269
269
  const isRemovePrevented = preventedRoutes[route.key]?.preventRemove;
270
+
270
271
  const modifiedPresentation = enableTransitions
271
272
  ? "containedTransparentModal"
272
273
  : presentation === "card"
273
274
  ? "push"
274
275
  : presentation;
275
-
276
276
  const modifiedAnimation = enableTransitions ? "none" : animation;
277
277
  const modifiedHeaderShown =
278
278
  enableTransitions || header !== undefined ? false : headerShown;
@@ -8,7 +8,8 @@ interface StartScreenTransitionProps {
8
8
  spec?: TransitionSpec;
9
9
  onAnimationFinish?: (finished: boolean) => void;
10
10
  animations: AnimationMap;
11
- velocity?: number;
11
+ /** Optional initial velocity for spring-based progress (units: progress/sec). */
12
+ initialVelocity?: number;
12
13
  }
13
14
 
14
15
  export const startScreenTransition = ({
@@ -16,12 +17,20 @@ export const startScreenTransition = ({
16
17
  spec,
17
18
  onAnimationFinish,
18
19
  animations,
19
- velocity,
20
+ initialVelocity,
20
21
  }: StartScreenTransitionProps) => {
21
22
  "worklet";
22
23
  const value = target === "open" ? 1 : 0;
23
24
  const config = target === "open" ? spec?.open : spec?.close;
24
25
 
26
+ const isSpringConfig =
27
+ !!config && !("duration" in config) && !("easing" in config);
28
+
29
+ const effectiveConfig =
30
+ isSpringConfig && typeof initialVelocity === "number"
31
+ ? { ...config, velocity: initialVelocity }
32
+ : config;
33
+
25
34
  const { progress, animating, closing } = animations;
26
35
 
27
36
  if (target === "close") {
@@ -40,13 +49,13 @@ export const startScreenTransition = ({
40
49
 
41
50
  animating.value = 1;
42
51
 
43
- progress.value = animate(value, { ...config, velocity }, (finished) => {
52
+ progress.value = animate(value, effectiveConfig, (finished) => {
44
53
  "worklet";
45
54
  if (finished) {
46
- animating.value = 0;
47
55
  if (onAnimationFinish) {
48
56
  runOnJS(onAnimationFinish)(finished);
49
57
  }
58
+ animating.value = 0;
50
59
  }
51
60
  });
52
61
  };
@@ -22,10 +22,7 @@ interface DetermineDismissalProps {
22
22
  gestureVelocityImpact: number;
23
23
  }
24
24
 
25
- // Note: we normalize velocity by the axis dimension to map px/s to progress/s.
26
- // This produces consistent behavior across devices and enables realistic bounce.
27
-
28
- const MAX_PROGRESS_VELOCITY = 3.5; // ~3 progress units/second
25
+ import { velocity as V } from "./velocity";
29
26
 
30
27
  const getAxisThreshold = ({
31
28
  translation,
@@ -34,31 +31,14 @@ const getAxisThreshold = ({
34
31
  gestureVelocityImpact,
35
32
  }: GetAxisThresholdProps) => {
36
33
  "worklet";
37
- const finalTranslation = translation + velocity * gestureVelocityImpact;
38
-
39
- return (
40
- Math.abs(finalTranslation) > screenSize / 2 &&
41
- (velocity !== 0 || translation !== 0)
34
+ return V.shouldPassDismissalThreshold(
35
+ translation,
36
+ velocity,
37
+ screenSize,
38
+ gestureVelocityImpact,
42
39
  );
43
40
  };
44
41
 
45
- const getVelocity = (
46
- dimensions: { width: number; height: number },
47
- velocityX: number,
48
- velocityY: number,
49
- dismissAxis: "x" | "y",
50
- ) => {
51
- "worklet";
52
- const axisSize = dismissAxis === "y" ? dimensions.height : dimensions.width;
53
- const axisVelocityPx = dismissAxis === "y" ? velocityY : velocityX;
54
- let velocity = axisVelocityPx / Math.max(1, axisSize);
55
-
56
- if (velocity > MAX_PROGRESS_VELOCITY) velocity = MAX_PROGRESS_VELOCITY;
57
- if (velocity < -MAX_PROGRESS_VELOCITY) velocity = -MAX_PROGRESS_VELOCITY;
58
-
59
- return velocity;
60
- };
61
-
62
42
  export const determineDismissal = ({
63
43
  event,
64
44
  directions,
@@ -68,45 +48,24 @@ export const determineDismissal = ({
68
48
  "worklet";
69
49
 
70
50
  let shouldDismiss: boolean = false;
71
- let dismissAxis: "x" | "y" = "x";
72
51
 
73
- if (directions.vertical && event.translationY > 0) {
74
- const dismiss = getAxisThreshold({
75
- translation: event.translationY,
76
- velocity: event.velocityY,
77
- screenSize: dimensions.height,
78
- gestureVelocityImpact,
79
- });
80
- if (dismiss) {
81
- dismissAxis = "y";
82
- shouldDismiss = true;
83
- }
84
- }
85
- if (directions.verticalInverted && event.translationY < 0) {
52
+ if (
53
+ (directions.vertical && event.translationY > 0) ||
54
+ (directions.verticalInverted && event.translationY < 0)
55
+ ) {
86
56
  const dismiss = getAxisThreshold({
87
57
  translation: event.translationY,
88
58
  velocity: event.velocityY,
89
59
  screenSize: dimensions.height,
90
60
  gestureVelocityImpact,
91
61
  });
92
- if (dismiss) {
93
- dismissAxis = "y";
94
- shouldDismiss = true;
95
- }
96
- }
97
- if (directions.horizontal && event.translationX > 0) {
98
- const dismiss = getAxisThreshold({
99
- translation: event.translationX,
100
- velocity: event.velocityX,
101
- screenSize: dimensions.width,
102
- gestureVelocityImpact,
103
- });
104
- if (dismiss) {
105
- dismissAxis = "x";
106
- shouldDismiss = true;
107
- }
62
+ if (dismiss) shouldDismiss = true;
108
63
  }
109
- if (directions.horizontalInverted && event.translationX < 0) {
64
+
65
+ if (
66
+ (directions.horizontal && event.translationX > 0) ||
67
+ (directions.horizontalInverted && event.translationX < 0)
68
+ ) {
110
69
  const dismiss = getAxisThreshold({
111
70
  translation: event.translationX,
112
71
  velocity: event.velocityX,
@@ -114,18 +73,8 @@ export const determineDismissal = ({
114
73
  gestureVelocityImpact,
115
74
  });
116
75
 
117
- if (dismiss) {
118
- dismissAxis = "x";
119
- shouldDismiss = true;
120
- }
76
+ if (dismiss) shouldDismiss = true;
121
77
  }
122
78
 
123
- const velocity = getVelocity(
124
- dimensions,
125
- event.velocityX,
126
- event.velocityY,
127
- dismissAxis,
128
- );
129
-
130
- return { shouldDismiss, velocity };
79
+ return { shouldDismiss };
131
80
  };
@@ -0,0 +1,48 @@
1
+ import type {
2
+ GestureStateChangeEvent,
3
+ PanGestureHandlerEventPayload,
4
+ } from "react-native-gesture-handler";
5
+ import type { GestureMap } from "../../stores/gestures";
6
+ import type { AnimationConfig } from "../../types/animation";
7
+ import { animate } from "../animation/animate";
8
+ import { velocity } from "./velocity";
9
+
10
+ interface ResetGestureValuesProps {
11
+ spec?: AnimationConfig;
12
+ gestures: GestureMap;
13
+ shouldDismiss: boolean;
14
+ event: GestureStateChangeEvent<PanGestureHandlerEventPayload>;
15
+ dimensions: { width: number; height: number };
16
+ }
17
+
18
+ export const resetGestureValues = ({
19
+ spec,
20
+ gestures,
21
+ shouldDismiss,
22
+ event,
23
+ dimensions,
24
+ }: ResetGestureValuesProps) => {
25
+ "worklet";
26
+
27
+ const vxNorm = velocity.normalize(event.velocityX, dimensions.width);
28
+ const vyNorm = velocity.normalize(event.velocityY, dimensions.height);
29
+
30
+ // Ensure spring starts moving toward zero using normalized gesture values for direction.
31
+ const nx =
32
+ gestures.normalizedX.value ||
33
+ event.translationX / Math.max(1, dimensions.width);
34
+ const ny =
35
+ gestures.normalizedY.value ||
36
+ event.translationY / Math.max(1, dimensions.height);
37
+
38
+ const vxTowardZero = velocity.calculateRestoreVelocity(nx, vxNorm);
39
+ const vyTowardZero = velocity.calculateRestoreVelocity(ny, vyNorm);
40
+
41
+ gestures.x.value = animate(0, { ...spec, velocity: vxTowardZero });
42
+ gestures.y.value = animate(0, { ...spec, velocity: vyTowardZero });
43
+
44
+ gestures.normalizedX.value = animate(0, { ...spec, velocity: vxTowardZero });
45
+ gestures.normalizedY.value = animate(0, { ...spec, velocity: vyTowardZero });
46
+ gestures.isDragging.value = 0;
47
+ gestures.isDismissing.value = Number(shouldDismiss);
48
+ };
@@ -0,0 +1,144 @@
1
+ import type {
2
+ GestureStateChangeEvent,
3
+ PanGestureHandlerEventPayload,
4
+ } from "react-native-gesture-handler";
5
+ import { clamp } from "react-native-reanimated";
6
+ import type { AnimationMap } from "../../stores/animations";
7
+
8
+ interface CalculateProgressProps {
9
+ animations: AnimationMap;
10
+ shouldDismiss: boolean;
11
+ event: GestureStateChangeEvent<PanGestureHandlerEventPayload>;
12
+ dimensions: { width: number; height: number };
13
+ directions: {
14
+ horizontal: boolean;
15
+ horizontalInverted: boolean;
16
+ vertical: boolean;
17
+ verticalInverted: boolean;
18
+ };
19
+ }
20
+
21
+ const MAX_VELOCITY_MAGNITUDE = 3.2;
22
+ const NEAR_ZERO_THRESHOLD = 0.01;
23
+
24
+ /**
25
+ * Converts velocity from pixels/second to normalized units/second (0-1 range)
26
+ * and caps the result for stability
27
+ */
28
+ const normalize = (velocityPixelsPerSecond: number, screenSize: number) => {
29
+ "worklet";
30
+ return clamp(
31
+ velocityPixelsPerSecond / Math.max(1, screenSize),
32
+ -MAX_VELOCITY_MAGNITUDE,
33
+ MAX_VELOCITY_MAGNITUDE,
34
+ );
35
+ };
36
+
37
+ /**
38
+ * Calculates a normalized velocity that moves the current value toward zero.
39
+ * Used for spring-back animations when dismissing gestures.
40
+ */
41
+ const calculateRestoreVelocity = (
42
+ currentValueNormalized: number,
43
+ baseVelocityNormalized: number,
44
+ threshold: number = NEAR_ZERO_THRESHOLD,
45
+ ) => {
46
+ "worklet";
47
+
48
+ if (Math.abs(currentValueNormalized) < threshold) return 0;
49
+
50
+ const directionTowardZero = Math.sign(currentValueNormalized) || 1;
51
+ const clampedVelocity = Math.min(Math.abs(baseVelocityNormalized), 1);
52
+
53
+ return -directionTowardZero * clampedVelocity;
54
+ };
55
+
56
+ const calculateProgressVelocity = ({
57
+ animations,
58
+ shouldDismiss,
59
+ event,
60
+ dimensions,
61
+ directions,
62
+ }: CalculateProgressProps) => {
63
+ "worklet";
64
+
65
+ const currentProgress = animations.progress.value;
66
+ const targetProgress = shouldDismiss ? 0 : 1;
67
+ const progressDelta = targetProgress - currentProgress;
68
+
69
+ const progressDirection = progressDelta === 0 ? 0 : Math.sign(progressDelta);
70
+
71
+ const normalizedVelocityX = normalize(event.velocityX, dimensions.width);
72
+ const normalizedVelocityY = normalize(event.velocityY, dimensions.height);
73
+
74
+ const normalizedTranslationX = Math.abs(
75
+ event.translationX / dimensions.width,
76
+ );
77
+ const normalizedTranslationY = Math.abs(
78
+ event.translationY / dimensions.height,
79
+ );
80
+
81
+ const supportsHorizontalGestures =
82
+ directions.horizontal || directions.horizontalInverted;
83
+
84
+ const supportsVerticalGestures =
85
+ directions.vertical || directions.verticalInverted;
86
+
87
+ let progressVelocityMagnitude = 0;
88
+
89
+ // Determine which axis should drive the progress velocity
90
+ if (
91
+ supportsHorizontalGestures &&
92
+ (!supportsVerticalGestures ||
93
+ normalizedTranslationX >= normalizedTranslationY)
94
+ ) {
95
+ progressVelocityMagnitude = Math.abs(normalizedVelocityX);
96
+ } else if (supportsVerticalGestures) {
97
+ progressVelocityMagnitude = Math.abs(normalizedVelocityY);
98
+ } else {
99
+ progressVelocityMagnitude = Math.max(
100
+ Math.abs(normalizedVelocityX),
101
+ Math.abs(normalizedVelocityY),
102
+ );
103
+ }
104
+
105
+ // Apply direction and clamp to prevent overly energetic springs
106
+ return (
107
+ progressDirection *
108
+ clamp(progressVelocityMagnitude, 0, MAX_VELOCITY_MAGNITUDE)
109
+ );
110
+ };
111
+
112
+ /**
113
+ * Determines if a gesture should trigger dismissal based on combined
114
+ * translation and velocity in normalized screen units (0-1 range).
115
+ *
116
+ * Formula: |translation/screen + clamp(velocity/screen, ±1) * velocityWeight| > 0.5
117
+ */
118
+ const shouldPassDismissalThreshold = (
119
+ translationPixels: number,
120
+ velocityPixelsPerSecond: number,
121
+ screenSize: number,
122
+ velocityWeight: number,
123
+ ) => {
124
+ "worklet";
125
+
126
+ const normalizedTranslation = translationPixels / Math.max(1, screenSize);
127
+ const normalizedVelocity = normalize(velocityPixelsPerSecond, screenSize);
128
+
129
+ const projectedNormalizedPosition =
130
+ normalizedTranslation + normalizedVelocity * velocityWeight;
131
+
132
+ const exceedsThreshold = Math.abs(projectedNormalizedPosition) > 0.5;
133
+
134
+ const hasMovement = translationPixels !== 0 || velocityPixelsPerSecond !== 0;
135
+
136
+ return exceedsThreshold && hasMovement;
137
+ };
138
+
139
+ export const velocity = {
140
+ normalize,
141
+ calculateRestoreVelocity,
142
+ calculateProgressVelocity,
143
+ shouldPassDismissalThreshold,
144
+ };
@@ -0,0 +1,5 @@
1
+ import { reanimatedVersion } from "react-native-reanimated";
2
+
3
+ export const isReanimated4 = () => {
4
+ return reanimatedVersion.split(".")[0] === "4";
5
+ };