react-native-screen-transitions 3.3.0-beta.2 → 3.3.0-beta.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 (119) hide show
  1. package/README.md +95 -31
  2. package/lib/commonjs/shared/animation/snap-to.js +17 -10
  3. package/lib/commonjs/shared/animation/snap-to.js.map +1 -1
  4. package/lib/commonjs/shared/components/create-transition-aware-component.js +20 -18
  5. package/lib/commonjs/shared/components/create-transition-aware-component.js.map +1 -1
  6. package/lib/commonjs/shared/components/screen-container.js +68 -9
  7. package/lib/commonjs/shared/components/screen-container.js.map +1 -1
  8. package/lib/commonjs/shared/constants.js +8 -1
  9. package/lib/commonjs/shared/constants.js.map +1 -1
  10. package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js +49 -39
  11. package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js.map +1 -1
  12. package/lib/commonjs/shared/hooks/gestures/use-screen-gesture-handlers.js +110 -61
  13. package/lib/commonjs/shared/hooks/gestures/use-screen-gesture-handlers.js.map +1 -1
  14. package/lib/commonjs/shared/hooks/gestures/use-scroll-registry.js +67 -70
  15. package/lib/commonjs/shared/hooks/gestures/use-scroll-registry.js.map +1 -1
  16. package/lib/commonjs/shared/providers/gestures.provider.js +113 -25
  17. package/lib/commonjs/shared/providers/gestures.provider.js.map +1 -1
  18. package/lib/commonjs/shared/types/ownership.types.js +71 -0
  19. package/lib/commonjs/shared/types/ownership.types.js.map +1 -0
  20. package/lib/commonjs/shared/utils/gesture/check-gesture-activation.js +72 -128
  21. package/lib/commonjs/shared/utils/gesture/check-gesture-activation.js.map +1 -1
  22. package/lib/commonjs/shared/utils/gesture/compute-claimed-directions.js +81 -0
  23. package/lib/commonjs/shared/utils/gesture/compute-claimed-directions.js.map +1 -0
  24. package/lib/commonjs/shared/utils/gesture/determine-snap-target.js +1 -1
  25. package/lib/commonjs/shared/utils/gesture/determine-snap-target.js.map +1 -1
  26. package/lib/commonjs/shared/utils/gesture/find-collapse-target.js +48 -0
  27. package/lib/commonjs/shared/utils/gesture/find-collapse-target.js.map +1 -0
  28. package/lib/commonjs/shared/utils/gesture/resolve-ownership.js +87 -0
  29. package/lib/commonjs/shared/utils/gesture/resolve-ownership.js.map +1 -0
  30. package/lib/commonjs/shared/utils/gesture/velocity.js +16 -5
  31. package/lib/commonjs/shared/utils/gesture/velocity.js.map +1 -1
  32. package/lib/module/shared/animation/snap-to.js +16 -10
  33. package/lib/module/shared/animation/snap-to.js.map +1 -1
  34. package/lib/module/shared/components/create-transition-aware-component.js +20 -18
  35. package/lib/module/shared/components/create-transition-aware-component.js.map +1 -1
  36. package/lib/module/shared/components/screen-container.js +68 -10
  37. package/lib/module/shared/components/screen-container.js.map +1 -1
  38. package/lib/module/shared/constants.js +7 -0
  39. package/lib/module/shared/constants.js.map +1 -1
  40. package/lib/module/shared/hooks/gestures/use-build-gestures.js +49 -39
  41. package/lib/module/shared/hooks/gestures/use-build-gestures.js.map +1 -1
  42. package/lib/module/shared/hooks/gestures/use-screen-gesture-handlers.js +112 -63
  43. package/lib/module/shared/hooks/gestures/use-screen-gesture-handlers.js.map +1 -1
  44. package/lib/module/shared/hooks/gestures/use-scroll-registry.js +68 -70
  45. package/lib/module/shared/hooks/gestures/use-scroll-registry.js.map +1 -1
  46. package/lib/module/shared/providers/gestures.provider.js +113 -25
  47. package/lib/module/shared/providers/gestures.provider.js.map +1 -1
  48. package/lib/module/shared/types/ownership.types.js +67 -0
  49. package/lib/module/shared/types/ownership.types.js.map +1 -0
  50. package/lib/module/shared/utils/gesture/check-gesture-activation.js +70 -126
  51. package/lib/module/shared/utils/gesture/check-gesture-activation.js.map +1 -1
  52. package/lib/module/shared/utils/gesture/compute-claimed-directions.js +77 -0
  53. package/lib/module/shared/utils/gesture/compute-claimed-directions.js.map +1 -0
  54. package/lib/module/shared/utils/gesture/determine-snap-target.js +1 -1
  55. package/lib/module/shared/utils/gesture/determine-snap-target.js.map +1 -1
  56. package/lib/module/shared/utils/gesture/find-collapse-target.js +44 -0
  57. package/lib/module/shared/utils/gesture/find-collapse-target.js.map +1 -0
  58. package/lib/module/shared/utils/gesture/resolve-ownership.js +83 -0
  59. package/lib/module/shared/utils/gesture/resolve-ownership.js.map +1 -0
  60. package/lib/module/shared/utils/gesture/velocity.js +16 -5
  61. package/lib/module/shared/utils/gesture/velocity.js.map +1 -1
  62. package/lib/typescript/shared/animation/snap-to.d.ts.map +1 -1
  63. package/lib/typescript/shared/components/create-transition-aware-component.d.ts.map +1 -1
  64. package/lib/typescript/shared/components/screen-container.d.ts.map +1 -1
  65. package/lib/typescript/shared/constants.d.ts +6 -0
  66. package/lib/typescript/shared/constants.d.ts.map +1 -1
  67. package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts +15 -3
  68. package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts.map +1 -1
  69. package/lib/typescript/shared/hooks/gestures/use-screen-gesture-handlers.d.ts +52 -2
  70. package/lib/typescript/shared/hooks/gestures/use-screen-gesture-handlers.d.ts.map +1 -1
  71. package/lib/typescript/shared/hooks/gestures/use-scroll-registry.d.ts +11 -6
  72. package/lib/typescript/shared/hooks/gestures/use-scroll-registry.d.ts.map +1 -1
  73. package/lib/typescript/shared/hooks/use-backdrop-pointer-events.d.ts +1 -1
  74. package/lib/typescript/shared/hooks/use-backdrop-pointer-events.d.ts.map +1 -1
  75. package/lib/typescript/shared/providers/gestures.provider.d.ts +28 -3
  76. package/lib/typescript/shared/providers/gestures.provider.d.ts.map +1 -1
  77. package/lib/typescript/shared/types/ownership.types.d.ts +52 -0
  78. package/lib/typescript/shared/types/ownership.types.d.ts.map +1 -0
  79. package/lib/typescript/shared/types/screen.types.d.ts +22 -1
  80. package/lib/typescript/shared/types/screen.types.d.ts.map +1 -1
  81. package/lib/typescript/shared/utils/gesture/check-gesture-activation.d.ts +23 -19
  82. package/lib/typescript/shared/utils/gesture/check-gesture-activation.d.ts.map +1 -1
  83. package/lib/typescript/shared/utils/gesture/compute-claimed-directions.d.ts +23 -0
  84. package/lib/typescript/shared/utils/gesture/compute-claimed-directions.d.ts.map +1 -0
  85. package/lib/typescript/shared/utils/gesture/determine-snap-target.d.ts +5 -1
  86. package/lib/typescript/shared/utils/gesture/determine-snap-target.d.ts.map +1 -1
  87. package/lib/typescript/shared/utils/gesture/find-collapse-target.d.ts +17 -0
  88. package/lib/typescript/shared/utils/gesture/find-collapse-target.d.ts.map +1 -0
  89. package/lib/typescript/shared/utils/gesture/resolve-ownership.d.ts +36 -0
  90. package/lib/typescript/shared/utils/gesture/resolve-ownership.d.ts.map +1 -0
  91. package/lib/typescript/shared/utils/gesture/velocity.d.ts.map +1 -1
  92. package/package.json +121 -120
  93. package/src/shared/animation/snap-to.ts +17 -11
  94. package/src/shared/components/create-transition-aware-component.tsx +28 -25
  95. package/src/shared/components/screen-container.tsx +79 -12
  96. package/src/shared/constants.ts +7 -0
  97. package/src/shared/hooks/gestures/use-build-gestures.tsx +80 -44
  98. package/src/shared/hooks/gestures/use-screen-gesture-handlers.ts +147 -71
  99. package/src/shared/hooks/gestures/use-scroll-registry.tsx +94 -86
  100. package/src/shared/hooks/use-backdrop-pointer-events.ts +1 -1
  101. package/src/shared/providers/gestures.provider.tsx +168 -25
  102. package/src/shared/types/ownership.types.ts +77 -0
  103. package/src/shared/types/screen.types.ts +24 -1
  104. package/src/shared/utils/gesture/check-gesture-activation.ts +82 -116
  105. package/src/shared/utils/gesture/compute-claimed-directions.ts +93 -0
  106. package/src/shared/utils/gesture/determine-snap-target.ts +6 -2
  107. package/src/shared/utils/gesture/find-collapse-target.ts +42 -0
  108. package/src/shared/utils/gesture/resolve-ownership.ts +110 -0
  109. package/src/shared/utils/gesture/velocity.ts +16 -6
  110. package/src/shared/__tests__/bounds.store.test.ts +0 -394
  111. package/src/shared/__tests__/derivations.test.ts +0 -156
  112. package/src/shared/__tests__/determine-dismissal.test.ts +0 -111
  113. package/src/shared/__tests__/determine-snap-target.test.ts +0 -268
  114. package/src/shared/__tests__/geometry.test.ts +0 -130
  115. package/src/shared/__tests__/gesture-activation.test.ts +0 -471
  116. package/src/shared/__tests__/gesture.velocity.test.ts +0 -131
  117. package/src/shared/__tests__/history.store.test.ts +0 -550
  118. package/src/shared/__tests__/sync-routes-with-removed.test.ts +0 -137
  119. package/src/shared/__tests__/validate-snap-points.test.ts +0 -125
