react-native-screen-transitions 3.0.0-rc.2 → 3.0.0-rc.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 (241) hide show
  1. package/README.md +421 -371
  2. package/lib/commonjs/blank-stack/components/{Overlay.js → overlay.js} +7 -5
  3. package/lib/commonjs/blank-stack/components/overlay.js.map +1 -0
  4. package/lib/commonjs/blank-stack/components/{Screens.js → screens.js} +20 -17
  5. package/lib/commonjs/blank-stack/components/screens.js.map +1 -0
  6. package/lib/commonjs/blank-stack/components/stack-view.js +101 -0
  7. package/lib/commonjs/blank-stack/components/stack-view.js.map +1 -0
  8. package/lib/commonjs/blank-stack/index.js +1 -8
  9. package/lib/commonjs/blank-stack/index.js.map +1 -1
  10. package/lib/commonjs/blank-stack/navigators/{createBlankStackNavigator.js → create-blank-stack-navigator.js} +3 -3
  11. package/lib/commonjs/blank-stack/navigators/create-blank-stack-navigator.js.map +1 -0
  12. package/lib/commonjs/blank-stack/utils/with-stack-navigation/helpers/compose-descriptors.js +1 -11
  13. package/lib/commonjs/blank-stack/utils/with-stack-navigation/helpers/compose-descriptors.js.map +1 -1
  14. package/lib/commonjs/blank-stack/utils/with-stack-navigation/hooks/use-closing-route-keys.js +1 -12
  15. package/lib/commonjs/blank-stack/utils/with-stack-navigation/hooks/use-closing-route-keys.js.map +1 -1
  16. package/lib/commonjs/blank-stack/utils/with-stack-navigation/hooks/use-stack-navigation-state.js.map +1 -1
  17. package/lib/commonjs/blank-stack/utils/with-stack-navigation/index.js +49 -55
  18. package/lib/commonjs/blank-stack/utils/with-stack-navigation/index.js.map +1 -1
  19. package/lib/commonjs/blank-stack/utils/with-stack-navigation/{_types.js → types.js} +1 -1
  20. package/lib/commonjs/blank-stack/utils/with-stack-navigation/types.js.map +1 -0
  21. package/lib/commonjs/native-stack/views/NativeStackView.native.js +110 -103
  22. package/lib/commonjs/native-stack/views/NativeStackView.native.js.map +1 -1
  23. package/lib/commonjs/shared/components/controllers/blank-stack-lifecycle.js +72 -0
  24. package/lib/commonjs/shared/components/controllers/blank-stack-lifecycle.js.map +1 -0
  25. package/lib/commonjs/shared/components/controllers/native-stack-lifecycle.js +79 -0
  26. package/lib/commonjs/shared/components/controllers/native-stack-lifecycle.js.map +1 -0
  27. package/lib/commonjs/shared/hooks/animation/use-screen-animation.js +49 -23
  28. package/lib/commonjs/shared/hooks/animation/use-screen-animation.js.map +1 -1
  29. package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js +11 -6
  30. package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js.map +1 -1
  31. package/lib/commonjs/shared/hooks/gestures/use-scroll-registry.js +7 -7
  32. package/lib/commonjs/shared/hooks/gestures/use-scroll-registry.js.map +1 -1
  33. package/lib/commonjs/shared/providers/flags.provider.js +25 -0
  34. package/lib/commonjs/shared/providers/flags.provider.js.map +1 -0
  35. package/lib/commonjs/shared/providers/gestures.provider.js +32 -5
  36. package/lib/commonjs/shared/providers/gestures.provider.js.map +1 -1
  37. package/lib/commonjs/shared/providers/register-bounds.provider.js +72 -45
  38. package/lib/commonjs/shared/providers/register-bounds.provider.js.map +1 -1
  39. package/lib/commonjs/shared/providers/routes.provider.js +48 -0
  40. package/lib/commonjs/shared/providers/routes.provider.js.map +1 -0
  41. package/lib/commonjs/shared/providers/screen-transition.provider.js.map +1 -1
  42. package/lib/commonjs/shared/stores/bounds.store.js +91 -47
  43. package/lib/commonjs/shared/stores/bounds.store.js.map +1 -1
  44. package/lib/commonjs/shared/types/state.types.js +9 -0
  45. package/lib/commonjs/shared/types/state.types.js.map +1 -0
  46. package/lib/commonjs/shared/utils/animation/compute-stack-progress.js +20 -0
  47. package/lib/commonjs/shared/utils/animation/compute-stack-progress.js.map +1 -0
  48. package/lib/commonjs/shared/utils/animation/derivations.js +1 -1
  49. package/lib/commonjs/shared/utils/animation/start-screen-transition.js +11 -11
  50. package/lib/commonjs/shared/utils/animation/start-screen-transition.js.map +1 -1
  51. package/lib/commonjs/shared/utils/bounds/helpers/is-bounds-equal.js +1 -1
  52. package/lib/commonjs/shared/utils/bounds/helpers/is-bounds-equal.js.map +1 -1
  53. package/lib/commonjs/shared/utils/bounds/index.js +4 -5
  54. package/lib/commonjs/shared/utils/bounds/index.js.map +1 -1
  55. package/lib/commonjs/shared/utils/create-provider.js +20 -1
  56. package/lib/commonjs/shared/utils/create-provider.js.map +1 -1
  57. package/lib/commonjs/shared/utils/reset-stores-for-screen.js +2 -0
  58. package/lib/commonjs/shared/utils/reset-stores-for-screen.js.map +1 -1
  59. package/lib/module/blank-stack/components/{Overlay.js → overlay.js} +7 -5
  60. package/lib/module/blank-stack/components/overlay.js.map +1 -0
  61. package/lib/module/blank-stack/components/screens.js +61 -0
  62. package/lib/module/blank-stack/components/screens.js.map +1 -0
  63. package/lib/module/blank-stack/components/stack-view.js +96 -0
  64. package/lib/module/blank-stack/components/stack-view.js.map +1 -0
  65. package/lib/module/blank-stack/index.js +1 -2
  66. package/lib/module/blank-stack/index.js.map +1 -1
  67. package/lib/module/blank-stack/navigators/{createBlankStackNavigator.js → create-blank-stack-navigator.js} +2 -2
  68. package/lib/module/blank-stack/navigators/create-blank-stack-navigator.js.map +1 -0
  69. package/lib/module/blank-stack/utils/with-stack-navigation/helpers/compose-descriptors.js +1 -11
  70. package/lib/module/blank-stack/utils/with-stack-navigation/helpers/compose-descriptors.js.map +1 -1
  71. package/lib/module/blank-stack/utils/with-stack-navigation/hooks/use-closing-route-keys.js +1 -12
  72. package/lib/module/blank-stack/utils/with-stack-navigation/hooks/use-closing-route-keys.js.map +1 -1
  73. package/lib/module/blank-stack/utils/with-stack-navigation/hooks/use-stack-navigation-state.js.map +1 -1
  74. package/lib/module/blank-stack/utils/with-stack-navigation/index.js +48 -54
  75. package/lib/module/blank-stack/utils/with-stack-navigation/index.js.map +1 -1
  76. package/lib/module/blank-stack/utils/with-stack-navigation/types.js +4 -0
  77. package/lib/module/blank-stack/utils/with-stack-navigation/types.js.map +1 -0
  78. package/lib/module/native-stack/views/NativeStackView.native.js +109 -102
  79. package/lib/module/native-stack/views/NativeStackView.native.js.map +1 -1
  80. package/lib/module/shared/components/controllers/blank-stack-lifecycle.js +66 -0
  81. package/lib/module/shared/components/controllers/blank-stack-lifecycle.js.map +1 -0
  82. package/lib/module/shared/components/controllers/native-stack-lifecycle.js +73 -0
  83. package/lib/module/shared/components/controllers/native-stack-lifecycle.js.map +1 -0
  84. package/lib/module/shared/hooks/animation/use-screen-animation.js +49 -23
  85. package/lib/module/shared/hooks/animation/use-screen-animation.js.map +1 -1
  86. package/lib/module/shared/hooks/gestures/use-build-gestures.js +11 -6
  87. package/lib/module/shared/hooks/gestures/use-build-gestures.js.map +1 -1
  88. package/lib/module/shared/hooks/gestures/use-scroll-registry.js +7 -7
  89. package/lib/module/shared/hooks/gestures/use-scroll-registry.js.map +1 -1
  90. package/lib/module/shared/providers/flags.provider.js +19 -0
  91. package/lib/module/shared/providers/flags.provider.js.map +1 -0
  92. package/lib/module/shared/providers/gestures.provider.js +31 -4
  93. package/lib/module/shared/providers/gestures.provider.js.map +1 -1
  94. package/lib/module/shared/providers/register-bounds.provider.js +72 -45
  95. package/lib/module/shared/providers/register-bounds.provider.js.map +1 -1
  96. package/lib/module/shared/providers/routes.provider.js +42 -0
  97. package/lib/module/shared/providers/routes.provider.js.map +1 -0
  98. package/lib/module/shared/providers/screen-transition.provider.js.map +1 -1
  99. package/lib/module/shared/stores/bounds.store.js +91 -47
  100. package/lib/module/shared/stores/bounds.store.js.map +1 -1
  101. package/lib/module/shared/types/state.types.js +5 -0
  102. package/lib/module/shared/types/state.types.js.map +1 -0
  103. package/lib/module/shared/utils/animation/compute-stack-progress.js +15 -0
  104. package/lib/module/shared/utils/animation/compute-stack-progress.js.map +1 -0
  105. package/lib/module/shared/utils/animation/derivations.js +1 -1
  106. package/lib/module/shared/utils/animation/start-screen-transition.js +11 -11
  107. package/lib/module/shared/utils/animation/start-screen-transition.js.map +1 -1
  108. package/lib/module/shared/utils/bounds/helpers/is-bounds-equal.js +1 -1
  109. package/lib/module/shared/utils/bounds/helpers/is-bounds-equal.js.map +1 -1
  110. package/lib/module/shared/utils/bounds/index.js +4 -5
  111. package/lib/module/shared/utils/bounds/index.js.map +1 -1
  112. package/lib/module/shared/utils/create-provider.js +20 -1
  113. package/lib/module/shared/utils/create-provider.js.map +1 -1
  114. package/lib/module/shared/utils/reset-stores-for-screen.js +2 -0
  115. package/lib/module/shared/utils/reset-stores-for-screen.js.map +1 -1
  116. package/lib/typescript/blank-stack/components/{Overlay.d.ts → overlay.d.ts} +1 -1
  117. package/lib/typescript/blank-stack/components/overlay.d.ts.map +1 -0
  118. package/lib/typescript/blank-stack/components/{Screens.d.ts → screens.d.ts} +1 -1
  119. package/lib/typescript/blank-stack/components/screens.d.ts.map +1 -0
  120. package/lib/typescript/blank-stack/components/stack-view.d.ts +3 -0
  121. package/lib/typescript/blank-stack/components/stack-view.d.ts.map +1 -0
  122. package/lib/typescript/blank-stack/index.d.ts +1 -2
  123. package/lib/typescript/blank-stack/index.d.ts.map +1 -1
  124. package/lib/typescript/blank-stack/navigators/{createBlankStackNavigator.d.ts → create-blank-stack-navigator.d.ts} +1 -1
  125. package/lib/typescript/blank-stack/navigators/create-blank-stack-navigator.d.ts.map +1 -0
  126. package/lib/typescript/blank-stack/types.d.ts +5 -39
  127. package/lib/typescript/blank-stack/types.d.ts.map +1 -1
  128. package/lib/typescript/blank-stack/utils/with-stack-navigation/helpers/compose-descriptors.d.ts.map +1 -1
  129. package/lib/typescript/blank-stack/utils/with-stack-navigation/hooks/use-closing-route-keys.d.ts.map +1 -1
  130. package/lib/typescript/blank-stack/utils/with-stack-navigation/hooks/use-stack-navigation-state.d.ts +1 -1
  131. package/lib/typescript/blank-stack/utils/with-stack-navigation/hooks/use-stack-navigation-state.d.ts.map +1 -1
  132. package/lib/typescript/blank-stack/utils/with-stack-navigation/index.d.ts +3 -5
  133. package/lib/typescript/blank-stack/utils/with-stack-navigation/index.d.ts.map +1 -1
  134. package/lib/typescript/blank-stack/utils/with-stack-navigation/{_types.d.ts → types.d.ts} +1 -1
  135. package/lib/typescript/blank-stack/utils/with-stack-navigation/types.d.ts.map +1 -0
  136. package/lib/typescript/native-stack/views/NativeStackView.native.d.ts.map +1 -1
  137. package/lib/typescript/shared/components/controllers/blank-stack-lifecycle.d.ts +8 -0
  138. package/lib/typescript/shared/components/controllers/blank-stack-lifecycle.d.ts.map +1 -0
  139. package/lib/typescript/shared/components/controllers/native-stack-lifecycle.d.ts +8 -0
  140. package/lib/typescript/shared/components/controllers/native-stack-lifecycle.d.ts.map +1 -0
  141. package/lib/typescript/shared/hooks/animation/use-screen-animation.d.ts.map +1 -1
  142. package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts +2 -2
  143. package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts.map +1 -1
  144. package/lib/typescript/shared/index.d.ts +20 -20
  145. package/lib/typescript/shared/providers/flags.provider.d.ts +10 -0
  146. package/lib/typescript/shared/providers/flags.provider.d.ts.map +1 -0
  147. package/lib/typescript/shared/providers/gestures.provider.d.ts +8 -2
  148. package/lib/typescript/shared/providers/gestures.provider.d.ts.map +1 -1
  149. package/lib/typescript/shared/providers/register-bounds.provider.d.ts.map +1 -1
  150. package/lib/typescript/shared/providers/routes.provider.d.ts +19 -0
  151. package/lib/typescript/shared/providers/routes.provider.d.ts.map +1 -0
  152. package/lib/typescript/shared/providers/screen-transition.provider.d.ts +2 -2
  153. package/lib/typescript/shared/providers/screen-transition.provider.d.ts.map +1 -1
  154. package/lib/typescript/shared/stores/bounds.store.d.ts +23 -11
  155. package/lib/typescript/shared/stores/bounds.store.d.ts.map +1 -1
  156. package/lib/typescript/shared/types/animation.types.d.ts +12 -0
  157. package/lib/typescript/shared/types/animation.types.d.ts.map +1 -1
  158. package/lib/typescript/shared/types/bounds.types.d.ts +2 -2
  159. package/lib/typescript/shared/types/bounds.types.d.ts.map +1 -1
  160. package/lib/typescript/shared/types/state.types.d.ts +3 -0
  161. package/lib/typescript/shared/types/state.types.d.ts.map +1 -0
  162. package/lib/typescript/shared/utils/animation/compute-stack-progress.d.ts +3 -0
  163. package/lib/typescript/shared/utils/animation/compute-stack-progress.d.ts.map +1 -0
  164. package/lib/typescript/shared/utils/animation/start-screen-transition.d.ts.map +1 -1
  165. package/lib/typescript/shared/utils/bounds/index.d.ts.map +1 -1
  166. package/lib/typescript/shared/utils/create-provider.d.ts +2 -2
  167. package/lib/typescript/shared/utils/create-provider.d.ts.map +1 -1
  168. package/lib/typescript/shared/utils/reset-stores-for-screen.d.ts.map +1 -1
  169. package/package.json +2 -1
  170. package/src/blank-stack/components/{Overlay.tsx → overlay.tsx} +4 -3
  171. package/src/blank-stack/components/{Screens.tsx → screens.tsx} +24 -20
  172. package/src/blank-stack/components/stack-view.tsx +115 -0
  173. package/src/blank-stack/index.ts +1 -2
  174. package/src/blank-stack/navigators/{createBlankStackNavigator.tsx → create-blank-stack-navigator.tsx} +1 -1
  175. package/src/blank-stack/types.ts +6 -31
  176. package/src/blank-stack/utils/with-stack-navigation/helpers/compose-descriptors.ts +1 -8
  177. package/src/blank-stack/utils/with-stack-navigation/hooks/use-closing-route-keys.tsx +1 -12
  178. package/src/blank-stack/utils/with-stack-navigation/hooks/use-stack-navigation-state.tsx +1 -1
  179. package/src/blank-stack/utils/with-stack-navigation/index.tsx +42 -62
  180. package/src/native-stack/views/NativeStackView.native.tsx +121 -112
  181. package/src/shared/__tests__/bounds.store.test.ts +376 -167
  182. package/src/shared/__tests__/determine-dismissal.test.ts +2 -12
  183. package/src/shared/__tests__/geometry.test.ts +1 -1
  184. package/src/shared/__tests__/gesture.velocity.test.ts +2 -10
  185. package/src/shared/components/controllers/blank-stack-lifecycle.tsx +70 -0
  186. package/src/shared/components/controllers/native-stack-lifecycle.tsx +87 -0
  187. package/src/shared/hooks/animation/use-screen-animation.tsx +61 -30
  188. package/src/shared/hooks/gestures/use-build-gestures.tsx +16 -7
  189. package/src/shared/hooks/gestures/use-scroll-registry.tsx +7 -7
  190. package/src/shared/providers/flags.provider.tsx +21 -0
  191. package/src/shared/providers/gestures.provider.tsx +34 -5
  192. package/src/shared/providers/register-bounds.provider.tsx +86 -54
  193. package/src/shared/providers/routes.provider.tsx +54 -0
  194. package/src/shared/providers/screen-transition.provider.tsx +2 -2
  195. package/src/shared/stores/bounds.store.ts +90 -54
  196. package/src/shared/types/animation.types.ts +13 -0
  197. package/src/shared/types/bounds.types.ts +2 -2
  198. package/src/shared/types/state.types.ts +2 -0
  199. package/src/shared/utils/animation/compute-stack-progress.ts +16 -0
  200. package/src/shared/utils/animation/derivations.ts +1 -1
  201. package/src/shared/utils/animation/start-screen-transition.ts +13 -10
  202. package/src/shared/utils/bounds/helpers/is-bounds-equal.ts +1 -1
  203. package/src/shared/utils/bounds/index.ts +7 -10
  204. package/src/shared/utils/create-provider.tsx +35 -1
  205. package/src/shared/utils/reset-stores-for-screen.ts +2 -0
  206. package/lib/commonjs/blank-stack/components/Overlay.js.map +0 -1
  207. package/lib/commonjs/blank-stack/components/Screens.js.map +0 -1
  208. package/lib/commonjs/blank-stack/components/StackView.js +0 -93
  209. package/lib/commonjs/blank-stack/components/StackView.js.map +0 -1
  210. package/lib/commonjs/blank-stack/navigators/createBlankStackNavigator.js.map +0 -1
  211. package/lib/commonjs/blank-stack/utils/with-stack-navigation/_types.js.map +0 -1
  212. package/lib/commonjs/shared/components/controllers/screen-lifecycle.js +0 -142
  213. package/lib/commonjs/shared/components/controllers/screen-lifecycle.js.map +0 -1
  214. package/lib/commonjs/shared/hooks/gestures/use-parent-gesture-registry.js +0 -28
  215. package/lib/commonjs/shared/hooks/gestures/use-parent-gesture-registry.js.map +0 -1
  216. package/lib/module/blank-stack/components/Overlay.js.map +0 -1
  217. package/lib/module/blank-stack/components/Screens.js +0 -58
  218. package/lib/module/blank-stack/components/Screens.js.map +0 -1
  219. package/lib/module/blank-stack/components/StackView.js +0 -88
  220. package/lib/module/blank-stack/components/StackView.js.map +0 -1
  221. package/lib/module/blank-stack/navigators/createBlankStackNavigator.js.map +0 -1
  222. package/lib/module/blank-stack/utils/with-stack-navigation/_types.js +0 -4
  223. package/lib/module/blank-stack/utils/with-stack-navigation/_types.js.map +0 -1
  224. package/lib/module/shared/components/controllers/screen-lifecycle.js +0 -136
  225. package/lib/module/shared/components/controllers/screen-lifecycle.js.map +0 -1
  226. package/lib/module/shared/hooks/gestures/use-parent-gesture-registry.js +0 -23
  227. package/lib/module/shared/hooks/gestures/use-parent-gesture-registry.js.map +0 -1
  228. package/lib/typescript/blank-stack/components/Overlay.d.ts.map +0 -1
  229. package/lib/typescript/blank-stack/components/Screens.d.ts.map +0 -1
  230. package/lib/typescript/blank-stack/components/StackView.d.ts +0 -2
  231. package/lib/typescript/blank-stack/components/StackView.d.ts.map +0 -1
  232. package/lib/typescript/blank-stack/navigators/createBlankStackNavigator.d.ts.map +0 -1
  233. package/lib/typescript/blank-stack/utils/with-stack-navigation/_types.d.ts.map +0 -1
  234. package/lib/typescript/shared/components/controllers/screen-lifecycle.d.ts +0 -12
  235. package/lib/typescript/shared/components/controllers/screen-lifecycle.d.ts.map +0 -1
  236. package/lib/typescript/shared/hooks/gestures/use-parent-gesture-registry.d.ts +0 -6
  237. package/lib/typescript/shared/hooks/gestures/use-parent-gesture-registry.d.ts.map +0 -1
  238. package/src/blank-stack/components/StackView.tsx +0 -108
  239. package/src/shared/components/controllers/screen-lifecycle.tsx +0 -154
  240. package/src/shared/hooks/gestures/use-parent-gesture-registry.tsx +0 -18
  241. /package/src/blank-stack/utils/with-stack-navigation/{_types.ts → types.ts} +0 -0
