react-native-screen-transitions 3.0.0-beta.0 → 3.0.0-beta.10

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 (132) hide show
  1. package/lib/commonjs/blank-stack/components/Overlay.js +33 -7
  2. package/lib/commonjs/blank-stack/components/Overlay.js.map +1 -1
  3. package/lib/commonjs/blank-stack/components/Screens.js +3 -3
  4. package/lib/commonjs/blank-stack/components/Screens.js.map +1 -1
  5. package/lib/commonjs/blank-stack/components/StackView.js +49 -40
  6. package/lib/commonjs/blank-stack/components/StackView.js.map +1 -1
  7. package/lib/commonjs/blank-stack/hooks/use-overlay-animation.js +3 -7
  8. package/lib/commonjs/blank-stack/hooks/use-overlay-animation.js.map +1 -1
  9. package/lib/commonjs/blank-stack/index.js +14 -0
  10. package/lib/commonjs/blank-stack/index.js.map +1 -1
  11. package/lib/commonjs/blank-stack/providers/blank-stack-state.js +65 -0
  12. package/lib/commonjs/blank-stack/providers/blank-stack-state.js.map +1 -0
  13. package/lib/commonjs/native-stack/views/NativeStackView.native.js +2 -1
  14. package/lib/commonjs/native-stack/views/NativeStackView.native.js.map +1 -1
  15. package/lib/commonjs/shared/components/controllers/screen-lifecycle.js +20 -6
  16. package/lib/commonjs/shared/components/controllers/screen-lifecycle.js.map +1 -1
  17. package/lib/commonjs/shared/hooks/animation/use-screen-animation.js.map +1 -1
  18. package/lib/commonjs/shared/hooks/bounds/use-bound-registry.js +21 -0
  19. package/lib/commonjs/shared/hooks/bounds/use-bound-registry.js.map +1 -1
  20. package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js +40 -28
  21. package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js.map +1 -1
  22. package/lib/commonjs/shared/hooks/use-derived-value-state.js +33 -0
  23. package/lib/commonjs/shared/hooks/use-derived-value-state.js.map +1 -0
  24. package/lib/commonjs/shared/hooks/use-stable-callback-value.js +7 -1
  25. package/lib/commonjs/shared/hooks/use-stable-callback-value.js.map +1 -1
  26. package/lib/commonjs/shared/providers/gestures.js +6 -3
  27. package/lib/commonjs/shared/providers/gestures.js.map +1 -1
  28. package/lib/commonjs/shared/providers/utils/create-provider.js +41 -29
  29. package/lib/commonjs/shared/providers/utils/create-provider.js.map +1 -1
  30. package/lib/commonjs/shared/stores/animation-store.js +12 -3
  31. package/lib/commonjs/shared/stores/animation-store.js.map +1 -1
  32. package/lib/commonjs/shared/stores/bound-store/index.js +12 -1
  33. package/lib/commonjs/shared/stores/bound-store/index.js.map +1 -1
  34. package/lib/commonjs/shared/stores/gesture-store.js +10 -0
  35. package/lib/commonjs/shared/stores/gesture-store.js.map +1 -1
  36. package/lib/module/blank-stack/components/Overlay.js +33 -7
  37. package/lib/module/blank-stack/components/Overlay.js.map +1 -1
  38. package/lib/module/blank-stack/components/Screens.js +3 -3
  39. package/lib/module/blank-stack/components/Screens.js.map +1 -1
  40. package/lib/module/blank-stack/components/StackView.js +48 -40
  41. package/lib/module/blank-stack/components/StackView.js.map +1 -1
  42. package/lib/module/blank-stack/hooks/use-overlay-animation.js +4 -8
  43. package/lib/module/blank-stack/hooks/use-overlay-animation.js.map +1 -1
  44. package/lib/module/blank-stack/index.js +2 -0
  45. package/lib/module/blank-stack/index.js.map +1 -1
  46. package/lib/module/blank-stack/providers/blank-stack-state.js +59 -0
  47. package/lib/module/blank-stack/providers/blank-stack-state.js.map +1 -0
  48. package/lib/module/native-stack/views/NativeStackView.native.js +2 -1
  49. package/lib/module/native-stack/views/NativeStackView.native.js.map +1 -1
  50. package/lib/module/shared/components/controllers/screen-lifecycle.js +20 -6
  51. package/lib/module/shared/components/controllers/screen-lifecycle.js.map +1 -1
  52. package/lib/module/shared/hooks/animation/use-screen-animation.js.map +1 -1
  53. package/lib/module/shared/hooks/bounds/use-bound-registry.js +22 -1
  54. package/lib/module/shared/hooks/bounds/use-bound-registry.js.map +1 -1
  55. package/lib/module/shared/hooks/gestures/use-build-gestures.js +42 -30
  56. package/lib/module/shared/hooks/gestures/use-build-gestures.js.map +1 -1
  57. package/lib/module/shared/hooks/use-derived-value-state.js +30 -0
  58. package/lib/module/shared/hooks/use-derived-value-state.js.map +1 -0
  59. package/lib/module/shared/hooks/use-stable-callback-value.js +8 -2
  60. package/lib/module/shared/hooks/use-stable-callback-value.js.map +1 -1
  61. package/lib/module/shared/providers/gestures.js +6 -3
  62. package/lib/module/shared/providers/gestures.js.map +1 -1
  63. package/lib/module/shared/providers/utils/create-provider.js +40 -27
  64. package/lib/module/shared/providers/utils/create-provider.js.map +1 -1
  65. package/lib/module/shared/stores/animation-store.js +15 -4
  66. package/lib/module/shared/stores/animation-store.js.map +1 -1
  67. package/lib/module/shared/stores/bound-store/index.js +12 -1
  68. package/lib/module/shared/stores/bound-store/index.js.map +1 -1
  69. package/lib/module/shared/stores/gesture-store.js +11 -1
  70. package/lib/module/shared/stores/gesture-store.js.map +1 -1
  71. package/lib/typescript/blank-stack/components/Overlay.d.ts.map +1 -1
  72. package/lib/typescript/blank-stack/components/Screens.d.ts +2 -3
  73. package/lib/typescript/blank-stack/components/Screens.d.ts.map +1 -1
  74. package/lib/typescript/blank-stack/components/StackView.d.ts.map +1 -1
  75. package/lib/typescript/blank-stack/hooks/use-overlay-animation.d.ts +1 -1
  76. package/lib/typescript/blank-stack/hooks/use-overlay-animation.d.ts.map +1 -1
  77. package/lib/typescript/blank-stack/index.d.ts +2 -0
  78. package/lib/typescript/blank-stack/index.d.ts.map +1 -1
  79. package/lib/typescript/blank-stack/providers/blank-stack-state.d.ts +13 -0
  80. package/lib/typescript/blank-stack/providers/blank-stack-state.d.ts.map +1 -0
  81. package/lib/typescript/blank-stack/types.d.ts +11 -3
  82. package/lib/typescript/blank-stack/types.d.ts.map +1 -1
  83. package/lib/typescript/native-stack/views/NativeStackView.native.d.ts.map +1 -1
  84. package/lib/typescript/shared/components/controllers/screen-lifecycle.d.ts.map +1 -1
  85. package/lib/typescript/shared/configs/index.d.ts +9 -9
  86. package/lib/typescript/shared/hooks/animation/use-screen-animation.d.ts.map +1 -1
  87. package/lib/typescript/shared/hooks/bounds/use-bound-registry.d.ts.map +1 -1
  88. package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts +5 -2
  89. package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts.map +1 -1
  90. package/lib/typescript/shared/hooks/use-derived-value-state.d.ts +9 -0
  91. package/lib/typescript/shared/hooks/use-derived-value-state.d.ts.map +1 -0
  92. package/lib/typescript/shared/hooks/use-stable-callback-value.d.ts.map +1 -1
  93. package/lib/typescript/shared/index.d.ts +10 -8
  94. package/lib/typescript/shared/index.d.ts.map +1 -1
  95. package/lib/typescript/shared/providers/gestures.d.ts +4 -2
  96. package/lib/typescript/shared/providers/gestures.d.ts.map +1 -1
  97. package/lib/typescript/shared/providers/utils/create-provider.d.ts +11 -29
  98. package/lib/typescript/shared/providers/utils/create-provider.d.ts.map +1 -1
  99. package/lib/typescript/shared/stores/animation-store.d.ts +4 -2
  100. package/lib/typescript/shared/stores/animation-store.d.ts.map +1 -1
  101. package/lib/typescript/shared/stores/bound-store/index.d.ts +2 -0
  102. package/lib/typescript/shared/stores/bound-store/index.d.ts.map +1 -1
  103. package/lib/typescript/shared/stores/gesture-store.d.ts.map +1 -1
  104. package/package.json +87 -99
  105. package/src/blank-stack/components/Overlay.tsx +40 -5
  106. package/src/blank-stack/components/Screens.tsx +5 -5
  107. package/src/blank-stack/components/StackView.tsx +55 -46
  108. package/src/blank-stack/hooks/use-overlay-animation.tsx +6 -21
  109. package/src/blank-stack/index.ts +2 -0
  110. package/src/blank-stack/providers/blank-stack-state.tsx +90 -0
  111. package/src/blank-stack/types.ts +11 -2
  112. package/src/native-stack/views/NativeStackView.native.tsx +3 -1
  113. package/src/shared/components/controllers/screen-lifecycle.tsx +20 -7
  114. package/src/shared/hooks/animation/use-screen-animation.tsx +1 -1
  115. package/src/shared/hooks/bounds/use-bound-registry.tsx +32 -1
  116. package/src/shared/hooks/gestures/use-build-gestures.tsx +63 -35
  117. package/src/shared/hooks/use-derived-value-state.ts +41 -0
  118. package/src/shared/hooks/use-stable-callback-value.tsx +10 -1
  119. package/src/shared/index.ts +8 -0
  120. package/src/shared/providers/gestures.tsx +17 -8
  121. package/src/shared/providers/utils/create-provider.tsx +77 -0
  122. package/src/shared/stores/animation-store.ts +19 -3
  123. package/src/shared/stores/bound-store/index.ts +23 -0
  124. package/src/shared/stores/gesture-store.ts +15 -1
  125. package/lib/commonjs/shared/stores/navigator-dismiss-state.js +0 -23
  126. package/lib/commonjs/shared/stores/navigator-dismiss-state.js.map +0 -1
  127. package/lib/module/shared/stores/navigator-dismiss-state.js +0 -19
  128. package/lib/module/shared/stores/navigator-dismiss-state.js.map +0 -1
  129. package/lib/typescript/shared/stores/navigator-dismiss-state.d.ts +0 -7
  130. package/lib/typescript/shared/stores/navigator-dismiss-state.d.ts.map +0 -1
  131. package/src/shared/providers/utils/create-provider.ts +0 -64
  132. package/src/shared/stores/navigator-dismiss-state.ts +0 -17
