react-native-screen-transitions 2.0.2 → 2.0.4

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 (77) hide show
  1. package/lib/commonjs/utils/bounds/constants.js +3 -3
  2. package/lib/commonjs/utils/bounds/constants.js.map +1 -1
  3. package/lib/commonjs/utils/bounds/get-bounds.js +3 -3
  4. package/lib/commonjs/utils/bounds/get-bounds.js.map +1 -1
  5. package/lib/module/utils/bounds/constants.js +3 -3
  6. package/lib/module/utils/bounds/constants.js.map +1 -1
  7. package/lib/module/utils/bounds/get-bounds.js +3 -3
  8. package/lib/module/utils/bounds/get-bounds.js.map +1 -1
  9. package/lib/typescript/utils/bounds/_types/get-bounds.d.ts +2 -2
  10. package/lib/typescript/utils/bounds/_types/get-bounds.d.ts.map +1 -1
  11. package/lib/typescript/utils/bounds/constants.d.ts +4 -4
  12. package/lib/typescript/utils/bounds/constants.d.ts.map +1 -1
  13. package/lib/typescript/utils/bounds/get-bounds.d.ts +2 -2
  14. package/lib/typescript/utils/bounds/get-bounds.d.ts.map +1 -1
  15. package/lib/typescript/utils/bounds/index.d.ts +1 -1
  16. package/lib/typescript/utils/bounds/index.d.ts.map +1 -1
  17. package/package.json +4 -2
  18. package/src/__tests__/geometry.test.ts +127 -0
  19. package/src/components/bounds-activator.tsx +29 -0
  20. package/src/components/controllers/screen-lifecycle.tsx +72 -0
  21. package/src/components/create-transition-aware-component.tsx +99 -0
  22. package/src/components/root-transition-aware.tsx +56 -0
  23. package/src/configs/index.ts +2 -0
  24. package/src/configs/presets.ts +227 -0
  25. package/src/configs/specs.ts +9 -0
  26. package/src/hooks/animation/use-associated-style.tsx +28 -0
  27. package/src/hooks/animation/use-screen-animation.tsx +142 -0
  28. package/src/hooks/bounds/use-bound-measurer.tsx +71 -0
  29. package/src/hooks/gestures/use-build-gestures.tsx +369 -0
  30. package/src/hooks/gestures/use-scroll-progress.tsx +60 -0
  31. package/src/hooks/use-stable-callback.tsx +15 -0
  32. package/src/index.ts +32 -0
  33. package/src/integrations/native-stack/navigators/createNativeStackNavigator.tsx +112 -0
  34. package/src/integrations/native-stack/utils/debounce.tsx +14 -0
  35. package/src/integrations/native-stack/utils/getModalRoutesKeys.ts +21 -0
  36. package/src/integrations/native-stack/utils/useAnimatedHeaderHeight.tsx +18 -0
  37. package/src/integrations/native-stack/utils/useDismissedRouteError.tsx +30 -0
  38. package/src/integrations/native-stack/utils/useInvalidPreventRemoveError.tsx +31 -0
  39. package/src/integrations/native-stack/views/FontProcessor.native.tsx +12 -0
  40. package/src/integrations/native-stack/views/FontProcessor.tsx +5 -0
  41. package/src/integrations/native-stack/views/FooterComponent.tsx +10 -0
  42. package/src/integrations/native-stack/views/NativeStackView.native.tsx +657 -0
  43. package/src/integrations/native-stack/views/NativeStackView.tsx +214 -0
  44. package/src/integrations/native-stack/views/useHeaderConfigProps.tsx +295 -0
  45. package/src/providers/gestures.tsx +89 -0
  46. package/src/providers/keys.tsx +38 -0
  47. package/src/stores/animations.ts +45 -0
  48. package/src/stores/bounds.ts +71 -0
  49. package/src/stores/gestures.ts +55 -0
  50. package/src/stores/navigator-dismiss-state.ts +17 -0
  51. package/src/stores/utils/reset-stores-for-screen.ts +14 -0
  52. package/src/types/animation.ts +76 -0
  53. package/src/types/bounds.ts +82 -0
  54. package/src/types/core.ts +50 -0
  55. package/src/types/gesture.ts +33 -0
  56. package/src/types/navigator.ts +744 -0
  57. package/src/types/utils.ts +3 -0
  58. package/src/utils/animation/animate.ts +28 -0
  59. package/src/utils/animation/run-transition.ts +49 -0
  60. package/src/utils/bounds/_types/builder.ts +35 -0
  61. package/src/utils/bounds/_types/geometry.ts +17 -0
  62. package/src/utils/bounds/_types/get-bounds.ts +10 -0
  63. package/src/utils/bounds/build-bound-styles.ts +184 -0
  64. package/src/utils/bounds/constants.ts +25 -0
  65. package/src/utils/bounds/flatten-styles.ts +21 -0
  66. package/src/utils/bounds/geometry.ts +113 -0
  67. package/src/utils/bounds/get-bounds.ts +56 -0
  68. package/src/utils/bounds/index.ts +46 -0
  69. package/src/utils/bounds/style-composers.ts +172 -0
  70. package/src/utils/gesture/apply-gesture-activation-criteria.ts +109 -0
  71. package/src/utils/gesture/map-gesture-to-progress.ts +11 -0
  72. package/src/utils/gesture/normalize-gesture-translation.ts +20 -0
  73. package/src/utils/index.ts +1 -0
  74. package/lib/commonjs/__tests__ /geometry.test.js +0 -178
  75. package/lib/commonjs/__tests__ /geometry.test.js.map +0 -1
  76. package/lib/module/__tests__ /geometry.test.js +0 -178
  77. package/lib/module/__tests__ /geometry.test.js.map +0 -1
