react-native-screen-transitions 3.2.0-beta.2 → 3.2.0

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 (239) hide show
  1. package/README.md +346 -620
  2. package/lib/commonjs/blank-stack/components/stack-view.js +0 -2
  3. package/lib/commonjs/blank-stack/components/stack-view.js.map +1 -1
  4. package/lib/commonjs/blank-stack/components/stack-view.native.js +0 -2
  5. package/lib/commonjs/blank-stack/components/stack-view.native.js.map +1 -1
  6. package/lib/commonjs/component-stack/components/stack-view.js +16 -20
  7. package/lib/commonjs/component-stack/components/stack-view.js.map +1 -1
  8. package/lib/commonjs/native-stack/views/NativeStackView.native.js +3 -3
  9. package/lib/commonjs/native-stack/views/NativeStackView.native.js.map +1 -1
  10. package/lib/commonjs/shared/components/overlay/variations/float-overlay.js +1 -6
  11. package/lib/commonjs/shared/components/overlay/variations/float-overlay.js.map +1 -1
  12. package/lib/commonjs/shared/components/overlay/variations/overlay-host.js +16 -64
  13. package/lib/commonjs/shared/components/overlay/variations/overlay-host.js.map +1 -1
  14. package/lib/commonjs/shared/components/overlay/variations/screen-overlay.js +5 -8
  15. package/lib/commonjs/shared/components/overlay/variations/screen-overlay.js.map +1 -1
  16. package/lib/commonjs/shared/components/screen-lifecycle.js +29 -0
  17. package/lib/commonjs/shared/components/screen-lifecycle.js.map +1 -0
  18. package/lib/commonjs/shared/constants.js +4 -2
  19. package/lib/commonjs/shared/constants.js.map +1 -1
  20. package/lib/commonjs/shared/hooks/animation/use-screen-animation.js +3 -0
  21. package/lib/commonjs/shared/hooks/animation/use-screen-animation.js.map +1 -1
  22. package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js.map +1 -1
  23. package/lib/commonjs/shared/hooks/lifecycle/use-close-transition.js +127 -0
  24. package/lib/commonjs/shared/hooks/lifecycle/use-close-transition.js.map +1 -0
  25. package/lib/commonjs/shared/hooks/lifecycle/use-open-transition.js +35 -0
  26. package/lib/commonjs/shared/hooks/lifecycle/use-open-transition.js.map +1 -0
  27. package/lib/commonjs/shared/hooks/lifecycle/use-screen-events.js +58 -0
  28. package/lib/commonjs/shared/hooks/lifecycle/use-screen-events.js.map +1 -0
  29. package/lib/commonjs/shared/hooks/navigation/use-history.js +24 -0
  30. package/lib/commonjs/shared/hooks/navigation/use-history.js.map +1 -0
  31. package/lib/commonjs/shared/hooks/navigation/use-screen-state.js +45 -0
  32. package/lib/commonjs/shared/hooks/navigation/use-screen-state.js.map +1 -0
  33. package/lib/commonjs/shared/hooks/use-stable-callback-value.js +0 -2
  34. package/lib/commonjs/shared/hooks/use-stable-callback-value.js.map +1 -1
  35. package/lib/commonjs/shared/index.js +14 -0
  36. package/lib/commonjs/shared/index.js.map +1 -1
  37. package/lib/commonjs/shared/providers/screen/keys.provider.js +0 -6
  38. package/lib/commonjs/shared/providers/screen/keys.provider.js.map +1 -1
  39. package/lib/commonjs/shared/providers/screen/screen-composer.js +9 -7
  40. package/lib/commonjs/shared/providers/screen/screen-composer.js.map +1 -1
  41. package/lib/commonjs/shared/providers/screen/styles.provider.js +1 -1
  42. package/lib/commonjs/shared/providers/screen/styles.provider.js.map +1 -1
  43. package/lib/commonjs/shared/providers/stack/direct.provider.js +9 -0
  44. package/lib/commonjs/shared/providers/stack/direct.provider.js.map +1 -1
  45. package/lib/commonjs/shared/providers/stack/managed.provider.js +9 -0
  46. package/lib/commonjs/shared/providers/stack/managed.provider.js.map +1 -1
  47. package/lib/commonjs/shared/stores/animation.store.js +3 -13
  48. package/lib/commonjs/shared/stores/animation.store.js.map +1 -1
  49. package/lib/commonjs/shared/stores/history.store.js +185 -0
  50. package/lib/commonjs/shared/stores/history.store.js.map +1 -0
  51. package/lib/commonjs/shared/types/index.js +3 -3
  52. package/lib/commonjs/shared/types/index.js.map +1 -1
  53. package/lib/commonjs/shared/types/stack.types.js +4 -0
  54. package/lib/commonjs/shared/types/stack.types.js.map +1 -1
  55. package/lib/commonjs/shared/utils/animation/start-screen-transition.js +10 -6
  56. package/lib/commonjs/shared/utils/animation/start-screen-transition.js.map +1 -1
  57. package/lib/commonjs/shared/utils/bounds/index.js +19 -4
  58. package/lib/commonjs/shared/utils/bounds/index.js.map +1 -1
  59. package/lib/module/blank-stack/components/stack-view.js +0 -2
  60. package/lib/module/blank-stack/components/stack-view.js.map +1 -1
  61. package/lib/module/blank-stack/components/stack-view.native.js +0 -2
  62. package/lib/module/blank-stack/components/stack-view.native.js.map +1 -1
  63. package/lib/module/component-stack/components/stack-view.js +16 -20
  64. package/lib/module/component-stack/components/stack-view.js.map +1 -1
  65. package/lib/module/native-stack/views/NativeStackView.native.js +3 -3
  66. package/lib/module/native-stack/views/NativeStackView.native.js.map +1 -1
  67. package/lib/module/shared/components/overlay/variations/float-overlay.js +1 -6
  68. package/lib/module/shared/components/overlay/variations/float-overlay.js.map +1 -1
  69. package/lib/module/shared/components/overlay/variations/overlay-host.js +16 -64
  70. package/lib/module/shared/components/overlay/variations/overlay-host.js.map +1 -1
  71. package/lib/module/shared/components/overlay/variations/screen-overlay.js +5 -8
  72. package/lib/module/shared/components/overlay/variations/screen-overlay.js.map +1 -1
  73. package/lib/module/shared/components/screen-lifecycle.js +24 -0
  74. package/lib/module/shared/components/screen-lifecycle.js.map +1 -0
  75. package/lib/module/shared/constants.js +3 -1
  76. package/lib/module/shared/constants.js.map +1 -1
  77. package/lib/module/shared/hooks/animation/use-screen-animation.js +3 -0
  78. package/lib/module/shared/hooks/animation/use-screen-animation.js.map +1 -1
  79. package/lib/module/shared/hooks/gestures/use-build-gestures.js.map +1 -1
  80. package/lib/module/shared/hooks/lifecycle/use-close-transition.js +122 -0
  81. package/lib/module/shared/hooks/lifecycle/use-close-transition.js.map +1 -0
  82. package/lib/module/shared/hooks/lifecycle/use-open-transition.js +32 -0
  83. package/lib/module/shared/hooks/lifecycle/use-open-transition.js.map +1 -0
  84. package/lib/module/shared/hooks/lifecycle/use-screen-events.js +54 -0
  85. package/lib/module/shared/hooks/lifecycle/use-screen-events.js.map +1 -0
  86. package/lib/module/shared/hooks/navigation/use-history.js +20 -0
  87. package/lib/module/shared/hooks/navigation/use-history.js.map +1 -0
  88. package/lib/module/shared/hooks/navigation/use-screen-state.js +41 -0
  89. package/lib/module/shared/hooks/navigation/use-screen-state.js.map +1 -0
  90. package/lib/module/shared/hooks/use-stable-callback-value.js +0 -3
  91. package/lib/module/shared/hooks/use-stable-callback-value.js.map +1 -1
  92. package/lib/module/shared/index.js +2 -0
  93. package/lib/module/shared/index.js.map +1 -1
  94. package/lib/module/shared/providers/screen/keys.provider.js +0 -6
  95. package/lib/module/shared/providers/screen/keys.provider.js.map +1 -1
  96. package/lib/module/shared/providers/screen/screen-composer.js +9 -7
  97. package/lib/module/shared/providers/screen/screen-composer.js.map +1 -1
  98. package/lib/module/shared/providers/screen/styles.provider.js +1 -1
  99. package/lib/module/shared/providers/screen/styles.provider.js.map +1 -1
  100. package/lib/module/shared/providers/stack/direct.provider.js +10 -1
  101. package/lib/module/shared/providers/stack/direct.provider.js.map +1 -1
  102. package/lib/module/shared/providers/stack/managed.provider.js +10 -1
  103. package/lib/module/shared/providers/stack/managed.provider.js.map +1 -1
  104. package/lib/module/shared/stores/animation.store.js +4 -14
  105. package/lib/module/shared/stores/animation.store.js.map +1 -1
  106. package/lib/module/shared/stores/history.store.js +181 -0
  107. package/lib/module/shared/stores/history.store.js.map +1 -0
  108. package/lib/module/shared/types/index.js +1 -1
  109. package/lib/module/shared/types/index.js.map +1 -1
  110. package/lib/module/shared/types/stack.types.js +5 -0
  111. package/lib/module/shared/types/stack.types.js.map +1 -1
  112. package/lib/module/shared/utils/animation/start-screen-transition.js +6 -2
  113. package/lib/module/shared/utils/animation/start-screen-transition.js.map +1 -1
  114. package/lib/module/shared/utils/bounds/index.js +19 -4
  115. package/lib/module/shared/utils/bounds/index.js.map +1 -1
  116. package/lib/typescript/blank-stack/components/stack-view.d.ts.map +1 -1
  117. package/lib/typescript/blank-stack/components/stack-view.native.d.ts.map +1 -1
  118. package/lib/typescript/blank-stack/types.d.ts +1 -8
  119. package/lib/typescript/blank-stack/types.d.ts.map +1 -1
  120. package/lib/typescript/component-stack/components/stack-view.d.ts.map +1 -1
  121. package/lib/typescript/component-stack/types.d.ts +1 -8
  122. package/lib/typescript/component-stack/types.d.ts.map +1 -1
  123. package/lib/typescript/native-stack/types.d.ts +2 -3
  124. package/lib/typescript/native-stack/types.d.ts.map +1 -1
  125. package/lib/typescript/native-stack/views/NativeStackView.native.d.ts.map +1 -1
  126. package/lib/typescript/shared/components/overlay/variations/float-overlay.d.ts.map +1 -1
  127. package/lib/typescript/shared/components/overlay/variations/overlay-host.d.ts +1 -6
  128. package/lib/typescript/shared/components/overlay/variations/overlay-host.d.ts.map +1 -1
  129. package/lib/typescript/shared/components/overlay/variations/screen-overlay.d.ts +4 -0
  130. package/lib/typescript/shared/components/overlay/variations/screen-overlay.d.ts.map +1 -1
  131. package/lib/typescript/shared/components/screen-lifecycle.d.ts +12 -0
  132. package/lib/typescript/shared/components/screen-lifecycle.d.ts.map +1 -0
  133. package/lib/typescript/shared/constants.d.ts +2 -1
  134. package/lib/typescript/shared/constants.d.ts.map +1 -1
  135. package/lib/typescript/shared/hooks/animation/use-screen-animation.d.ts.map +1 -1
  136. package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts.map +1 -1
  137. package/lib/typescript/shared/hooks/lifecycle/use-close-transition.d.ts +13 -0
  138. package/lib/typescript/shared/hooks/lifecycle/use-close-transition.d.ts.map +1 -0
  139. package/lib/typescript/shared/hooks/lifecycle/use-open-transition.d.ts +11 -0
  140. package/lib/typescript/shared/hooks/lifecycle/use-open-transition.d.ts.map +1 -0
  141. package/lib/typescript/shared/hooks/lifecycle/use-screen-events.d.ts +7 -0
  142. package/lib/typescript/shared/hooks/lifecycle/use-screen-events.d.ts.map +1 -0
  143. package/lib/typescript/shared/hooks/navigation/use-history.d.ts +37 -0
  144. package/lib/typescript/shared/hooks/navigation/use-history.d.ts.map +1 -0
  145. package/lib/typescript/shared/hooks/navigation/use-screen-state.d.ts +40 -0
  146. package/lib/typescript/shared/hooks/navigation/use-screen-state.d.ts.map +1 -0
  147. package/lib/typescript/shared/hooks/use-stable-callback-value.d.ts.map +1 -1
  148. package/lib/typescript/shared/index.d.ts +22 -20
  149. package/lib/typescript/shared/index.d.ts.map +1 -1
  150. package/lib/typescript/shared/providers/screen/keys.provider.d.ts +2 -9
  151. package/lib/typescript/shared/providers/screen/keys.provider.d.ts.map +1 -1
  152. package/lib/typescript/shared/providers/screen/screen-composer.d.ts +1 -3
  153. package/lib/typescript/shared/providers/screen/screen-composer.d.ts.map +1 -1
  154. package/lib/typescript/shared/providers/stack/direct.provider.d.ts.map +1 -1
  155. package/lib/typescript/shared/providers/stack/managed.provider.d.ts.map +1 -1
  156. package/lib/typescript/shared/stores/animation.store.d.ts +3 -4
  157. package/lib/typescript/shared/stores/animation.store.d.ts.map +1 -1
  158. package/lib/typescript/shared/stores/history.store.d.ts +82 -0
  159. package/lib/typescript/shared/stores/history.store.d.ts.map +1 -0
  160. package/lib/typescript/shared/types/animation.types.d.ts +10 -6
  161. package/lib/typescript/shared/types/animation.types.d.ts.map +1 -1
  162. package/lib/typescript/shared/types/bounds.types.d.ts +1 -1
  163. package/lib/typescript/shared/types/bounds.types.d.ts.map +1 -1
  164. package/lib/typescript/shared/types/index.d.ts +2 -2
  165. package/lib/typescript/shared/types/index.d.ts.map +1 -1
  166. package/lib/typescript/shared/types/overlay.types.d.ts +21 -0
  167. package/lib/typescript/shared/types/overlay.types.d.ts.map +1 -1
  168. package/lib/typescript/shared/types/screen.types.d.ts +5 -4
  169. package/lib/typescript/shared/types/screen.types.d.ts.map +1 -1
  170. package/lib/typescript/shared/types/stack.types.d.ts +10 -0
  171. package/lib/typescript/shared/types/stack.types.d.ts.map +1 -1
  172. package/lib/typescript/shared/utils/animation/start-screen-transition.d.ts.map +1 -1
  173. package/lib/typescript/shared/utils/bounds/index.d.ts.map +1 -1
  174. package/package.json +1 -1
  175. package/src/blank-stack/components/stack-view.native.tsx +0 -2
  176. package/src/blank-stack/components/stack-view.tsx +0 -2
  177. package/src/blank-stack/types.ts +0 -10
  178. package/src/component-stack/components/stack-view.tsx +19 -23
  179. package/src/component-stack/types.ts +0 -10
  180. package/src/native-stack/types.ts +2 -3
  181. package/src/native-stack/views/NativeStackView.native.tsx +2 -3
  182. package/src/shared/__tests__/history.store.test.ts +550 -0
  183. package/src/shared/components/overlay/variations/float-overlay.tsx +2 -8
  184. package/src/shared/components/overlay/variations/overlay-host.tsx +18 -84
  185. package/src/shared/components/overlay/variations/screen-overlay.tsx +6 -15
  186. package/src/shared/components/screen-lifecycle.tsx +32 -0
  187. package/src/shared/constants.ts +4 -1
  188. package/src/shared/hooks/animation/use-screen-animation.tsx +7 -3
  189. package/src/shared/hooks/gestures/use-build-gestures.tsx +0 -2
  190. package/src/shared/hooks/lifecycle/use-close-transition.ts +147 -0
  191. package/src/shared/hooks/lifecycle/use-open-transition.ts +30 -0
  192. package/src/shared/hooks/lifecycle/use-screen-events.ts +62 -0
  193. package/src/shared/hooks/navigation/use-history.ts +63 -0
  194. package/src/shared/hooks/navigation/use-screen-state.tsx +99 -0
  195. package/src/shared/hooks/use-stable-callback-value.ts +0 -1
  196. package/src/shared/index.ts +5 -0
  197. package/src/shared/providers/screen/keys.provider.tsx +1 -25
  198. package/src/shared/providers/screen/screen-composer.tsx +7 -13
  199. package/src/shared/providers/screen/styles.provider.tsx +1 -1
  200. package/src/shared/providers/stack/direct.provider.tsx +11 -1
  201. package/src/shared/providers/stack/managed.provider.tsx +11 -1
  202. package/src/shared/stores/animation.store.ts +6 -20
  203. package/src/shared/stores/history.store.ts +201 -0
  204. package/src/shared/types/animation.types.ts +11 -6
  205. package/src/shared/types/bounds.types.ts +1 -0
  206. package/src/shared/types/index.ts +2 -7
  207. package/src/shared/types/overlay.types.ts +23 -0
  208. package/src/shared/types/screen.types.ts +5 -4
  209. package/src/shared/types/stack.types.ts +17 -1
  210. package/src/shared/utils/animation/start-screen-transition.ts +5 -2
  211. package/src/shared/utils/bounds/index.ts +35 -4
  212. package/lib/commonjs/native-stack/controllers/native-stack-lifecycle.js +0 -90
  213. package/lib/commonjs/native-stack/controllers/native-stack-lifecycle.js.map +0 -1
  214. package/lib/commonjs/shared/controller/managed-lifecycle.js +0 -78
  215. package/lib/commonjs/shared/controller/managed-lifecycle.js.map +0 -1
  216. package/lib/commonjs/shared/types/state.types.js +0 -9
  217. package/lib/commonjs/shared/types/state.types.js.map +0 -1
  218. package/lib/commonjs/shared/utils/read-shared-value.js +0 -17
  219. package/lib/commonjs/shared/utils/read-shared-value.js.map +0 -1
  220. package/lib/module/native-stack/controllers/native-stack-lifecycle.js +0 -83
  221. package/lib/module/native-stack/controllers/native-stack-lifecycle.js.map +0 -1
  222. package/lib/module/shared/controller/managed-lifecycle.js +0 -72
  223. package/lib/module/shared/controller/managed-lifecycle.js.map +0 -1
  224. package/lib/module/shared/types/state.types.js +0 -5
  225. package/lib/module/shared/types/state.types.js.map +0 -1
  226. package/lib/module/shared/utils/read-shared-value.js +0 -14
  227. package/lib/module/shared/utils/read-shared-value.js.map +0 -1
  228. package/lib/typescript/native-stack/controllers/native-stack-lifecycle.d.ts +0 -8
  229. package/lib/typescript/native-stack/controllers/native-stack-lifecycle.d.ts.map +0 -1
  230. package/lib/typescript/shared/controller/managed-lifecycle.d.ts +0 -9
  231. package/lib/typescript/shared/controller/managed-lifecycle.d.ts.map +0 -1
  232. package/lib/typescript/shared/types/state.types.d.ts +0 -3
  233. package/lib/typescript/shared/types/state.types.d.ts.map +0 -1
  234. package/lib/typescript/shared/utils/read-shared-value.d.ts +0 -7
  235. package/lib/typescript/shared/utils/read-shared-value.d.ts.map +0 -1
  236. package/src/native-stack/controllers/native-stack-lifecycle.tsx +0 -96
  237. package/src/shared/controller/managed-lifecycle.tsx +0 -73
  238. package/src/shared/types/state.types.ts +0 -2
  239. package/src/shared/utils/read-shared-value.ts +0 -15