@@ -1,394 +0,0 @@
1
- import { beforeEach, describe, expect, it } from "bun:test";
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"] => ({
11
- x,
12
- y,
13
- pageX: x,
14
- pageY: y,
15
- width,
16
- height,
17
- });
18
-
19
- // Reset registry before each test
20
- beforeEach(() => {
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
- });
112
- });
113
-
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");
151
- });
152
- });
153
-
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);
199
- });
200
- });
201
-
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");
393
- });
394
- });
@@ -1,156 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import type { ScreenTransitionState } from "../types/animation.types";
3
- import { derivations } from "../utils/animation/derivations";
4
-
5
- const createMockState = (
6
- overrides: Partial<ScreenTransitionState> = {},
7
- ): ScreenTransitionState => ({
8
- progress: 1,
9
- closing: 0,
10
- animating: 0,
11
- entering: 1,
12
- gesture: {
13
- isDragging: 0,
14
- x: 0,
15
- y: 0,
16
- normalizedX: 0,
17
- normalizedY: 0,
18
- isDismissing: 0,
19
- direction: null,
20
- },
21
- route: { key: "test-route", name: "TestScreen" } as any,
22
- ...overrides,
23
- });
24
-
25
- describe("derivations", () => {
26
- describe("progress", () => {
27
- it("returns current progress when no next screen", () => {
28
- const result = derivations({
29
- current: createMockState({ progress: 0.5 }),
30
- });
31
- expect(result.progress).toBe(0.5);
32
- });
33
-
34
- it("combines current + next progress (0-2 range)", () => {
35
- const result = derivations({
36
- current: createMockState({ progress: 1 }),
37
- next: createMockState({ progress: 0.5 }),
38
- });
39
- expect(result.progress).toBe(1.5);
40
- });
41
-
42
- it("returns 2 when both screens fully transitioned", () => {
43
- const result = derivations({
44
- current: createMockState({ progress: 1 }),
45
- next: createMockState({ progress: 1 }),
46
- });
47
- expect(result.progress).toBe(2);
48
- });
49
- });
50
-
51
- describe("focused", () => {
52
- it("returns true when no next screen", () => {
53
- const result = derivations({
54
- current: createMockState(),
55
- });
56
- expect(result.focused).toBe(true);
57
- });
58
-
59
- it("returns false when next screen exists", () => {
60
- const result = derivations({
61
- current: createMockState(),
62
- next: createMockState(),
63
- });
64
- expect(result.focused).toBe(false);
65
- });
66
- });
67
-
68
- describe("active", () => {
69
- it("returns current when focused (no next)", () => {
70
- const current = createMockState({ progress: 0.3 });
71
- const result = derivations({ current });
72
- expect(result.active).toBe(current);
73
- });
74
-
75
- it("returns next when not focused", () => {
76
- const current = createMockState({ progress: 1 });
77
- const next = createMockState({ progress: 0.5 });
78
- const result = derivations({ current, next });
79
- expect(result.active).toBe(next);
80
- });
81
- });
82
-
83
- describe("isActiveTransitioning", () => {
84
- it("returns true when active screen is dragging", () => {
85
- const result = derivations({
86
- current: createMockState({
87
- gesture: {
88
- isDragging: 1,
89
- x: 0,
90
- y: 0,
91
- normalizedX: 0,
92
- normalizedY: 0,
93
- isDismissing: 0,
94
- direction: null,
95
- },
96
- }),
97
- });
98
- expect(result.isActiveTransitioning).toBe(true);
99
- });
100
-
101
- it("returns true when active screen is animating", () => {
102
- const result = derivations({
103
- current: createMockState({ animating: 1 }),
104
- });
105
- expect(result.isActiveTransitioning).toBe(true);
106
- });
107
-
108
- it("returns false when not dragging or animating", () => {
109
- const result = derivations({
110
- current: createMockState({ animating: 0 }),
111
- });
112
- expect(result.isActiveTransitioning).toBe(false);
113
- });
114
- });
115
-
116
- describe("isDismissing", () => {
117
- it("returns true when gesture isDismissing", () => {
118
- const result = derivations({
119
- current: createMockState({
120
- gesture: {
121
- isDragging: 0,
122
- x: 0,
123
- y: 0,
124
- normalizedX: 0,
125
- normalizedY: 0,
126
- isDismissing: 1,
127
- direction: null,
128
- },
129
- }),
130
- });
131
- expect(result.isDismissing).toBe(true);
132
- });
133
-
134
- it("returns true when closing flag is set", () => {
135
- const result = derivations({
136
- current: createMockState({ closing: 1 }),
137
- });
138
- expect(result.isDismissing).toBe(true);
139
- });
140
-
141
- it("returns false when not dismissing or closing", () => {
142
- const result = derivations({
143
- current: createMockState(),
144
- });
145
- expect(result.isDismissing).toBe(false);
146
- });
147
-
148
- it("checks active screen (next) for dismissing state", () => {
149
- const result = derivations({
150
- current: createMockState(),
151
- next: createMockState({ closing: 1 }),
152
- });
153
- expect(result.isDismissing).toBe(true);
154
- });
155
- });
156
- });
@@ -1,111 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { determineDismissal } from "../utils/gesture/determine-dismissal";
3
-
4
- describe("determineDismissal", () => {
5
- const dimensions = { width: 320, height: 640 };
6
-
7
- it("dismisses when horizontal translation exceeds the threshold", () => {
8
- const { shouldDismiss } = determineDismissal({
9
- event: {
10
- translationX: 170,
11
- translationY: 0,
12
- velocityX: 0,
13
- velocityY: 0,
14
- },
15
- directions: {
16
- vertical: false,
17
- verticalInverted: false,
18
- horizontal: true,
19
- horizontalInverted: false,
20
- },
21
- dimensions,
22
- gestureVelocityImpact: 0.3,
23
- });
24
-
25
- expect(shouldDismiss).toBe(true);
26
- });
27
-
28
- it("ignores movement in disallowed directions", () => {
29
- const { shouldDismiss } = determineDismissal({
30
- event: {
31
- translationX: 200,
32
- translationY: 0,
33
- velocityX: 0,
34
- velocityY: 0,
35
- },
36
- directions: {
37
- vertical: true,
38
- verticalInverted: false,
39
- horizontal: false,
40
- horizontalInverted: false,
41
- },
42
- dimensions,
43
- gestureVelocityImpact: 0.3,
44
- });
45
-
46
- expect(shouldDismiss).toBe(false);
47
- });
48
-
49
- it("dismisses vertical gestures when velocity pushes the projection past the threshold", () => {
50
- const { shouldDismiss } = determineDismissal({
51
- event: {
52
- translationX: 0,
53
- translationY: 40,
54
- velocityX: 0,
55
- velocityY: 1800,
56
- },
57
- directions: {
58
- vertical: true,
59
- verticalInverted: false,
60
- horizontal: false,
61
- horizontalInverted: false,
62
- },
63
- dimensions,
64
- gestureVelocityImpact: 0.3,
65
- });
66
-
67
- expect(shouldDismiss).toBe(true);
68
- });
69
-
70
- it("respects inverted horizontal directions", () => {
71
- const { shouldDismiss } = determineDismissal({
72
- event: {
73
- translationX: -160,
74
- translationY: 0,
75
- velocityX: -700,
76
- velocityY: 0,
77
- },
78
- directions: {
79
- vertical: false,
80
- verticalInverted: false,
81
- horizontal: false,
82
- horizontalInverted: true,
83
- },
84
- dimensions,
85
- gestureVelocityImpact: 0.25,
86
- });
87
-
88
- expect(shouldDismiss).toBe(true);
89
- });
90
-
91
- it("returns false when movement never exceeds the composite threshold", () => {
92
- const { shouldDismiss } = determineDismissal({
93
- event: {
94
- translationX: 30,
95
- translationY: 0,
96
- velocityX: 100,
97
- velocityY: 0,
98
- },
99
- directions: {
100
- vertical: false,
101
- verticalInverted: false,
102
- horizontal: true,
103
- horizontalInverted: false,
104
- },
105
- dimensions,
106
- gestureVelocityImpact: 0.2,
107
- });
108
-
109
- expect(shouldDismiss).toBe(false);
110
- });
111
- });