@@ -0,0 +1,71 @@
1
+ import {
2
+ type MeasuredDimensions,
3
+ makeMutable,
4
+ type StyleProps,
5
+ } from "react-native-reanimated";
6
+ import type { ScreenKey } from "../types/navigator";
7
+ import type { Any } from "../types/utils";
8
+
9
+ type BoundsDict = Record<
10
+ string,
11
+ Record<string, { bounds: MeasuredDimensions; styles: StyleProps }>
12
+ >;
13
+
14
+ const registry = makeMutable<BoundsDict>({});
15
+ const activeBoundId = makeMutable<string | null>(null);
16
+
17
+ function setBounds(
18
+ screenId: string,
19
+ boundId: string,
20
+ bounds: MeasuredDimensions | null = null,
21
+ styles: StyleProps = {},
22
+ ) {
23
+ "worklet";
24
+ registry.modify((state: Any) => {
25
+ "worklet";
26
+ if (!state[screenId]) {
27
+ state[screenId] = {};
28
+ }
29
+ state[screenId][boundId] = { bounds, styles };
30
+
31
+ return state;
32
+ });
33
+ }
34
+
35
+ function getBounds(screenId: string) {
36
+ "worklet";
37
+ return registry.value[screenId] ?? {};
38
+ }
39
+
40
+ function setActiveBoundId(boundId: string) {
41
+ "worklet";
42
+ activeBoundId.value = boundId;
43
+ }
44
+
45
+ function getActiveBoundId() {
46
+ "worklet";
47
+ return activeBoundId.value;
48
+ }
49
+
50
+ function clear(routeKey: ScreenKey) {
51
+ "worklet";
52
+ registry.modify((state: Any) => {
53
+ "worklet";
54
+ delete state[routeKey];
55
+ return state;
56
+ });
57
+ }
58
+
59
+ function clearActive() {
60
+ "worklet";
61
+ activeBoundId.value = null;
62
+ }
63
+
64
+ export const Bounds = {
65
+ setBounds,
66
+ getBounds,
67
+ setActiveBoundId,
68
+ getActiveBoundId,
69
+ clear,
70
+ clearActive,
71
+ };
@@ -0,0 +1,55 @@
1
+ import { makeMutable, type SharedValue } from "react-native-reanimated";
2
+ import type { ScreenKey } from "../types/navigator";
3
+
4
+ export type GestureKey =
5
+ | "x"
6
+ | "y"
7
+ | "normalizedX"
8
+ | "normalizedY"
9
+ | "isDismissing"
10
+ | "isDragging";
11
+
12
+ export type GestureMap = {
13
+ x: SharedValue<number>;
14
+ y: SharedValue<number>;
15
+ normalizedX: SharedValue<number>;
16
+ normalizedY: SharedValue<number>;
17
+ isDismissing: SharedValue<number>;
18
+ isDragging: SharedValue<number>;
19
+ };
20
+
21
+ const store: Record<ScreenKey, GestureMap> = {};
22
+
23
+ function ensure(routeKey: ScreenKey): GestureMap {
24
+ let bag = store[routeKey];
25
+ if (!bag) {
26
+ bag = {
27
+ x: makeMutable(0),
28
+ y: makeMutable(0),
29
+ normalizedX: makeMutable(0),
30
+ normalizedY: makeMutable(0),
31
+ isDismissing: makeMutable(0),
32
+ isDragging: makeMutable(0),
33
+ };
34
+ store[routeKey] = bag;
35
+ }
36
+ return bag;
37
+ }
38
+
39
+ function getGesture(routeKey: ScreenKey, gestureKey: GestureKey) {
40
+ return ensure(routeKey)[gestureKey];
41
+ }
42
+
43
+ function getRouteGestures(routeKey: ScreenKey) {
44
+ return ensure(routeKey);
45
+ }
46
+
47
+ function clear(routeKey: ScreenKey) {
48
+ delete store[routeKey];
49
+ }
50
+
51
+ export const Gestures = {
52
+ getGesture,
53
+ getRouteGestures,
54
+ clear,
55
+ };
@@ -0,0 +1,17 @@
1
+ const map = new Map<string, boolean>();
2
+
3
+ export const NavigatorDismissState = {
4
+ get(id: string | undefined): boolean {
5
+ if (!id) return false;
6
+ return map.get(id) === true;
7
+ },
8
+ set(id: string, val: boolean) {
9
+ map.set(id, !!val);
10
+ },
11
+ remove(id: string) {
12
+ map.delete(id);
13
+ },
14
+ clear() {
15
+ map.clear();
16
+ },
17
+ };
@@ -0,0 +1,14 @@
1
+ import type { NativeStackDescriptor } from "../../types/navigator";
2
+ import { Animations } from "../animations";
3
+ import { Bounds } from "../bounds";
4
+ import { Gestures } from "../gestures";
5
+
6
+ /**
7
+ * Reset all stores for a given screen
8
+ */
9
+ export const resetStoresForScreen = (current: NativeStackDescriptor) => {
10
+ Animations.clear(current.route.key);
11
+ Gestures.clear(current.route.key);
12
+ Bounds.clear(current.route.key);
13
+ Bounds.clearActive();
14
+ };
@@ -0,0 +1,76 @@
1
+ import type { ParamListBase, RouteProp } from "@react-navigation/native";
2
+ import type {
3
+ StyleProps,
4
+ WithSpringConfig,
5
+ WithTimingConfig,
6
+ } from "react-native-reanimated";
7
+ import type { EdgeInsets } from "react-native-safe-area-context";
8
+ import type { BoundEntry, BoundsAccessor } from "./bounds";
9
+ import type { GestureValues } from "./gesture";
10
+
11
+ export type ScreenTransitionState = {
12
+ progress: number;
13
+ closing: number;
14
+ animating: number;
15
+ gesture: GestureValues;
16
+ bounds: Record<string, BoundEntry>;
17
+ route: RouteProp<ParamListBase>;
18
+ };
19
+
20
+ export interface ScreenInterpolationProps {
21
+ /** Values for the screen that is the focus of the transition (e.g., the one opening). */
22
+ previous: ScreenTransitionState | undefined;
23
+ current: ScreenTransitionState;
24
+ /** Values for the screen immediately behind the current one in the screen. */
25
+ next: ScreenTransitionState | undefined;
26
+ /** Layout measurements for the screen. */
27
+ layouts: {
28
+ /** The `width` and `height` of the screen container. */
29
+ screen: {
30
+ width: number;
31
+ height: number;
32
+ };
33
+ };
34
+ /** The safe area insets for the screen. */
35
+ insets: EdgeInsets;
36
+ /** The id of the active bound. */
37
+ activeBoundId: string;
38
+ /** Whether the screen is focused. */
39
+ focused: boolean;
40
+ /** The progress of the screen transitions (0-2). */
41
+ progress: number;
42
+ /** A function that returns a bounds builder for the screen. */
43
+ bounds: BoundsAccessor;
44
+ }
45
+
46
+ export type ScreenStyleInterpolator = (
47
+ props: ScreenInterpolationProps,
48
+ ) => TransitionInterpolatedStyle;
49
+
50
+ export type TransitionInterpolatedStyle = {
51
+ /**
52
+ * Animated style for the main screen view. Styles are only applied when Transition.View is present.
53
+ */
54
+ contentStyle?: StyleProps;
55
+ /**
56
+ * Animated style for a semi-transparent overlay. Styles are only applied when Transition.View is present.
57
+ */
58
+ overlayStyle?: StyleProps;
59
+ /**
60
+ * Define your own custom styles by using an id as the key: [id]: StyleProps
61
+ */
62
+ [id: string]: StyleProps | undefined;
63
+ };
64
+
65
+ /**
66
+ * A Reanimated animation configuration object.
67
+ */
68
+ export type AnimationConfig = WithSpringConfig | WithTimingConfig;
69
+
70
+ /**
71
+ * Defines separate animation configurations for opening and closing a screen.
72
+ */
73
+ export interface TransitionSpec {
74
+ open?: AnimationConfig;
75
+ close?: AnimationConfig;
76
+ }
@@ -0,0 +1,82 @@
1
+ import type { MeasuredDimensions, StyleProps } from "react-native-reanimated";
2
+ import type { ScreenPhase } from "./core";
3
+
4
+ /**
5
+ * Target style computation.
6
+ * - "transform": translates and scales (scaleX/scaleY), no width/height size
7
+ * - "size": translates and sizes (width/height), no scaleX/scaleY
8
+ * - "content": screen-level content transform that aligns the destination screen
9
+ * so the target bound matches the source at progress start
10
+ */
11
+ export type BoundsMethod = "transform" | "size" | "content";
12
+
13
+ export type BoundsBuilder = {
14
+ /**
15
+ * Include gesture offsets (x/y) in the computed transform for all methods.
16
+ * This syncs the focused screen’s gesture deltas with the previous screen’s bound
17
+ * to give the shared look while interacting.
18
+ */
19
+ gestures: (options?: { x?: number; y?: number }) => BoundsBuilder;
20
+
21
+ /**
22
+ * Animate to the full screen bounds as the destination.
23
+ * Useful when the next screen does not define a bound for the same id.
24
+ */
25
+ toFullscreen: () => BoundsBuilder;
26
+
27
+ /**
28
+ * Compute using absolute window coordinates (pageX/pageY).
29
+ * No relative delta math—good when elements are unconstrained by parent layout.
30
+ */
31
+ absolute: () => BoundsBuilder;
32
+
33
+ /**
34
+ * Compute using relative deltas between start/end bounds (dx/dy, scale).
35
+ * This makes the math bound-relative; great when elements are within layout constraints.
36
+ */
37
+ relative: () => BoundsBuilder;
38
+
39
+ /**
40
+ * Select transform method: translate + scaleX/scaleY (no width/height size).
41
+ * Note: x/y translation is applied for all methods when applicable.
42
+ */
43
+ transform: () => BoundsBuilder;
44
+
45
+ /**
46
+ * Select size method: translate + width/height interpolation (no scaleX/scaleY).
47
+ */
48
+ size: () => BoundsBuilder;
49
+
50
+ /**
51
+ * Select content method: screen-level transform to align destination content
52
+ * so its bound matches the source at progress start. This modifies where the
53
+ * bound sits within the screen rather than the bound’s own local transform.
54
+ */
55
+ content: () => BoundsBuilder;
56
+
57
+ /**
58
+ * Select content scale mode: "aspectFill" (fill), "aspectFit" (fit), or "auto" (default).
59
+ */
60
+ contentFill: () => BoundsBuilder;
61
+
62
+ /**
63
+ * Select content scale mode: "aspectFill" (fill), "aspectFit" (fit), or "auto" (default).
64
+ */
65
+ contentFit: () => BoundsBuilder;
66
+
67
+ /**
68
+ * Build the final animated style.
69
+ * If a method is not explicitly selected via transform/resize/content,
70
+ * the provided argument will be used; defaults to "transform".
71
+ */
72
+ build: (method?: BoundsMethod) => StyleProps;
73
+ };
74
+
75
+ export type BoundEntry = {
76
+ bounds: MeasuredDimensions;
77
+ styles: StyleProps;
78
+ };
79
+
80
+ export type BoundsAccessor = ((id?: string) => BoundsBuilder) & {
81
+ get: (id?: string, phase?: ScreenPhase) => BoundEntry;
82
+ };
@@ -0,0 +1,50 @@
1
+ import type { AnimatedProps } from "react-native-reanimated";
2
+ import type { TransitionSpec } from "./animation";
3
+
4
+ export type ScreenPhase = "previous" | "current" | "next";
5
+
6
+ export type TransitionAwareProps<T extends object> = AnimatedProps<T> & {
7
+ /**
8
+ * Connects this component to custom animated styles defined in screenStyleInterpolator.
9
+ *
10
+ * When you return custom styles from your interpolator with a matching key,
11
+ * those styles will be applied to this component during transitions.
12
+ *
13
+ * @example
14
+ * // In your component:
15
+ * <Transition.View styleId="hero-image">
16
+ * <Image source={...} />
17
+ * </Transition.View>
18
+ *
19
+ * // In your screenStyleInterpolator:
20
+ * return {
21
+ * 'hero-image': {
22
+ * opacity: interpolate(progress, [0, 1], [0, 1]),
23
+ * transform: [{ scale: interpolate(progress, [0, 1], [0.8, 1]) }]
24
+ * }
25
+ * }
26
+ */
27
+ styleId?: string;
28
+
29
+ /**
30
+ * Marks this component for measurement to enable shared element transitions.
31
+ * Components with the same tag across different screens will animate between each other.
32
+ *
33
+ * @example
34
+ * // Screen A:
35
+ * <Transition.View sharedBoundTag="profile-avatar">
36
+ * <Avatar size="small" />
37
+ * </Transition.View>
38
+ *
39
+ * // Screen B:
40
+ * <Transition.View sharedBoundTag="profile-avatar">
41
+ * <Avatar size="large" />
42
+ * </Transition.View>
43
+ */
44
+ sharedBoundTag?: string;
45
+ };
46
+
47
+ export type TransitionConfig = {
48
+ open: TransitionSpec;
49
+ close: TransitionSpec;
50
+ };
@@ -0,0 +1,33 @@
1
+ export type GestureDirection =
2
+ | "horizontal"
3
+ | "horizontal-inverted"
4
+ | "vertical"
5
+ | "vertical-inverted"
6
+ | "bidirectional";
7
+
8
+ export type GestureValues = {
9
+ /**
10
+ * A `SharedValue` indicating if the user's finger is on the screen (0 or 1).
11
+ */
12
+ isDragging: number;
13
+ /**
14
+ * The live horizontal translation of the gesture.
15
+ */
16
+ x: number;
17
+ /**
18
+ * The live vertical translation of the gesture.
19
+ */
20
+ y: number;
21
+ /**
22
+ * The live normalized horizontal translation of the gesture (-1 to 1).
23
+ */
24
+ normalizedX: number;
25
+ /**
26
+ * The live normalized vertical translation of the gesture (-1 to 1).
27
+ */
28
+ normalizedY: number;
29
+ /**
30
+ * A flag indicating if the screen is in the process of dismissing.
31
+ */
32
+ isDismissing: number;
33
+ };