@@ -0,0 +1,90 @@
1
+ import type { NavigationRoute, ParamListBase } from "@react-navigation/native";
2
+ import { useCallback, useEffect, useMemo, useState } from "react";
3
+ import { useDerivedValue } from "react-native-reanimated";
4
+ import { useSharedValueState } from "../../shared/hooks/use-shared-value-state";
5
+ import createProvider from "../../shared/providers/utils/create-provider";
6
+ import { AnimationStore } from "../../shared/stores/animation-store";
7
+ import { useStackNavigationContext } from "../utils/with-stack-navigation";
8
+
9
+ interface BlankStackStateProviderProps {
10
+ children: React.ReactNode;
11
+ }
12
+
13
+ type BlankStackStateProviderContext = {
14
+ parentIndex: number;
15
+ parentRoutes: NavigationRoute<ParamListBase, string>[];
16
+ index: number;
17
+ routes: NavigationRoute<ParamListBase, string>[];
18
+ };
19
+
20
+ const {
21
+ useBlankStackStateContext: useBlankStackState,
22
+ BlankStackStateProvider,
23
+ } = createProvider("BlankStackState", { guarded: false })<
24
+ BlankStackStateProviderProps,
25
+ BlankStackStateProviderContext
26
+ >(() => {
27
+ const { focusedIndex, routes: stackRoutes } = useStackNavigationContext();
28
+ const [index, setIndex] = useState(0);
29
+ const [routes, setRoutes] = useState<
30
+ NavigationRoute<ParamListBase, string>[]
31
+ >([]);
32
+
33
+ const progressValues = useMemo(
34
+ () => stackRoutes.map((route) => AnimationStore.getAll(route.key)),
35
+ [stackRoutes],
36
+ );
37
+
38
+ const parentIndex = useDerivedValue(() => {
39
+ "worklet";
40
+
41
+ const activeIndex = progressValues.length - 1;
42
+ const isOneDismissing = Number(
43
+ progressValues.some((value) => value.closing.value > 0),
44
+ );
45
+
46
+ const optimisticIndex = activeIndex - isOneDismissing;
47
+
48
+ return optimisticIndex;
49
+ }, [focusedIndex, progressValues]);
50
+
51
+ const parentIndexValue = useSharedValueState(parentIndex);
52
+
53
+ const _registerNested = useCallback(
54
+ (
55
+ nestedIndex: number,
56
+ nestedRoutes: NavigationRoute<ParamListBase, string>[],
57
+ ) => {
58
+ if (nestedIndex !== index) {
59
+ setIndex(nestedIndex);
60
+ }
61
+ if (nestedRoutes.length !== routes.length) {
62
+ setRoutes(nestedRoutes);
63
+ }
64
+ },
65
+ [routes.length, index],
66
+ );
67
+
68
+ const context = useBlankStackState();
69
+
70
+ useEffect(() => {
71
+ //@ts-expect-error Internally used
72
+ if (context?._registerNested) {
73
+ //@ts-expect-error Internally used
74
+ context._registerNested(parentIndexValue, stackRoutes);
75
+ }
76
+ //@ts-expect-error Internally used
77
+ }, [parentIndexValue, context?._registerNested, stackRoutes]);
78
+
79
+ return {
80
+ value: {
81
+ parentIndex: parentIndexValue,
82
+ parentRoutes: stackRoutes,
83
+ index,
84
+ routes,
85
+ _registerNested,
86
+ },
87
+ };
88
+ });
89
+
90
+ export { useBlankStackState, BlankStackStateProvider };
@@ -16,6 +16,7 @@ import type { EdgeInsets } from "react-native-safe-area-context";
16
16
  import type { ScreenProps } from "react-native-screens";