@@ -1,185 +1,394 @@
1
1
  import { beforeEach, describe, expect, it } from "bun:test";
2
- import { resolveActiveBound } from "../stores/bounds/_utils";
3
- import type { ScreenTransitionState } from "../types/animation";
4
-
5
- type Dim = {
6
- x: number;
7
- y: number;
8
- pageX: number;
9
- pageY: number;
10
- width: number;
11
- height: number;
12
- };
13
-
14
- const getDimensions = (x = 0, y = 0, w = 100, h = 100): Dim => ({
2
+ import { BoundStore, type Snapshot } from "../stores/bounds.store";
3
+
4
+ // Helper to create mock bounds
5
+ const createBounds = (
6
+ x = 0,
7
+ y = 0,
8
+ width = 100,
9
+ height = 100,
10
+ ): Snapshot["bounds"] => ({
15
11
  x,
16
12
  y,
17
13
  pageX: x,
18
14
  pageY: y,
19
- width: w,
20
- height: h,
15
+ width,
16
+ height,
21
17
  });
22
18
 
23
- const mockState = (
24
- routeKey: string,
25
- ids: string[] = [],
26
- ): ScreenTransitionState => {
27
- const bounds: Record<
28
- string,
29
- { bounds: Dim; styles: Record<string, unknown> }
30
- > = {};
31
- ids.forEach((id, i) => {
32
- bounds[id] = { bounds: getDimensions(i * 10, i * 10), styles: {} };
33
- });
34
- return {
35
- progress: 1,
36
- closing: 0,
37
- animating: 1,
38
- gesture: {
39
- x: 0,
40
- y: 0,
41
- normalizedX: 0,
42
- normalizedY: 0,
43
- isDismissing: 0,
44
- isDragging: 0,
45
- },
46
- bounds,
47
- // @ts-expect-error partial route
48
- route: { key: routeKey },
49
- };
50
- };
51
-
52
- let cache: Record<string, string>;
53
- let lastActiveByRoute: Record<string, string>;
19
+ // Reset registry before each test
54
20
  beforeEach(() => {
55
- cache = {};
56
- lastActiveByRoute = {};
21
+ globalThis.resetMutableRegistry();
22
+ });
23
+
24
+ // =============================================================================
25
+ // Unit Tests - registerSnapshot
26
+ // =============================================================================
27
+
28
+ describe("BoundStore.registerSnapshot", () => {
29
+ it("registers new tag with bounds and styles", () => {
30
+ const bounds = createBounds(10, 20, 200, 300);
31
+ const styles = { backgroundColor: "red" };
32
+
33
+ BoundStore.registerSnapshot("card", "screen-a", bounds, styles);
34
+
35
+ const snapshot = BoundStore.getSnapshot("card", "screen-a");
36
+ expect(snapshot).not.toBeNull();
37
+ expect(snapshot?.bounds).toEqual(bounds);
38
+ expect(snapshot?.styles).toEqual(styles);
39
+ });
40
+
41
+ it("adds snapshot to existing tag", () => {
42
+ const boundsA = createBounds(0, 0, 100, 100);
43
+ const boundsB = createBounds(50, 50, 150, 150);
44
+
45
+ BoundStore.registerSnapshot("card", "screen-a", boundsA);
46
+ BoundStore.registerSnapshot("card", "screen-b", boundsB);
47
+
48
+ expect(BoundStore.getSnapshot("card", "screen-a")?.bounds).toEqual(boundsA);
49
+ expect(BoundStore.getSnapshot("card", "screen-b")?.bounds).toEqual(boundsB);
50
+ });
51
+
52
+ it("stores ancestorKeys correctly", () => {
53
+ const bounds = createBounds();
54
+ const ancestors = ["stack-a", "tab-nav"];
55
+
56
+ BoundStore.registerSnapshot("card", "screen-a", bounds, {}, ancestors);
57
+
58
+ // Verify ancestor matching works
59
+ const viaAncestor = BoundStore.getSnapshot("card", "stack-a");
60
+ expect(viaAncestor).not.toBeNull();
61
+ expect(viaAncestor?.bounds).toEqual(bounds);
62
+ });
63
+
64
+ it("updates existing snapshot on re-measurement", () => {
65
+ const initialBounds = createBounds(0, 0, 100, 100);
66
+ const updatedBounds = createBounds(10, 10, 200, 200);
67
+
68
+ BoundStore.registerSnapshot("card", "screen-a", initialBounds);
69
+ BoundStore.registerSnapshot("card", "screen-a", updatedBounds);
70
+
71
+ const snapshot = BoundStore.getSnapshot("card", "screen-a");
72
+ expect(snapshot?.bounds).toEqual(updatedBounds);
73
+ });
74
+ });
75
+
76
+ // =============================================================================
77
+ // Unit Tests - setLinkSource / setLinkDestination
78
+ // =============================================================================
79
+
80
+ describe("BoundStore.setLinkSource", () => {
81
+ it("creates new tag if it does not exist", () => {
82
+ const bounds = createBounds();
83
+
84
+ BoundStore.setLinkSource("card", "screen-a", bounds);
85
+
86
+ const link = BoundStore.getActiveLink("card");
87
+ expect(link).not.toBeNull();
88
+ expect(link?.source.screenKey).toBe("screen-a");
89
+ });
90
+
91
+ it("pushes link with source and null destination", () => {
92
+ const bounds = createBounds();
93
+
94
+ BoundStore.setLinkSource("card", "screen-a", bounds);
95
+
96
+ const link = BoundStore.getActiveLink("card");
97
+ expect(link?.source.screenKey).toBe("screen-a");
98
+ expect(link?.destination).toBeNull();
99
+ });
100
+
101
+ it("multiple sources create multiple links", () => {
102
+ const boundsA = createBounds(0, 0);
103
+ const boundsB = createBounds(100, 100);
104
+
105
+ BoundStore.setLinkSource("card", "screen-a", boundsA);
106
+ BoundStore.setLinkSource("card", "screen-b", boundsB);
107
+
108
+ // Most recent link should be from screen-b
109
+ const link = BoundStore.getActiveLink("card");
110
+ expect(link?.source.screenKey).toBe("screen-b");
111
+ });
57
112
  });
58
113
 
59
- const getPairCache = (from: string, to: string) =>
60
- cache[`${from}|${to}`] ?? null;
61
- const setPairCache = (from: string, to: string, id: string) => {
62
- cache[`${from}|${to}`] = id;
63
- };
64
- const getRouteActive = (routeKey: string) =>
65
- lastActiveByRoute[routeKey] ?? null;
66
-
67
- describe("Bounds.getActiveBound - requested id priority and acceptance", () => {
68
- it("selects requested id when present only on current (opening)", () => {
69
- const A = "A-1";
70
- const B = "B-1";
71
- const current = mockState(A, ["container"]);
72
- const previous = mockState(B, ["icon"]);
73
-
74
- // Opening: fromKey is previous.route.key (B)
75
- lastActiveByRoute[B] = "container";
76
- const active = resolveActiveBound({
77
- current,
78
- previous,
79
- getPairCache,
80
- setPairCache,
81
- getRouteActive,
82
- });
83
-
84
- expect(active).toBe("container");
85
- expect(getPairCache(B, A)).toBeNull();
86
- });
87
-
88
- it("selects requested id when present only on other (closing)", () => {
89
- const A = "A-2";
90
- const B = "B-2";
91
- const current = mockState(A, ["icon"]);
92
- const next = mockState(B, ["container"]);
93
-
94
- // Closing: fromKey is current.route.key (A)
95
- lastActiveByRoute[A] = "container";
96
- const active = resolveActiveBound({
97
- current,
98
- next,
99
- getPairCache,
100
- setPairCache,
101
- getRouteActive,
102
- });
103
- expect(active).toBe("container");
104
- expect(getPairCache(A, B)).toBeNull();
114
+ describe("BoundStore.setLinkDestination", () => {
115
+ it("fills topmost link with null destination", () => {
116
+ const srcBounds = createBounds(0, 0);
117
+ const dstBounds = createBounds(100, 100);
118
+
119
+ BoundStore.setLinkSource("card", "screen-a", srcBounds);
120
+ BoundStore.setLinkDestination("card", "screen-b", dstBounds);
121
+
122
+ const link = BoundStore.getActiveLink("card");
123
+ expect(link?.source.screenKey).toBe("screen-a");
124
+ expect(link?.destination?.screenKey).toBe("screen-b");
125
+ });
126
+
127
+ it("ignores if no pending links", () => {
128
+ const bounds = createBounds();
129
+
130
+ // No source set, destination should be ignored
131
+ BoundStore.setLinkDestination("card", "screen-b", bounds);
132
+
133
+ const link = BoundStore.getActiveLink("card");
134
+ expect(link).toBeNull();
135
+ });
136
+
137
+ it("fills correct link when multiple pending", () => {
138
+ const boundsA = createBounds(0, 0);
139
+ const boundsB = createBounds(50, 50);
140
+ const boundsC = createBounds(100, 100);
141
+
142
+ // Two sources, one destination
143
+ BoundStore.setLinkSource("card", "screen-a", boundsA);
144
+ BoundStore.setLinkSource("card", "screen-b", boundsB);
145
+ BoundStore.setLinkDestination("card", "screen-c", boundsC);
146
+
147
+ // Most recent link (from screen-b) should have destination
148
+ const link = BoundStore.getActiveLink("card");
149
+ expect(link?.source.screenKey).toBe("screen-b");
150
+ expect(link?.destination?.screenKey).toBe("screen-c");
105
151
  });
106
152
  });
107
153
 
108
- describe("Bounds.getActiveBound - hint behavior (guarded writes)", () => {
109
- it("writes cache only when both sides have the id", () => {
110
- const A = "A-3";
111
- const B = "B-3";
112
- // Both have the same id measured
113
- const current = mockState(A, ["container"]);
114
- const previous = mockState(B, ["container"]);
115
-
116
- // Opening: fromKey is previous.route.key (B)
117
- lastActiveByRoute[B] = "container";
118
- const active = resolveActiveBound({
119
- current,
120
- previous,
121
- getPairCache,
122
- setPairCache,
123
- getRouteActive,
124
- });
125
- expect(active).toBe("container");
126
- expect(getPairCache(B, A)).toBe("container");
127
- });
128
-
129
- it("requested id overrides existing cache and updates it when both sides measured", () => {
130
- const A = "A-4";
131
- const B = "B-4";
132
- // Both sides have icon and container
133
- // Pre-seed a conflicting cache
134
- setPairCache(B, A, "icon");
135
-
136
- const current = mockState(A, ["icon", "container"]);
137
- const previous = mockState(B, ["icon", "container"]);
138
-
139
- // Opening: fromKey is previous.route.key (B)
140
- lastActiveByRoute[B] = "container";
141
- const active = resolveActiveBound({
142
- current,
143
- previous,
144
- getPairCache,
145
- setPairCache,
146
- getRouteActive,
147
- });
148
- expect(active).toBe("container");
149
- expect(getPairCache(B, A)).toBe("container");
154
+ // =============================================================================
155
+ // Unit Tests - getSnapshot
156
+ // =============================================================================
157
+
158
+ describe("BoundStore.getSnapshot", () => {
159
+ it("returns null for unknown tag", () => {
160
+ const result = BoundStore.getSnapshot("unknown", "screen-a");
161
+ expect(result).toBeNull();
162
+ });
163
+
164
+ it("returns bounds and styles for direct key match", () => {
165
+ const bounds = createBounds(10, 20, 300, 400);
166
+ const styles = { borderRadius: 8 };
167
+
168
+ BoundStore.registerSnapshot("card", "screen-a", bounds, styles);
169
+
170
+ const result = BoundStore.getSnapshot("card", "screen-a");
171
+ expect(result).toEqual({ bounds, styles });
172
+ });
173
+
174
+ it("returns bounds via ancestor match", () => {
175
+ const bounds = createBounds();
176
+ const ancestors = ["stack-a", "root"];
177
+
178
+ BoundStore.registerSnapshot("card", "screen-a", bounds, {}, ancestors);
179
+
180
+ // Query by ancestor key
181
+ const result = BoundStore.getSnapshot("card", "stack-a");
182
+ expect(result).not.toBeNull();
183
+ expect(result?.bounds).toEqual(bounds);
184
+ });
185
+
186
+ it("prefers direct match over ancestor match", () => {
187
+ const directBounds = createBounds(0, 0, 100, 100);
188
+ const ancestorBounds = createBounds(200, 200, 50, 50);
189
+
190
+ // Register with ancestor that matches another screen's key
191
+ BoundStore.registerSnapshot("card", "screen-a", ancestorBounds, {}, [
192
+ "stack-a",
193
+ ]);
194
+ BoundStore.registerSnapshot("card", "stack-a", directBounds);
195
+
196
+ // Direct match should win
197
+ const result = BoundStore.getSnapshot("card", "stack-a");
198
+ expect(result?.bounds).toEqual(directBounds);
150
199
  });
151
200
  });
152
201
 
153
- describe("Bounds.getActiveBound - set intersection and fallbacks", () => {
154
- it("falls back to intersection when no request or cache", () => {
155
- const A = "A-5";
156
- const B = "B-5";
157
- const current = mockState(A, ["alpha", "beta"]);
158
- const previous = mockState(B, ["beta", "gamma"]);
159
-
160
- const active = resolveActiveBound({
161
- current,
162
- previous,
163
- getPairCache,
164
- setPairCache,
165
- getRouteActive,
166
- });
167
- expect(active).toBe("beta");
168
- });
169
-
170
- it("when other has a single bound, selects it (no request/cache)", () => {
171
- const A = "A-6";
172
- const B = "B-6";
173
- const current = mockState(A, ["alpha"]);
174
- const previous = mockState(B, ["only"]);
175
-
176
- const active = resolveActiveBound({
177
- current,
178
- previous,
179
- getPairCache,
180
- setPairCache,
181
- getRouteActive,
182
- });
183
- expect(active).toBe("only");
202
+ // =============================================================================
203
+ // Unit Tests - getActiveLink
204
+ // =============================================================================
205
+
206
+ describe("BoundStore.getActiveLink", () => {
207
+ it("returns null for unknown tag", () => {
208
+ const result = BoundStore.getActiveLink("unknown");
209
+ expect(result).toBeNull();
210
+ });
211
+
212
+ it("returns null for empty linkStack", () => {
213
+ // Register snapshot but no links
214
+ BoundStore.registerSnapshot("card", "screen-a", createBounds());
215
+
216
+ const result = BoundStore.getActiveLink("card");
217
+ expect(result).toBeNull();
218
+ });
219
+
220
+ it("returns most recent link when no screenKey provided", () => {
221
+ BoundStore.setLinkSource("card", "screen-a", createBounds());
222
+ BoundStore.setLinkDestination("card", "screen-b", createBounds());
223
+ BoundStore.setLinkSource("card", "screen-b", createBounds());
224
+ BoundStore.setLinkDestination("card", "screen-c", createBounds());
225
+
226
+ const link = BoundStore.getActiveLink("card");
227
+ expect(link?.source.screenKey).toBe("screen-b");
228
+ expect(link?.destination?.screenKey).toBe("screen-c");
229
+ });
230
+
231
+ it("infers isClosing when screenKey matches source", () => {
232
+ BoundStore.setLinkSource("card", "screen-a", createBounds());
233
+ BoundStore.setLinkDestination("card", "screen-b", createBounds());
234
+
235
+ // Query from source screen = closing (going back)
236
+ const linkFromSource = BoundStore.getActiveLink("card", "screen-a");
237
+ expect(linkFromSource?.source.screenKey).toBe("screen-a");
238
+
239
+ // Query from destination screen = opening
240
+ const linkFromDest = BoundStore.getActiveLink("card", "screen-b");
241
+ expect(linkFromDest?.destination?.screenKey).toBe("screen-b");
242
+ });
243
+
244
+ it("ancestor matching works in link lookup", () => {
245
+ const ancestors = ["stack-a"];
246
+
247
+ BoundStore.setLinkSource("card", "screen-a", createBounds(), {}, ancestors);
248
+ BoundStore.setLinkDestination("card", "screen-b", createBounds());
249
+
250
+ // Query by ancestor key (matches source)
251
+ const link = BoundStore.getActiveLink("card", "stack-a");
252
+ expect(link).not.toBeNull();
253
+ expect(link?.source.screenKey).toBe("screen-a");
254
+ });
255
+
256
+ it("returns null when screenKey does not match any link", () => {
257
+ BoundStore.setLinkSource("card", "screen-a", createBounds());
258
+ BoundStore.setLinkDestination("card", "screen-b", createBounds());
259
+
260
+ const link = BoundStore.getActiveLink("card", "screen-x");
261
+ expect(link).toBeNull();
262
+ });
263
+ });
264
+
265
+ // =============================================================================
266
+ // Scenario Tests - Navigation Flows
267
+ // =============================================================================
268
+
269
+ describe("Scenario: Simple push/pop navigation", () => {
270
+ it("captures source on press, destination on layout, reverses on pop", () => {
271
+ const srcBounds = createBounds(50, 100, 200, 200);
272
+ const dstBounds = createBounds(0, 0, 400, 400);
273
+
274
+ // 1. User presses card on Screen A (source captured)
275
+ BoundStore.setLinkSource("card", "screen-a", srcBounds);
276
+
277
+ // 2. Screen B mounts, measures card (destination captured)
278
+ BoundStore.setLinkDestination("card", "screen-b", dstBounds);
279
+
280
+ // Verify link is complete - query from destination (opening)
281
+ const openingLink = BoundStore.getActiveLink("card", "screen-b");
282
+ expect(openingLink?.source.bounds).toEqual(srcBounds);
283
+ expect(openingLink?.destination?.bounds).toEqual(dstBounds);
284
+
285
+ // 3. Query from source (closing - going back)
286
+ const closingLink = BoundStore.getActiveLink("card", "screen-a");
287
+ expect(closingLink?.source.screenKey).toBe("screen-a");
288
+ expect(closingLink?.destination?.screenKey).toBe("screen-b");
289
+ });
290
+ });
291
+
292
+ describe("Scenario: Multiple bounds, only one matches", () => {
293
+ it("establishes link only for matching bound", () => {
294
+ // Screen A has header, card, footer
295
+ BoundStore.registerSnapshot("header", "screen-a", createBounds(0, 0));
296
+ BoundStore.registerSnapshot("card", "screen-a", createBounds(0, 100));
297
+ BoundStore.registerSnapshot("footer", "screen-a", createBounds(0, 500));
298
+
299
+ // Only card triggers navigation
300
+ BoundStore.setLinkSource("card", "screen-a", createBounds(0, 100));
301
+
302
+ // Screen B only has card
303
+ BoundStore.setLinkDestination("card", "screen-b", createBounds(0, 0));
304
+
305
+ // Card link exists
306
+ expect(BoundStore.getActiveLink("card")).not.toBeNull();
307
+
308
+ // Header and footer have no links (only snapshots)
309
+ expect(BoundStore.getActiveLink("header")).toBeNull();
310
+ expect(BoundStore.getActiveLink("footer")).toBeNull();
311
+ });
312
+ });
313
+
314
+ describe("Scenario: Nested navigator with ancestor keys", () => {
315
+ it("supports cross-stack bounds via ancestor matching", () => {
316
+ // Tab Navigator structure:
317
+ // - Stack A (key: "stack-a") -> Screen A1 (key: "a1", ancestors: ["stack-a"])
318
+ // - Stack B (key: "stack-b") -> Screen B1 (key: "b1", ancestors: ["stack-b"])
319
+
320
+ const boundsA = createBounds(10, 10, 80, 80);
321
+ const boundsB = createBounds(20, 20, 100, 100);
322
+
323
+ // Register snapshot in Stack A
324
+ BoundStore.registerSnapshot("profile", "a1", boundsA, {}, ["stack-a"]);
325
+
326
+ // Register snapshot in Stack B
327
+ BoundStore.registerSnapshot("profile", "b1", boundsB, {}, ["stack-b"]);
328
+
329
+ // Query by stack key should return correct bounds
330
+ const fromStackA = BoundStore.getSnapshot("profile", "stack-a");
331
+ expect(fromStackA?.bounds).toEqual(boundsA);
332
+
333
+ const fromStackB = BoundStore.getSnapshot("profile", "stack-b");
334
+ expect(fromStackB?.bounds).toEqual(boundsB);
335
+ });
336
+
337
+ it("getActiveLink respects ancestor chain", () => {
338
+ // Navigation from Stack A to detail screen
339
+ BoundStore.setLinkSource("profile", "a1", createBounds(10, 10), {}, [
340
+ "stack-a",
341
+ ]);
342
+ BoundStore.setLinkDestination("profile", "detail", createBounds(0, 0));
343
+
344
+ // Query by ancestor should find the link
345
+ const link = BoundStore.getActiveLink("profile", "stack-a");
346
+ expect(link?.source.screenKey).toBe("a1");
347
+ });
348
+ });
349
+
350
+ describe("Scenario: Rapid navigation A → B → C → pop → pop", () => {
351
+ it("link stack grows and getActiveLink finds correct link for each screen", () => {
352
+ // A → B
353
+ BoundStore.setLinkSource("card", "screen-a", createBounds(0, 0));
354
+ BoundStore.setLinkDestination("card", "screen-b", createBounds(100, 100));
355
+
356
+ // B → C
357
+ BoundStore.setLinkSource("card", "screen-b", createBounds(100, 100));
358
+ BoundStore.setLinkDestination("card", "screen-c", createBounds(200, 200));
359
+
360
+ // Most recent link is B → C
361
+ const latest = BoundStore.getActiveLink("card");
362
+ expect(latest?.source.screenKey).toBe("screen-b");
363
+ expect(latest?.destination?.screenKey).toBe("screen-c");
364
+
365
+ // Query from C (destination of B→C) = opening
366
+ const fromC = BoundStore.getActiveLink("card", "screen-c");
367
+ expect(fromC?.destination?.screenKey).toBe("screen-c");
368
+
369
+ // Query from B - B is source of B→C link, so isClosing=true
370
+ const fromB = BoundStore.getActiveLink("card", "screen-b");
371
+ expect(fromB?.source.screenKey).toBe("screen-b");
372
+ });
373
+ });
374
+
375
+ describe("Scenario: Global bounds (fullscreen target)", () => {
376
+ it("getActiveLink with no screenKey returns most recent for fullscreen", () => {
377
+ // Source exists, destination will be fullscreen (no specific screenKey needed)
378
+ BoundStore.setLinkSource(
379
+ "image",
380
+ "gallery",
381
+ createBounds(50, 50, 100, 100),
382
+ );
383
+ BoundStore.setLinkDestination(
384
+ "image",
385
+ "fullscreen-viewer",
386
+ createBounds(0, 0, 400, 800),
387
+ );
388
+
389
+ // Fullscreen target can get link without knowing screenKey
390
+ const link = BoundStore.getActiveLink("image");
391
+ expect(link).not.toBeNull();
392
+ expect(link?.destination?.screenKey).toBe("fullscreen-viewer");
184
393
  });
185
394
  });
@@ -1,15 +1,5 @@
1
- import { describe, expect, it, mock } from "bun:test";
2
-
3
- mock.module("react-native", () => ({}));
4
- mock.module("react-native-gesture-handler", () => ({}));
5
- mock.module("react-native-reanimated", () => ({
6
- clamp: (value: number, lower: number, upper: number) =>
7
- Math.min(Math.max(value, lower), upper),
8
- }));
9
-
10
- const { determineDismissal } = await import(
11
- "../utils/gesture/determine-dismissal"
12
- );
1
+ import { describe, expect, it } from "bun:test";
2
+ import { determineDismissal } from "../utils/gesture/determine-dismissal";
13
3
 
14
4
  describe("determineDismissal", () => {
15
5
  const dimensions = { width: 320, height: 640 };
@@ -2,7 +2,7 @@ import { describe, expect, it } from "bun:test";
2
2
  import {
3
3
  computeContentTransformGeometry,
4
4
  computeRelativeGeometry,
5
- } from "../utils/bounds/_utils/geometry";
5
+ } from "../utils/bounds/helpers/geometry";
6
6
 
7
7
  describe("computeRelativeGeometry", () => {
8
8
  it("calculates correct relative geometry when entering", () => {
@@ -1,13 +1,5 @@
1
- import { describe, expect, it, mock } from "bun:test";
2
-
3
- mock.module("react-native", () => ({}));
4
- mock.module("react-native-gesture-handler", () => ({}));
5
- mock.module("react-native-reanimated", () => ({
6
- clamp: (value: number, lower: number, upper: number) =>
7
- Math.min(Math.max(value, lower), upper),
8
- }));
9
-
10
- const { velocity } = await import("../utils/gesture/velocity");
1
+ import { describe, expect, it } from "bun:test";
2
+ import { velocity } from "../utils/gesture/velocity";
11
3
 
12
4
  type Directions = {
13
5
  horizontal: boolean;