@@ -0,0 +1,550 @@
1
+ import { beforeEach, describe, expect, it } from "bun:test";
2
+ import { HistoryStore } from "../stores/history.store";
3
+ import type { BaseStackDescriptor } from "../types/stack.types";
4
+
5
+ // Helper to create mock descriptors
6
+ const createDescriptor = (
7
+ name: string,
8
+ navigatorKey = "nav-1",
9
+ ): BaseStackDescriptor =>
10
+ ({
11
+ route: { key: `${name}-instance`, name },
12
+ navigation: { getState: () => ({ key: navigatorKey }) },
13
+ options: {},
14
+ render: () => null,
15
+ }) as unknown as BaseStackDescriptor;
16
+
17
+ // Helper to generate expected history key
18
+ const historyKey = (name: string, navigatorKey = "nav-1") =>
19
+ `${navigatorKey}:${name}`;
20
+
21
+ // Reset history before each test
22
+ beforeEach(() => {
23
+ HistoryStore._reset();
24
+ });
25
+
26
+ // =============================================================================
27
+ // Unit Tests - focus (LRU behavior)
28
+ // =============================================================================
29
+
30
+ describe("HistoryStore.focus - LRU behavior", () => {
31
+ it("adds new screen to history", () => {
32
+ const descriptor = createDescriptor("screen-a");
33
+
34
+ HistoryStore.focus(descriptor, "nav-1");
35
+
36
+ expect(HistoryStore.has(historyKey("screen-a"))).toBe(true);
37
+ expect(HistoryStore.size()).toBe(1);
38
+ });
39
+
40
+ it("adds multiple screens in order", () => {
41
+ HistoryStore.focus(createDescriptor("a"), "nav-1");
42
+ HistoryStore.focus(createDescriptor("b"), "nav-1");
43
+ HistoryStore.focus(createDescriptor("c"), "nav-1");
44
+
45
+ const entries = HistoryStore.toArray();
46
+ expect(entries.length).toBe(3);
47
+ expect(entries[0].descriptor.route.name).toBe("a"); // oldest
48
+ expect(entries[1].descriptor.route.name).toBe("b");
49
+ expect(entries[2].descriptor.route.name).toBe("c"); // most recent
50
+ });
51
+
52
+ it("moves existing screen to top (LRU reorder)", () => {
53
+ // Initial order: [A, B, C, D, E]
54
+ HistoryStore.focus(createDescriptor("a"), "nav-1");
55
+ HistoryStore.focus(createDescriptor("b"), "nav-1");
56
+ HistoryStore.focus(createDescriptor("c"), "nav-1");
57
+ HistoryStore.focus(createDescriptor("d"), "nav-1");
58
+ HistoryStore.focus(createDescriptor("e"), "nav-1");
59
+
60
+ // Focus B again - should move to top
61
+ HistoryStore.focus(createDescriptor("b"), "nav-1");
62
+
63
+ const entries = HistoryStore.toArray();
64
+ expect(entries.length).toBe(5); // No duplicates
65
+ expect(entries.map((e) => e.descriptor.route.name)).toEqual([
66
+ "a",
67
+ "c",
68
+ "d",
69
+ "e",
70
+ "b", // B moved to top
71
+ ]);
72
+ });
73
+
74
+ it("LRU reorder - complex scenario (browser history simulation)", () => {
75
+ // Simulate: Click b1, b2, b3, b4, b5
76
+ HistoryStore.focus(createDescriptor("b1"), "nav-1");
77
+ HistoryStore.focus(createDescriptor("b2"), "nav-1");
78
+ HistoryStore.focus(createDescriptor("b3"), "nav-1");
79
+ HistoryStore.focus(createDescriptor("b4"), "nav-1");
80
+ HistoryStore.focus(createDescriptor("b5"), "nav-1");
81
+
82
+ expect(HistoryStore.toArray().map((e) => e.descriptor.route.name)).toEqual([
83
+ "b1",
84
+ "b2",
85
+ "b3",
86
+ "b4",
87
+ "b5",
88
+ ]);
89
+
90
+ // Click b1 again - moves to top, removed from original position
91
+ HistoryStore.focus(createDescriptor("b1"), "nav-1");
92
+
93
+ expect(HistoryStore.toArray().map((e) => e.descriptor.route.name)).toEqual([
94
+ "b2", // b1 was here, now gone
95
+ "b3",
96
+ "b4",
97
+ "b5",
98
+ "b1", // b1 moved to top
99
+ ]);
100
+
101
+ // Click b2 - moves to top
102
+ HistoryStore.focus(createDescriptor("b2"), "nav-1");
103
+
104
+ expect(HistoryStore.toArray().map((e) => e.descriptor.route.name)).toEqual([
105
+ "b3", // b2 was here, now gone
106
+ "b4",
107
+ "b5",
108
+ "b1",
109
+ "b2", // b2 moved to top
110
+ ]);
111
+ });
112
+
113
+ it("updates descriptor when refocusing", () => {
114
+ const original = createDescriptor("a");
115
+ const updated = {
116
+ ...createDescriptor("a"),
117
+ options: { title: "Updated" },
118
+ } as unknown as BaseStackDescriptor;
119
+
120
+ HistoryStore.focus(original, "nav-1");
121
+ HistoryStore.focus(updated, "nav-1");
122
+
123
+ const entry = HistoryStore.get(historyKey("a"));
124
+ expect(entry?.descriptor.options).toEqual({ title: "Updated" });
125
+ });
126
+ });
127
+
128
+ // =============================================================================
129
+ // Unit Tests - Limit / Eviction
130
+ // =============================================================================
131
+
132
+ describe("HistoryStore - limit and eviction", () => {
133
+ it("evicts oldest when limit exceeded", () => {
134
+ // Add 101 entries (limit is 100)
135
+ for (let i = 0; i < 101; i++) {
136
+ HistoryStore.focus(createDescriptor(`screen-${i}`), "nav-1");
137
+ }
138
+
139
+ expect(HistoryStore.size()).toBe(100);
140
+ expect(HistoryStore.has(historyKey("screen-0"))).toBe(false); // Oldest evicted
141
+ expect(HistoryStore.has(historyKey("screen-1"))).toBe(true); // Second oldest kept
142
+ expect(HistoryStore.has(historyKey("screen-100"))).toBe(true); // Most recent kept
143
+ });
144
+
145
+ it("evicts multiple when way over limit", () => {
146
+ // Add 105 entries
147
+ for (let i = 0; i < 105; i++) {
148
+ HistoryStore.focus(createDescriptor(`screen-${i}`), "nav-1");
149
+ }
150
+
151
+ expect(HistoryStore.size()).toBe(100);
152
+ expect(HistoryStore.has(historyKey("screen-0"))).toBe(false);
153
+ expect(HistoryStore.has(historyKey("screen-4"))).toBe(false);
154
+ expect(HistoryStore.has(historyKey("screen-5"))).toBe(true);
155
+ });
156
+
157
+ it("refocusing does not cause extra eviction", () => {
158
+ // Add 99 entries
159
+ for (let i = 0; i < 99; i++) {
160
+ HistoryStore.focus(createDescriptor(`screen-${i}`), "nav-1");
161
+ }
162
+
163
+ // Refocus an existing entry - should not trigger eviction
164
+ HistoryStore.focus(createDescriptor("screen-50"), "nav-1");
165
+
166
+ expect(HistoryStore.size()).toBe(99);
167
+ expect(HistoryStore.has(historyKey("screen-0"))).toBe(true);
168
+ });
169
+ });
170
+
171
+ // =============================================================================
172
+ // Unit Tests - Query methods
173
+ // =============================================================================
174
+
175
+ describe("HistoryStore.getMostRecent", () => {
176
+ it("returns undefined when empty", () => {
177
+ expect(HistoryStore.getMostRecent()).toBeUndefined();
178
+ });
179
+
180
+ it("returns most recent entry", () => {
181
+ HistoryStore.focus(createDescriptor("a"), "nav-1");
182
+ HistoryStore.focus(createDescriptor("b"), "nav-1");
183
+ HistoryStore.focus(createDescriptor("c"), "nav-1");
184
+
185
+ const recent = HistoryStore.getMostRecent();
186
+ expect(recent?.descriptor.route.name).toBe("c");
187
+ });
188
+
189
+ it("returns newly focused entry after LRU reorder", () => {
190
+ HistoryStore.focus(createDescriptor("a"), "nav-1");
191
+ HistoryStore.focus(createDescriptor("b"), "nav-1");
192
+ HistoryStore.focus(createDescriptor("a"), "nav-1"); // Refocus A
193
+
194
+ const recent = HistoryStore.getMostRecent();
195
+ expect(recent?.descriptor.route.name).toBe("a");
196
+ });
197
+ });
198
+
199
+ describe("HistoryStore.getRecent", () => {
200
+ it("returns empty array when empty", () => {
201
+ expect(HistoryStore.getRecent(5)).toEqual([]);
202
+ });
203
+
204
+ it("returns N most recent entries (most recent first)", () => {
205
+ HistoryStore.focus(createDescriptor("a"), "nav-1");
206
+ HistoryStore.focus(createDescriptor("b"), "nav-1");
207
+ HistoryStore.focus(createDescriptor("c"), "nav-1");
208
+ HistoryStore.focus(createDescriptor("d"), "nav-1");
209
+
210
+ const recent = HistoryStore.getRecent(2);
211
+ expect(recent.length).toBe(2);
212
+ expect(recent[0].descriptor.route.name).toBe("d"); // Most recent first
213
+ expect(recent[1].descriptor.route.name).toBe("c");
214
+ });
215
+
216
+ it("returns all if N > size", () => {
217
+ HistoryStore.focus(createDescriptor("a"), "nav-1");
218
+ HistoryStore.focus(createDescriptor("b"), "nav-1");
219
+
220
+ const recent = HistoryStore.getRecent(10);
221
+ expect(recent.length).toBe(2);
222
+ });
223
+ });
224
+
225
+ describe("HistoryStore.get / has", () => {
226
+ it("get returns undefined for unknown key", () => {
227
+ expect(HistoryStore.get("unknown")).toBeUndefined();
228
+ });
229
+
230
+ it("has returns false for unknown key", () => {
231
+ expect(HistoryStore.has("unknown")).toBe(false);
232
+ });
233
+
234
+ it("get returns entry for known key", () => {
235
+ HistoryStore.focus(createDescriptor("a"), "nav-1");
236
+
237
+ const entry = HistoryStore.get(historyKey("a"));
238
+ expect(entry).toBeDefined();
239
+ expect(entry?.descriptor.route.name).toBe("a");
240
+ expect(entry?.navigatorKey).toBe("nav-1");
241
+ });
242
+
243
+ it("has returns true for known key", () => {
244
+ HistoryStore.focus(createDescriptor("a"), "nav-1");
245
+ expect(HistoryStore.has(historyKey("a"))).toBe(true);
246
+ });
247
+ });
248
+
249
+ // =============================================================================
250
+ // Unit Tests - Navigator scoped queries
251
+ // =============================================================================
252
+
253
+ describe("HistoryStore.getByNavigator", () => {
254
+ it("returns empty array for unknown navigator", () => {
255
+ HistoryStore.focus(createDescriptor("a"), "nav-1");
256
+
257
+ expect(HistoryStore.getByNavigator("nav-2")).toEqual([]);
258
+ });
259
+
260
+ it("returns entries for specific navigator only", () => {
261
+ HistoryStore.focus(createDescriptor("a", "nav-1"), "nav-1");
262
+ HistoryStore.focus(createDescriptor("b", "nav-2"), "nav-2");
263
+ HistoryStore.focus(createDescriptor("c", "nav-1"), "nav-1");
264
+ HistoryStore.focus(createDescriptor("d", "nav-2"), "nav-2");
265
+
266
+ const nav1 = HistoryStore.getByNavigator("nav-1");
267
+ expect(nav1.length).toBe(2);
268
+ expect(nav1.map((e) => e.descriptor.route.name)).toEqual(["c", "a"]); // Most recent first
269
+
270
+ const nav2 = HistoryStore.getByNavigator("nav-2");
271
+ expect(nav2.length).toBe(2);
272
+ expect(nav2.map((e) => e.descriptor.route.name)).toEqual(["d", "b"]);
273
+ });
274
+
275
+ it("returns in recency order (most recent first)", () => {
276
+ HistoryStore.focus(createDescriptor("a"), "nav-1");
277
+ HistoryStore.focus(createDescriptor("b"), "nav-1");
278
+ HistoryStore.focus(createDescriptor("c"), "nav-1");
279
+
280
+ const entries = HistoryStore.getByNavigator("nav-1");
281
+ expect(entries[0].descriptor.route.name).toBe("c"); // Most recent
282
+ expect(entries[2].descriptor.route.name).toBe("a"); // Oldest
283
+ });
284
+ });
285
+
286
+ describe("HistoryStore.clearNavigator", () => {
287
+ it("removes all entries for specified navigator", () => {
288
+ HistoryStore.focus(createDescriptor("a", "nav-1"), "nav-1");
289
+ HistoryStore.focus(createDescriptor("b", "nav-2"), "nav-2");
290
+ HistoryStore.focus(createDescriptor("c", "nav-1"), "nav-1");
291
+
292
+ HistoryStore.clearNavigator("nav-1");
293
+
294
+ expect(HistoryStore.size()).toBe(1);
295
+ expect(HistoryStore.has(historyKey("a", "nav-1"))).toBe(false);
296
+ expect(HistoryStore.has(historyKey("c", "nav-1"))).toBe(false);
297
+ expect(HistoryStore.has(historyKey("b", "nav-2"))).toBe(true);
298
+ });
299
+
300
+ it("does nothing for unknown navigator", () => {
301
+ HistoryStore.focus(createDescriptor("a"), "nav-1");
302
+
303
+ HistoryStore.clearNavigator("nav-unknown");
304
+
305
+ expect(HistoryStore.size()).toBe(1);
306
+ });
307
+ });
308
+
309
+ // =============================================================================
310
+ // Unit Tests - getPath (for multi-waypoint interpolation)
311
+ // =============================================================================
312
+
313
+ describe("HistoryStore.getPath", () => {
314
+ it("returns empty array if fromKey not found", () => {
315
+ HistoryStore.focus(createDescriptor("a"), "nav-1");
316
+ HistoryStore.focus(createDescriptor("b"), "nav-1");
317
+
318
+ expect(HistoryStore.getPath("unknown", historyKey("b"))).toEqual([]);
319
+ });
320
+
321
+ it("returns empty array if toKey not found", () => {
322
+ HistoryStore.focus(createDescriptor("a"), "nav-1");
323
+ HistoryStore.focus(createDescriptor("b"), "nav-1");
324
+
325
+ expect(HistoryStore.getPath(historyKey("a"), "unknown")).toEqual([]);
326
+ });
327
+
328
+ it("returns path from older to newer (forward direction)", () => {
329
+ HistoryStore.focus(createDescriptor("a"), "nav-1");
330
+ HistoryStore.focus(createDescriptor("b"), "nav-1");
331
+ HistoryStore.focus(createDescriptor("c"), "nav-1");
332
+ HistoryStore.focus(createDescriptor("d"), "nav-1");
333
+ HistoryStore.focus(createDescriptor("e"), "nav-1");
334
+
335
+ const path = HistoryStore.getPath(historyKey("a"), historyKey("e"));
336
+ expect(path).toEqual([
337
+ historyKey("a"),
338
+ historyKey("b"),
339
+ historyKey("c"),
340
+ historyKey("d"),
341
+ historyKey("e"),
342
+ ]);
343
+ });
344
+
345
+ it("returns path from newer to older (backward direction)", () => {
346
+ HistoryStore.focus(createDescriptor("a"), "nav-1");
347
+ HistoryStore.focus(createDescriptor("b"), "nav-1");
348
+ HistoryStore.focus(createDescriptor("c"), "nav-1");
349
+ HistoryStore.focus(createDescriptor("d"), "nav-1");
350
+ HistoryStore.focus(createDescriptor("e"), "nav-1");
351
+
352
+ const path = HistoryStore.getPath(historyKey("e"), historyKey("a"));
353
+ expect(path).toEqual([
354
+ historyKey("e"),
355
+ historyKey("d"),
356
+ historyKey("c"),
357
+ historyKey("b"),
358
+ historyKey("a"),
359
+ ]);
360
+ });
361
+
362
+ it("returns path for partial range", () => {
363
+ HistoryStore.focus(createDescriptor("a"), "nav-1");
364
+ HistoryStore.focus(createDescriptor("b"), "nav-1");
365
+ HistoryStore.focus(createDescriptor("c"), "nav-1");
366
+ HistoryStore.focus(createDescriptor("d"), "nav-1");
367
+ HistoryStore.focus(createDescriptor("e"), "nav-1");
368
+
369
+ const path = HistoryStore.getPath(historyKey("b"), historyKey("d"));
370
+ expect(path).toEqual([historyKey("b"), historyKey("c"), historyKey("d")]);
371
+ });
372
+
373
+ it("returns single key if from === to", () => {
374
+ HistoryStore.focus(createDescriptor("a"), "nav-1");
375
+ HistoryStore.focus(createDescriptor("b"), "nav-1");
376
+
377
+ const path = HistoryStore.getPath(historyKey("b"), historyKey("b"));
378
+ expect(path).toEqual([historyKey("b")]);
379
+ });
380
+
381
+ it("respects LRU reordering in path", () => {
382
+ // Initial: [A, B, C, D, E]
383
+ HistoryStore.focus(createDescriptor("a"), "nav-1");
384
+ HistoryStore.focus(createDescriptor("b"), "nav-1");
385
+ HistoryStore.focus(createDescriptor("c"), "nav-1");
386
+ HistoryStore.focus(createDescriptor("d"), "nav-1");
387
+ HistoryStore.focus(createDescriptor("e"), "nav-1");
388
+
389
+ // Refocus B - now: [A, C, D, E, B]
390
+ HistoryStore.focus(createDescriptor("b"), "nav-1");
391
+
392
+ // Path from A to B should reflect new order
393
+ const path = HistoryStore.getPath(historyKey("a"), historyKey("b"));
394
+ expect(path).toEqual([
395
+ historyKey("a"),
396
+ historyKey("c"),
397
+ historyKey("d"),
398
+ historyKey("e"),
399
+ historyKey("b"),
400
+ ]);
401
+ });
402
+ });
403
+
404
+ // =============================================================================
405
+ // Scenario Tests - Navigation Flows
406
+ // =============================================================================
407
+
408
+ describe("Scenario: DismissAll interpolation", () => {
409
+ it("provides path for dismissAll from E to A", () => {
410
+ // Navigate: A → B → C → D → E
411
+ HistoryStore.focus(createDescriptor("a"), "nav-1");
412
+ HistoryStore.focus(createDescriptor("b"), "nav-1");
413
+ HistoryStore.focus(createDescriptor("c"), "nav-1");
414
+ HistoryStore.focus(createDescriptor("d"), "nav-1");
415
+ HistoryStore.focus(createDescriptor("e"), "nav-1");
416
+
417
+ // DismissAll from E to A - need path for interpolation
418
+ const path = HistoryStore.getPath(historyKey("e"), historyKey("a"));
419
+ expect(path).toEqual([
420
+ historyKey("e"),
421
+ historyKey("d"),
422
+ historyKey("c"),
423
+ historyKey("b"),
424
+ historyKey("a"),
425
+ ]);
426
+ });
427
+ });
428
+
429
+ describe("Scenario: Forward navigation", () => {
430
+ it("dismissed screen remains in history for forward nav", () => {
431
+ // Navigate: A → B → C
432
+ HistoryStore.focus(createDescriptor("a"), "nav-1");
433
+ HistoryStore.focus(createDescriptor("b"), "nav-1");
434
+ HistoryStore.focus(createDescriptor("c"), "nav-1");
435
+
436
+ // Simulate going back to B (C dismissed but NOT removed from history)
437
+ HistoryStore.focus(createDescriptor("b"), "nav-1");
438
+
439
+ // C is still in history (before B since B was just refocused)
440
+ expect(HistoryStore.has(historyKey("c"))).toBe(true);
441
+
442
+ // Order is now [A, C, B] - C didn't move, B moved to top
443
+ const entries = HistoryStore.toArray();
444
+ expect(entries.map((e) => e.descriptor.route.name)).toEqual([
445
+ "a",
446
+ "c",
447
+ "b",
448
+ ]);
449
+
450
+ // For forward nav, we could go back to C
451
+ // (In practice, forward nav would look at what was most recent before B)
452
+ });
453
+ });
454
+
455
+ describe("Scenario: Cross-navigator history", () => {
456
+ it("maintains global order across navigators", () => {
457
+ // Tab 1: A → B
458
+ HistoryStore.focus(createDescriptor("a", "tab-1"), "tab-1");
459
+ HistoryStore.focus(createDescriptor("b", "tab-1"), "tab-1");
460
+
461
+ // Switch to Tab 2: C → D
462
+ HistoryStore.focus(createDescriptor("c", "tab-2"), "tab-2");
463
+ HistoryStore.focus(createDescriptor("d", "tab-2"), "tab-2");
464
+
465
+ // Switch back to Tab 1, focus A
466
+ HistoryStore.focus(createDescriptor("a", "tab-1"), "tab-1");
467
+
468
+ // Global order: [B, C, D, A]
469
+ const entries = HistoryStore.toArray();
470
+ expect(entries.map((e) => e.descriptor.route.name)).toEqual([
471
+ "b",
472
+ "c",
473
+ "d",
474
+ "a",
475
+ ]);
476
+
477
+ // Tab-specific order
478
+ const tab1 = HistoryStore.getByNavigator("tab-1");
479
+ expect(tab1.map((e) => e.descriptor.route.name)).toEqual(["a", "b"]);
480
+
481
+ const tab2 = HistoryStore.getByNavigator("tab-2");
482
+ expect(tab2.map((e) => e.descriptor.route.name)).toEqual(["d", "c"]);
483
+ });
484
+
485
+ it("getMostRecent returns globally most recent regardless of navigator", () => {
486
+ HistoryStore.focus(createDescriptor("a", "tab-1"), "tab-1");
487
+ HistoryStore.focus(createDescriptor("b", "tab-2"), "tab-2");
488
+ HistoryStore.focus(createDescriptor("c", "tab-1"), "tab-1");
489
+
490
+ expect(HistoryStore.getMostRecent()?.descriptor.route.name).toBe("c");
491
+ expect(HistoryStore.getMostRecent()?.navigatorKey).toBe("tab-1");
492
+ });
493
+ });
494
+
495
+ describe("Scenario: Nested navigator cleanup", () => {
496
+ it("modal navigator cleanup does not affect parent", () => {
497
+ // Parent stack
498
+ HistoryStore.focus(createDescriptor("home", "stack-main"), "stack-main");
499
+ HistoryStore.focus(
500
+ createDescriptor("details", "stack-main"),
501
+ "stack-main",
502
+ );
503
+
504
+ // Modal opens with its own navigator
505
+ HistoryStore.focus(
506
+ createDescriptor("modal-a", "stack-modal"),
507
+ "stack-modal",
508
+ );
509
+ HistoryStore.focus(
510
+ createDescriptor("modal-b", "stack-modal"),
511
+ "stack-modal",
512
+ );
513
+
514
+ expect(HistoryStore.size()).toBe(4);
515
+
516
+ // Modal closes - cleanup modal navigator
517
+ HistoryStore.clearNavigator("stack-modal");
518
+
519
+ expect(HistoryStore.size()).toBe(2);
520
+ expect(HistoryStore.has(historyKey("home", "stack-main"))).toBe(true);
521
+ expect(HistoryStore.has(historyKey("details", "stack-main"))).toBe(true);
522
+ expect(HistoryStore.has(historyKey("modal-a", "stack-modal"))).toBe(false);
523
+ expect(HistoryStore.has(historyKey("modal-b", "stack-modal"))).toBe(false);
524
+ });
525
+ });
526
+
527
+ // =============================================================================
528
+ // Unit Tests - Subscribe / Snapshot (for React integration)
529
+ // =============================================================================
530
+
531
+ describe("HistoryStore.subscribe / getSnapshot", () => {
532
+ it("getSnapshot returns current state", () => {
533
+ HistoryStore.focus(createDescriptor("a"), "nav-1");
534
+
535
+ const snapshot = HistoryStore.getSnapshot();
536
+ expect(snapshot.size).toBe(1);
537
+ expect(snapshot.get(historyKey("a"))).toBeDefined();
538
+ });
539
+
540
+ it("snapshot is updated after focus", () => {
541
+ const snapshot1 = HistoryStore.getSnapshot();
542
+ expect(snapshot1.size).toBe(0);
543
+
544
+ HistoryStore.focus(createDescriptor("a"), "nav-1");
545
+
546
+ const snapshot2 = HistoryStore.getSnapshot();
547
+ expect(snapshot2.size).toBe(1);
548
+ expect(snapshot1).not.toBe(snapshot2); // New reference
549
+ });
550
+ });
@@ -10,7 +10,7 @@ import { OverlayHost } from "./overlay-host";
10
10
  * Gets routes and descriptors from stack context.
11
11
  */
12
12
  export function FloatOverlay() {
13
- const { routes, scenes, focusedIndex, flags } = useStack();
13
+ const { scenes, focusedIndex, flags } = useStack();
14
14
 
15
15
  const activeOverlay = useMemo(
16
16
  () =>
@@ -31,13 +31,7 @@ export function FloatOverlay() {
31
31
  return (
32
32
  <KeysProvider current={current} previous={previous} next={next}>
33
33
  <ScreenStylesProvider>
34
- <OverlayHost
35
- scene={scene}
36
- scenes={scenes}
37
- routes={routes}
38
- overlayIndex={overlayIndex}
39
- isFloating
40
- />
34
+ <OverlayHost scene={scene} />
41
35
  </ScreenStylesProvider>
42
36
  </KeysProvider>
43
37
  );