17
17
  import type {
18
18
  OverlayInterpolationProps,
19
+ ScreenInterpolationProps,
19
20
  ScreenStyleInterpolator,
20
21
  TransitionSpec,
21
22
  } from "../shared/types/animation";
@@ -106,6 +107,10 @@ export type BlankStackOverlayProps = {
106
107
  * Route object for the current screen.
107
108
  */
108
109
  route: Route<string>;
110
+ /**
111
+ * Route object for the currently focused screen in the stack.
112
+ */
113
+ focusedRoute: Route<string>;
109
114
  /**
110
115
  * Navigation prop for the overlay.
111
116
  */
@@ -115,9 +120,13 @@ export type BlankStackOverlayProps = {
115
120
  */
116
121
  insets: EdgeInsets;
117
122
  /**
118
- * Accumulated progress across the stack for the overlay owner and subsequent screens.
123
+ * Animation values for the overlay.
124
+ */
125
+ overlayAnimation: DerivedValue<OverlayInterpolationProps>;
126
+ /**
127
+ * Animation values for the screen.
119
128
  */
120
- animation: DerivedValue<OverlayInterpolationProps>;
129
+ screenAnimation: DerivedValue<ScreenInterpolationProps>;
121
130
  /**
122
131
  * Index of the active route
123
132
  */
@@ -513,11 +513,13 @@ export function NativeStackView({
513
513
  return acc;
514
514
  }, {});
515
515
 
516
+ const routes = state.routes.concat(state.preloadedRoutes);
517
+
516
518
  return (
517
519
  <GestureHandlerRootView>
518
520
  <SafeAreaProviderCompat>
519
521
  <ScreenStack style={styles.container}>
520
- {state.routes.concat(state.preloadedRoutes).map((route, index) => {
522
+ {routes.map((route, index) => {
521
523
  const descriptor =
522
524
  descriptors[route.key] ?? preloadedDescriptors[route.key];
523
525
  const isFocused = state.index === index;
@@ -4,10 +4,11 @@ import type { BlankStackDescriptor } from "../../../blank-stack/types";
4
4
  import { useStackNavigationContext } from "../../../blank-stack/utils/with-stack-navigation";
5
5
  import type { NativeStackDescriptor } from "../../../native-stack/types";
6
6
  import { useParentGestureRegistry } from "../../hooks/gestures/use-parent-gesture-registry";
7
+ import { useDerivedValueState } from "../../hooks/use-derived-value-state";
7
8
  import useStableCallback from "../../hooks/use-stable-callback";
9
+ import { useGestureContext } from "../../providers/gestures";
8
10
  import { useKeys } from "../../providers/keys";
9
11
  import { AnimationStore } from "../../stores/animation-store";
10
- import { NavigatorDismissState } from "../../stores/navigator-dismiss-state";
11
12
  import { resetStoresForScreen } from "../../stores/utils/reset-stores-for-screen";
12
13
  import { startScreenTransition } from "../../utils/animation/start-screen-transition";
13
14
 
@@ -22,19 +23,22 @@ export const NativeStackScreenLifecycleController = ({
22
23
  children,
23
24
  }: ScreenLifecycleProps) => {
24
25
  const { current } = useKeys<NativeStackDescriptor>();
26
+ const { parentContext } = useGestureContext();
27
+
28
+ const isParentDismissingViaGesture = useDerivedValueState(() => {
29
+ "worklet";
30
+ return parentContext?.gestureAnimationValues.isDismissing?.value ?? false;
31
+ });
25
32
 
26
33
  const animations = AnimationStore.getAll(current.route.key);
27
34
 
28
35
  const handleBeforeRemove = useStableCallback((e: any) => {
29
- const key = current.navigation.getParent()?.getState().key;
30
- const requestedDismissOnNavigator = NavigatorDismissState.get(key);
31
-
32
36
  const isEnabled = current.options.enableTransitions;
33
- const isRequestedDismissOnNavigator = requestedDismissOnNavigator;
37
+
34
38
  const isFirstScreen = current.navigation.getState().index === 0;
35
39
 
36
40
  // If transitions are disabled, or the dismissal was on the local root, or this is the first screen of the stack, reset the stores
37
- if (!isEnabled || isRequestedDismissOnNavigator || isFirstScreen) {
41
+ if (!isEnabled || isParentDismissingViaGesture || isFirstScreen) {
38
42
  resetStoresForScreen(current);
39
43
  return;
40
44
  }
@@ -105,6 +109,10 @@ export const BlankStackScreenLifecycleController = ({
105
109
  });
106
110
  });
107
111
 
112
+ const handleCleanup = useStableCallback(() => {
113
+ resetStoresForScreen(current);
114
+ });
115
+
108
116
  const handleCloseEnd = useStableCallback((finished: boolean) => {
109
117
  if (!finished) {
110
118
  return;
@@ -130,7 +138,12 @@ export const BlankStackScreenLifecycleController = ({
130
138
  },
131
139
  );
132
140
 
133
- useLayoutEffect(handleInitialize);
141
+ useLayoutEffect(() => {
142
+ handleInitialize();
143
+ return () => {
144
+ handleCleanup();
145
+ };
146
+ }, [handleInitialize, handleCleanup]);
134
147
 
135
148
  // important for t.a scrollviews inside nested navigators.
136
149
  useParentGestureRegistry();
@@ -9,6 +9,7 @@ import {
9
9
  NO_BOUNDS_MAP,
10
10
  } from "../../constants";
11
11
  import { type TransitionDescriptor, useKeys } from "../../providers/keys";
12
+
12
13
  import { AnimationStore } from "../../stores/animation-store";
13
14
  import { BoundStore } from "../../stores/bound-store";
14
15
  import { GestureStore, type GestureStoreMap } from "../../stores/gesture-store";
@@ -121,7 +122,6 @@ export function _useScreenAnimation() {
121
122
  focused,
122
123
  activeBoundId,
123
124
  progress,
124
-
125
125
  active,
126
126
  isActiveTransitioning,
127
127
  isDismissing,
@@ -1,4 +1,11 @@
1
- import { createContext, Fragment, useContext, useMemo } from "react";
1
+ import {
2
+ createContext,
3
+ Fragment,
4
+ useContext,
5
+ useLayoutEffect,
6
+ useMemo,
7
+ useRef,
8
+ } from "react";
2
9
  import type { View } from "react-native";
3
10
  import {
4
11
  type AnimatedRef,
@@ -132,6 +139,30 @@ export const useBoundsRegistry = ({
132
139
  );
133
140
  }, [IS_ROOT, sharedBoundTag, ROOT_SIGNAL]);
134
141
 
142
+ const prevNextRef = useRef(next);
143
+ /**
144
+ * Measure non-pressable elements when the screen goes from focused to blurred
145
+ * (or when a new `next` descriptor appears) so we capture final bounds
146
+ * right before the transition starts.
147
+ */
148
+ useLayoutEffect(() => {
149
+ if (!sharedBoundTag || onPress) return;
150
+
151
+ const hadNext = !!prevNextRef.current;
152
+ const hasNext = !!next;
153
+
154
+ if (!hadNext && hasNext) {
155
+ runOnUI(maybeMeasureAndStore)({});
156
+ }
157
+
158
+ prevNextRef.current = next;
159
+ }, [next, sharedBoundTag, onPress, maybeMeasureAndStore]);
160
+
161
+ /**
162
+ * Signal child shared elements (nested under this provider) to refresh their
163
+ * measurements when the root updates, while preventing them from marking
164
+ * themselves active during that sync.
165
+ */
135
166
  useAnimatedReaction(
136
167
  () => ROOT_MEASUREMENT_SIGNAL?.updateSignal.value,
137
168
  (current) => {
@@ -1,4 +1,5 @@
1
- import { useMemo } from "react";
1
+ import { StackActions } from "@react-navigation/native";
2
+ import { useCallback, useMemo } from "react";
2
3
  import { useWindowDimensions } from "react-native";
3
4
  import {
4
5
  Gesture,
@@ -9,11 +10,7 @@ import {
9
10
  type PanGestureHandlerEventPayload,
10
11
  } from "react-native-gesture-handler";
11
12
  import type { GestureStateManagerType } from "react-native-gesture-handler/lib/typescript/handlers/gestures/gestureStateManager";
12
- import {
13
- runOnJS,
14
- type SharedValue,
15
- useSharedValue,
16
- } from "react-native-reanimated";
13
+ import { type SharedValue, useSharedValue } from "react-native-reanimated";
17
14
  import {
18
15
  DEFAULT_GESTURE_ACTIVATION_AREA,
19
16
  DEFAULT_GESTURE_DIRECTION,
@@ -21,11 +18,14 @@ import {
21
18
  DEFAULT_GESTURE_ENABLED,
22
19
  GESTURE_VELOCITY_IMPACT,
23
20
  } from "../../constants";
24
- import type { ScrollConfig } from "../../providers/gestures";
21
+ import type {
22
+ GestureContextType,
23
+ ScrollConfig,
24
+ } from "../../providers/gestures";
25
25
  import { useKeys } from "../../providers/keys";
26
26
  import { AnimationStore } from "../../stores/animation-store";
27
- import { GestureStore } from "../../stores/gesture-store";
28
- import { NavigatorDismissState } from "../../stores/navigator-dismiss-state";
27
+ import { GestureStore, type GestureStoreMap } from "../../stores/gesture-store";
28
+
29
29
  import { type GestureDirection, GestureOffsetState } from "../../types/gesture";
30
30
  import { startScreenTransition } from "../../utils/animation/start-screen-transition";
31
31
  import { applyOffsetRules } from "../../utils/gesture/check-gesture-activation";
@@ -33,18 +33,20 @@ import { determineDismissal } from "../../utils/gesture/determine-dismissal";
33
33
  import { mapGestureToProgress } from "../../utils/gesture/map-gesture-to-progress";
34
34
  import { resetGestureValues } from "../../utils/gesture/reset-gesture-values";
35
35
  import { velocity } from "../../utils/gesture/velocity";
36
- import useStableCallback from "../use-stable-callback";
37
36
  import useStableCallbackValue from "../use-stable-callback-value";
38
37
 
39
38
  interface BuildGesturesHookProps {
40
39
  scrollConfig: SharedValue<ScrollConfig | null>;
40
+ parentContext?: GestureContextType | null;
41
41
  }
42
42
 
43
43
  export const useBuildGestures = ({
44
44
  scrollConfig,
45
+ parentContext,
45
46
  }: BuildGesturesHookProps): {
46
47
  panGesture: GestureType;
47
48
  nativeGesture: GestureType;
49
+ gestureAnimationValues: GestureStoreMap;
48
50
  } => {
49
51
  const dimensions = useWindowDimensions();
50
52
  const { current } = useKeys();
@@ -58,7 +60,9 @@ export const useBuildGestures = ({
58
60
  GestureOffsetState.PENDING,
59
61
  );
60
62
 
61
- const gestures = GestureStore.getRouteGestures(current.route.key);
63
+ const gestureAnimationValues = GestureStore.getRouteGestures(
64
+ current.route.key,
65
+ );
62
66
  const animations = AnimationStore.getAll(current.route.key);
63
67
 
64
68
  const {
@@ -87,17 +91,29 @@ export const useBuildGestures = ({
87
91
  };
88
92
  }, [gestureDirection]);
89
93
 
90
- const setNavigatorDismissal = useStableCallback(() => {
91
- const key = current.navigation.getState().key;
94
+ const handleDismiss = useCallback(() => {
95
+ // If an ancestor navigator is already dismissing, skip this dismiss to
96
+ // avoid racing with the parent
97
+ if (parentContext?.gestureAnimationValues.isDismissing?.value) {
98
+ return;
99
+ }
92
100
 
93
- NavigatorDismissState.set(key, true);
94
- });
101
+ const state = current.navigation.getState();
95
102
 
96
- const handleDismiss = useStableCallback(() => {
97
- const key = current.navigation.getState().key;
98
- current.navigation.goBack();
99
- NavigatorDismissState.remove(key);
100
- });
103
+ const routeStillPresent = state.routes.some(
104
+ (route) => route.key === current.route.key,
105
+ );
106
+
107
+ if (!routeStillPresent) {
108
+ return;
109
+ }
110
+
111
+ current.navigation.dispatch({
112
+ ...StackActions.pop(),
113
+ source: current.route.key,
114
+ target: state.key,
115
+ });
116
+ }, [current, parentContext]);
101
117
 
102
118
  const onTouchesDown = useStableCallbackValue((e: GestureTouchEvent) => {
103
119
  "worklet";
@@ -110,6 +126,13 @@ export const useBuildGestures = ({
110
126
  (e: GestureTouchEvent, manager: GestureStateManagerType) => {
111
127
  "worklet";
112
128
 
129
+ // If an ancestor navigator is already dismissing via gesture, block new gestures here.
130
+ if (parentContext?.gestureAnimationValues.isDismissing?.value) {
131
+ gestureOffsetState.value = GestureOffsetState.FAILED;
132
+ manager.fail();
133
+ return;
134
+ }
135
+
113
136
  const touch = e.changedTouches[0];
114
137
 
115
138
  const { isSwipingDown, isSwipingUp, isSwipingRight, isSwipingLeft } =
@@ -130,7 +153,7 @@ export const useBuildGestures = ({
130
153
  }
131
154
 
132
155
  // Keep pending until thresholds are met; no eager activation.
133
- if (gestures.isDragging?.value) {
156
+ if (gestureAnimationValues.isDragging?.value) {
134
157
  manager.activate();
135
158
  return;
136
159
  }
@@ -178,9 +201,9 @@ export const useBuildGestures = ({
178
201
  if (
179
202
  shouldActivate &&
180
203
  gestureOffsetState.value === GestureOffsetState.PASSED &&
181
- !gestures.isDismissing?.value
204
+ !gestureAnimationValues.isDismissing?.value
182
205
  ) {
183
- gestures.direction.value = activatedDirection;
206
+ gestureAnimationValues.direction.value = activatedDirection;
184
207
  manager.activate();
185
208
  return;
186
209
  }
@@ -189,8 +212,8 @@ export const useBuildGestures = ({
189
212
 
190
213
  const onStart = useStableCallbackValue(() => {
191
214
  "worklet";
192
- gestures.isDragging.value = 1;
193
- gestures.isDismissing.value = 0;
215
+ gestureAnimationValues.isDragging.value = 1;
216
+ gestureAnimationValues.isDismissing.value = 0;
194
217
  });
195
218
 
196
219
  const onUpdate = useStableCallbackValue(
@@ -202,13 +225,13 @@ export const useBuildGestures = ({
202
225
  const { translationX, translationY } = event;
203
226
  const { width, height } = dimensions;
204
227
 
205
- gestures.x.value = translationX;
206
- gestures.y.value = translationY;
207
- gestures.normalizedX.value = Math.max(
228
+ gestureAnimationValues.x.value = translationX;
229
+ gestureAnimationValues.y.value = translationY;
230
+ gestureAnimationValues.normalizedX.value = Math.max(
208
231
  -1,
209
232
  Math.min(1, translationX / width),
210
233
  );
211
- gestures.normalizedY.value = Math.max(
234
+ gestureAnimationValues.normalizedY.value = Math.max(
212
235
  -1,
213
236
  Math.min(1, translationY / height),
214
237
  );
@@ -275,16 +298,12 @@ export const useBuildGestures = ({
275
298
 
276
299
  resetGestureValues({
277
300
  spec,
278
- gestures,
301
+ gestures: gestureAnimationValues,
279
302
  shouldDismiss,
280
303
  event,
281
304
  dimensions,
282
305
  });
283
306
 
284
- if (shouldDismiss) {
285
- runOnJS(setNavigatorDismissal)();
286
- }
287
-
288
307
  const initialVelocity = velocity.calculateProgressVelocity({
289
308
  animations,
290
309
  shouldDismiss,
@@ -319,6 +338,15 @@ export const useBuildGestures = ({
319
338
  return {
320
339
  panGesture,
321
340
  nativeGesture,
341
+ gestureAnimationValues,
322
342
  };
323
- }, [gestureEnabled, onTouchesDown, onTouchesMove, onStart, onUpdate, onEnd]);
343
+ }, [
344
+ gestureEnabled,
345
+ onTouchesDown,
346
+ onTouchesMove,
347
+ onStart,
348
+ onUpdate,
349
+ onEnd,
350
+ gestureAnimationValues,
351
+ ]);
324
352
  };
@@ -0,0 +1,41 @@
1
+ import { useState } from "react";
2
+ import {
3
+ executeOnUIRuntimeSync,
4
+ runOnJS,
5
+ type SharedValue,
6
+ useAnimatedReaction,
7
+ useDerivedValue,
8
+ } from "react-native-reanimated";
9
+
10
+ /**
11
+ * Derives React state from a Reanimated worklet.
12
+ *
13
+ * @param processor - The worklet function that calculates the value
14
+ * @param dependencies - Array of dependencies for the worklet
15
+ * @returns The derived value as React State
16
+ */
17
+ export function useDerivedValueState<T>(
18
+ processor: () => T,
19
+ dependencies: any[] = [],
20
+ ): T {
21
+ const derivedValue = useDerivedValue(processor, dependencies);
22
+
23
+ const [state, setState] = useState<T>(() => {
24
+ const readOnUI = executeOnUIRuntimeSync((sv: SharedValue<T>) => {
25
+ "worklet";
26
+ return sv.value;
27
+ });
28
+ return readOnUI(derivedValue);
29
+ });
30
+
31
+ useAnimatedReaction(
32
+ () => derivedValue.value,
33
+ (curr, prev) => {
34
+ if (curr !== prev) {
35
+ runOnJS(setState)(curr);
36
+ }
37
+ },
38
+ );
39
+
40
+ return state;
41
+ }
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { useCallback, useEffect, useState } from "react";
6
6
  import {
7
+ cancelAnimation,
7
8
  isWorkletFunction,
8
9
  makeMutable,
9
10
  runOnJS,
@@ -13,7 +14,15 @@ import {
13
14
  type AnyFunction = (...args: Array<any>) => any;
14
15
 
15
16
  function useMutableValue<T>(initialValue: T) {
16
- return useState(() => makeMutable(initialValue))[0];
17
+ const [mutable] = useState(() => makeMutable(initialValue));
18
+
19
+ useEffect(() => {
20
+ return () => {
21
+ cancelAnimation(mutable);
22
+ };
23
+ }, [mutable]);
24
+
25
+ return mutable;
17
26
  }
18
27
 
19
28
  // We cannot store a function as a SharedValue because reanimated will treat
@@ -19,3 +19,11 @@ export default {
19
19
  };
20
20
 
21
21
  export { useScreenAnimation } from "./hooks/animation/use-screen-animation";
22
+
23
+ export type {
24
+ AnimationConfig,
25
+ OverlayInterpolationProps,
26
+ ScreenInterpolationProps,
27
+ ScreenStyleInterpolator,
28
+ } from "./types/animation";
29
+ export type { ScreenTransitionConfig } from "./types/core";
@@ -5,6 +5,7 @@ import { GestureDetector } from "react-native-gesture-handler";
5
5
  import type { SharedValue } from "react-native-reanimated";
6
6
  import { useSharedValue } from "react-native-reanimated";
7
7
  import { useBuildGestures } from "../hooks/gestures/use-build-gestures";
8
+ import type { GestureStoreMap } from "../stores/gesture-store";
8
9
 
9
10
  export type ScrollConfig = {
10
11
  x: number;
@@ -19,34 +20,42 @@ export interface GestureContextType {
19
20
  panGesture: GestureType;
20
21
  nativeGesture: GestureType;
21
22
  scrollConfig: SharedValue<ScrollConfig | null>;
23
+ gestureAnimationValues: GestureStoreMap;
22
24
  parentContext: GestureContextType | null;
23
25
  }
24
26
 
25
- type ScreenGestureProviderProps = {
27
+ type GestureProviderProps = {
26
28
  children: React.ReactNode;
27
29
  };
28
30
 
29
31
  const GestureContext = createContext<GestureContextType | undefined>(undefined);
30
32
 
31
- export const ScreenGestureProvider = ({
32
- children,
33
- }: ScreenGestureProviderProps) => {
33
+ export const ScreenGestureProvider = ({ children }: GestureProviderProps) => {
34
34
  const parentContext = useContext(GestureContext);
35
35
 
36
36
  const scrollConfig = useSharedValue<ScrollConfig | null>(null);
37
37
 
38
- const { panGesture, nativeGesture } = useBuildGestures({
39
- scrollConfig,
40
- });
38
+ const { panGesture, nativeGesture, gestureAnimationValues } =
39
+ useBuildGestures({
40
+ scrollConfig,
41
+ parentContext,
42
+ });
41
43
 
42
44
  const value: GestureContextType = useMemo(
43
45
  () => ({
44
46
  panGesture,
45
47
  scrollConfig,
46
48
  nativeGesture,
49
+ gestureAnimationValues,
47
50
  parentContext: parentContext || null,
48
51
  }),
49
- [panGesture, scrollConfig, nativeGesture, parentContext],
52
+ [
53
+ panGesture,
54
+ scrollConfig,
55
+ nativeGesture,
56
+ gestureAnimationValues,
57
+ parentContext,
58
+ ],
50
59
  );
51
60
 
52
61
  return (
@@ -0,0 +1,77 @@
1
+ /**
2
+ * THANK YOU @MatiPl01
3
+ * https://github.com/MatiPl01/react-native-sortables/blob/main/packages/react-native-sortables/src/providers/utils/createProvider.tsx
4
+ * SUPER COOL AMAZING UTILITY
5
+ */
6
+ import {
7
+ createContext,
8
+ type PropsWithChildren,
9
+ type ReactNode,
10
+ useContext,
11
+ useMemo,
12
+ } from "react";
13
+
14
+ export default function createProvider<
15
+ ProviderName extends string,
16
+ Guarded extends boolean = true,
17
+ >(name: ProviderName, options?: { guarded?: Guarded }) {
18
+ return <ProviderProps extends PropsWithChildren<object>, ContextValue>(
19
+ factory: (props: ProviderProps) => {
20
+ value?: ContextValue;
21
+ enabled?: boolean;
22
+ children?: ReactNode;
23
+ },
24
+ ) => {
25
+ const { guarded = true } = options ?? {};
26
+
27
+ const Context = createContext<ContextValue | null>(null);
28
+ Context.displayName = name;
29
+
30
+ const Provider: React.FC<ProviderProps> = (props) => {
31
+ const {
32
+ children = props.children,
33
+ enabled = true,
34
+ value,
35
+ } = factory(props);
36
+
37
+ if (!value) {
38
+ throw new Error(
39
+ `${name}Context value must be provided. You likely forgot to return it from the factory function.`,
40
+ );
41
+ }
42
+
43
+ const memoValue = useMemo(
44
+ () => (enabled ? value : null),
45
+ [enabled, value],
46
+ );
47
+
48
+ return <Context.Provider value={memoValue}>{children}</Context.Provider>;
49
+ };
50
+
51
+ const useEnhancedContext = (): ContextValue | null => {
52
+ const context = useContext(Context);
53
+
54
+ if (guarded && context === null) {
55
+ throw new Error(
56
+ `${name} context must be used within a ${name}Provider`,
57
+ );
58
+ }
59
+
60
+ return context;
61
+ };
62
+
63
+ return {
64
+ [`${name}Context`]: Context,
65
+ [`${name}Provider`]: Provider,
66
+ [`use${name}Context`]: useEnhancedContext,
67
+ } as {
68
+ [P in ProviderName as `${P}Context`]: React.Context<ContextValue>;
69
+ } & {
70
+ [P in ProviderName as `${P}Provider`]: React.FC<ProviderProps>;
71
+ } & {
72
+ [P in ProviderName as `use${P}Context`]: () => Guarded extends true
73
+ ? ContextValue
74
+ : ContextValue | null;
75
+ };
76
+ };
77
+ }