@symbiote-native/navigation 2.0.3 → 2.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 (320) hide show
  1. package/README.md +42 -12
  2. package/build/angular/drawer/index.js +29 -13
  3. package/build/angular/index.d.ts +4 -4
  4. package/build/angular/injectors/inject-navigation.js +1 -1
  5. package/build/angular/navigation-scope.directive.js +1 -1
  6. package/build/angular/search-bar-ref.directive.js +1 -1
  7. package/build/angular/stack/index.d.ts +2 -0
  8. package/build/angular/stack/index.js +43 -9
  9. package/build/angular/tabs/index.d.ts +1 -1
  10. package/build/angular/tabs/index.js +40 -13
  11. package/build/core/drawer-options/index.js +19 -5
  12. package/build/core/drawer-router-state/index.js +8 -2
  13. package/build/core/guards.js +3 -1
  14. package/build/core/header-bar-buttons.js +14 -4
  15. package/build/core/index.d.ts +6 -5
  16. package/build/core/index.js +7 -3
  17. package/build/core/navigator-state/index.js +12 -4
  18. package/build/core/render-drawer.js +4 -1
  19. package/build/core/render-stack.js +13 -4
  20. package/build/core/screen-options.js +1 -1
  21. package/build/core/search-bar-commands.js +1 -1
  22. package/build/core/stack-router-state/index.d.ts +2 -0
  23. package/build/core/stack-router-state/index.js +44 -0
  24. package/build/core/state-persistence/index.js +6 -2
  25. package/build/core/tab-router-state/index.d.ts +1 -0
  26. package/build/core/tab-router-state/index.js +27 -2
  27. package/build/react/drawer/index.js +59 -26
  28. package/build/react/index.d.ts +7 -7
  29. package/build/react/index.js +2 -2
  30. package/build/react/stack/index.js +47 -10
  31. package/build/react/tabs/index.js +36 -12
  32. package/build/solid/drawer/index.d.ts +22 -0
  33. package/build/solid/drawer/index.js +394 -0
  34. package/build/solid/drawer-screen-props.d.ts +13 -0
  35. package/build/solid/drawer-screen-props.js +2 -0
  36. package/build/solid/host.d.ts +2 -0
  37. package/build/solid/host.js +23 -0
  38. package/build/solid/index.d.ts +19 -0
  39. package/build/solid/index.js +24 -0
  40. package/build/solid/linking.d.ts +3 -0
  41. package/build/solid/linking.js +55 -0
  42. package/build/solid/navigation-context.d.ts +13 -0
  43. package/build/solid/navigation-context.js +37 -0
  44. package/build/solid/primitives/create-focus-effect.d.ts +1 -0
  45. package/build/solid/primitives/create-focus-effect.js +30 -0
  46. package/build/solid/primitives/create-is-focused.d.ts +2 -0
  47. package/build/solid/primitives/create-is-focused.js +31 -0
  48. package/build/solid/primitives/create-navigation-state.d.ts +3 -0
  49. package/build/solid/primitives/create-navigation-state.js +30 -0
  50. package/build/solid/primitives/index.d.ts +12 -0
  51. package/build/solid/primitives/index.js +8 -0
  52. package/build/solid/primitives/use-drawer-navigation.d.ts +5 -0
  53. package/build/solid/primitives/use-drawer-navigation.js +15 -0
  54. package/build/solid/primitives/use-navigation.d.ts +8 -0
  55. package/build/solid/primitives/use-navigation.js +23 -0
  56. package/build/solid/primitives/use-route.d.ts +3 -0
  57. package/build/solid/primitives/use-route.js +14 -0
  58. package/build/solid/primitives/use-stack-navigation.d.ts +5 -0
  59. package/build/solid/primitives/use-stack-navigation.js +19 -0
  60. package/build/solid/primitives/use-tab-navigation.d.ts +5 -0
  61. package/build/solid/primitives/use-tab-navigation.js +15 -0
  62. package/build/solid/screen-props.d.ts +19 -0
  63. package/build/solid/screen-props.js +9 -0
  64. package/build/solid/screen-registry.d.ts +31 -0
  65. package/build/solid/screen-registry.js +86 -0
  66. package/build/solid/screen.d.ts +6 -0
  67. package/build/solid/screen.js +69 -0
  68. package/build/solid/stack/index.d.ts +15 -0
  69. package/build/solid/stack/index.js +348 -0
  70. package/build/solid/tab-screen-props.d.ts +13 -0
  71. package/build/solid/tab-screen-props.js +3 -0
  72. package/build/solid/tabs/index.d.ts +14 -0
  73. package/build/solid/tabs/index.js +228 -0
  74. package/build/svelte/attachments.d.ts +5 -0
  75. package/build/svelte/attachments.js +61 -0
  76. package/build/svelte/descriptor-subtree.d.ts +3 -0
  77. package/build/svelte/descriptor-subtree.js +39 -0
  78. package/build/svelte/drawer/drawer-props.d.ts +14 -0
  79. package/build/svelte/drawer/drawer-props.js +3 -0
  80. package/build/svelte/drawer/index.d.ts +10 -0
  81. package/build/svelte/drawer/index.js +6 -0
  82. package/build/svelte/drawer/index.svelte +559 -0
  83. package/build/svelte/drawer/index.svelte.d.ts +10 -0
  84. package/build/svelte/drawer-screen-props.d.ts +13 -0
  85. package/build/svelte/drawer-screen-props.js +4 -0
  86. package/build/svelte/drawer-screen.svelte +25 -0
  87. package/build/svelte/drawer-screen.svelte.d.ts +4 -0
  88. package/build/svelte/index.d.ts +21 -0
  89. package/build/svelte/index.js +20 -0
  90. package/build/svelte/linking.svelte.d.ts +2 -0
  91. package/build/svelte/linking.svelte.js +55 -0
  92. package/build/svelte/navigation-context.d.ts +14 -0
  93. package/build/svelte/navigation-context.js +44 -0
  94. package/build/svelte/navigation-scope.svelte +27 -0
  95. package/build/svelte/navigation-scope.svelte.d.ts +9 -0
  96. package/build/svelte/registry-host.d.ts +1 -0
  97. package/build/svelte/registry-host.js +26 -0
  98. package/build/svelte/runes/index.d.ts +12 -0
  99. package/build/svelte/runes/index.js +12 -0
  100. package/build/svelte/runes/use-drawer-navigation.svelte.d.ts +6 -0
  101. package/build/svelte/runes/use-drawer-navigation.svelte.js +19 -0
  102. package/build/svelte/runes/use-focus-effect.svelte.d.ts +1 -0
  103. package/build/svelte/runes/use-focus-effect.svelte.js +31 -0
  104. package/build/svelte/runes/use-is-focused.svelte.d.ts +3 -0
  105. package/build/svelte/runes/use-is-focused.svelte.js +34 -0
  106. package/build/svelte/runes/use-navigation-state.svelte.d.ts +4 -0
  107. package/build/svelte/runes/use-navigation-state.svelte.js +39 -0
  108. package/build/svelte/runes/use-navigation.svelte.d.ts +9 -0
  109. package/build/svelte/runes/use-navigation.svelte.js +26 -0
  110. package/build/svelte/runes/use-route.svelte.d.ts +4 -0
  111. package/build/svelte/runes/use-route.svelte.js +12 -0
  112. package/build/svelte/runes/use-stack-navigation.svelte.d.ts +6 -0
  113. package/build/svelte/runes/use-stack-navigation.svelte.js +22 -0
  114. package/build/svelte/runes/use-tab-navigation.svelte.d.ts +6 -0
  115. package/build/svelte/runes/use-tab-navigation.svelte.js +19 -0
  116. package/build/svelte/screen-props.d.ts +21 -0
  117. package/build/svelte/screen-props.js +6 -0
  118. package/build/svelte/screen-registry.d.ts +17 -0
  119. package/build/svelte/screen-registry.js +57 -0
  120. package/build/svelte/screen.svelte +28 -0
  121. package/build/svelte/screen.svelte.d.ts +4 -0
  122. package/build/svelte/stack/index.d.ts +14 -0
  123. package/build/svelte/stack/index.js +7 -0
  124. package/build/svelte/stack/index.svelte +261 -0
  125. package/build/svelte/stack/index.svelte.d.ts +15 -0
  126. package/build/svelte/stack/stack-props.d.ts +20 -0
  127. package/build/svelte/stack/stack-props.js +4 -0
  128. package/build/svelte/stack/stack-screen.svelte +205 -0
  129. package/build/svelte/stack/stack-screen.svelte.d.ts +4 -0
  130. package/build/svelte/tab-screen-props.d.ts +13 -0
  131. package/build/svelte/tab-screen-props.js +4 -0
  132. package/build/svelte/tab-screen.svelte +26 -0
  133. package/build/svelte/tab-screen.svelte.d.ts +4 -0
  134. package/build/svelte/tabs/index.d.ts +8 -0
  135. package/build/svelte/tabs/index.js +6 -0
  136. package/build/svelte/tabs/index.svelte +269 -0
  137. package/build/svelte/tabs/index.svelte.d.ts +8 -0
  138. package/build/svelte/tabs/tab-props.d.ts +7 -0
  139. package/build/svelte/tabs/tab-props.js +3 -0
  140. package/build/vue/composables/use-navigation.js +5 -1
  141. package/build/vue/drawer/index.js +36 -13
  142. package/build/vue/index.d.ts +3 -3
  143. package/build/vue/navigation-context.js +1 -1
  144. package/build/vue/stack/index.js +43 -18
  145. package/build/vue/tabs/index.js +65 -39
  146. package/build-ngc/angular/drawer/index.js +40 -18
  147. package/build-ngc/angular/drawer/index.js.map +1 -1
  148. package/build-ngc/angular/drawer-screen.directive.js +3 -3
  149. package/build-ngc/angular/index.d.ts +4 -4
  150. package/build-ngc/angular/index.js.map +1 -1
  151. package/build-ngc/angular/injectors/inject-focus-effect.js.map +1 -1
  152. package/build-ngc/angular/injectors/inject-is-focused.js.map +1 -1
  153. package/build-ngc/angular/injectors/inject-navigation-state.js.map +1 -1
  154. package/build-ngc/angular/injectors/inject-navigation.js +1 -1
  155. package/build-ngc/angular/injectors/inject-navigation.js.map +1 -1
  156. package/build-ngc/angular/linking.js.map +1 -1
  157. package/build-ngc/angular/navigation-context.service.js +3 -3
  158. package/build-ngc/angular/navigation-context.service.js.map +1 -1
  159. package/build-ngc/angular/navigation-scope.directive.js +4 -4
  160. package/build-ngc/angular/navigation-scope.directive.js.map +1 -1
  161. package/build-ngc/angular/screen.directive.js +3 -3
  162. package/build-ngc/angular/screen.directive.js.map +1 -1
  163. package/build-ngc/angular/search-bar-ref.directive.js +4 -4
  164. package/build-ngc/angular/search-bar-ref.directive.js.map +1 -1
  165. package/build-ngc/angular/stack/index.d.ts +2 -0
  166. package/build-ngc/angular/stack/index.js +52 -14
  167. package/build-ngc/angular/stack/index.js.map +1 -1
  168. package/build-ngc/angular/tab-screen.directive.js +3 -3
  169. package/build-ngc/angular/tab-screen.directive.js.map +1 -1
  170. package/build-ngc/angular/tabs/index.d.ts +1 -1
  171. package/build-ngc/angular/tabs/index.js +43 -16
  172. package/build-ngc/angular/tabs/index.js.map +1 -1
  173. package/build-ngc/core/constants.js.map +1 -1
  174. package/build-ngc/core/drawer-options/index.js +19 -5
  175. package/build-ngc/core/drawer-options/index.js.map +1 -1
  176. package/build-ngc/core/drawer-router-state/index.js +8 -2
  177. package/build-ngc/core/drawer-router-state/index.js.map +1 -1
  178. package/build-ngc/core/guards.js +3 -1
  179. package/build-ngc/core/guards.js.map +1 -1
  180. package/build-ngc/core/header-bar-buttons.js +14 -4
  181. package/build-ngc/core/header-bar-buttons.js.map +1 -1
  182. package/build-ngc/core/index.d.ts +6 -5
  183. package/build-ngc/core/index.js +7 -3
  184. package/build-ngc/core/index.js.map +1 -1
  185. package/build-ngc/core/linking-config/index.js.map +1 -1
  186. package/build-ngc/core/navigation-events/index.js.map +1 -1
  187. package/build-ngc/core/navigator-handles/index.js.map +1 -1
  188. package/build-ngc/core/navigator-state/index.js +12 -4
  189. package/build-ngc/core/navigator-state/index.js.map +1 -1
  190. package/build-ngc/core/render-drawer.js +4 -1
  191. package/build-ngc/core/render-drawer.js.map +1 -1
  192. package/build-ngc/core/render-stack.js +13 -4
  193. package/build-ngc/core/render-stack.js.map +1 -1
  194. package/build-ngc/core/render-tabs.js.map +1 -1
  195. package/build-ngc/core/screen-options.js +1 -1
  196. package/build-ngc/core/screen-options.js.map +1 -1
  197. package/build-ngc/core/search-bar-commands.js +1 -1
  198. package/build-ngc/core/search-bar-commands.js.map +1 -1
  199. package/build-ngc/core/stack-router-state/index.d.ts +2 -0
  200. package/build-ngc/core/stack-router-state/index.js +45 -0
  201. package/build-ngc/core/stack-router-state/index.js.map +1 -0
  202. package/build-ngc/core/state-persistence/index.js +6 -2
  203. package/build-ngc/core/state-persistence/index.js.map +1 -1
  204. package/build-ngc/core/tab-router-state/index.d.ts +1 -0
  205. package/build-ngc/core/tab-router-state/index.js +27 -2
  206. package/build-ngc/core/tab-router-state/index.js.map +1 -1
  207. package/package.json +44 -16
  208. package/src/angular/drawer/index.ts +75 -29
  209. package/src/angular/index.ts +20 -4
  210. package/src/angular/injectors/inject-focus-effect.ts +8 -2
  211. package/src/angular/injectors/inject-is-focused.ts +6 -4
  212. package/src/angular/injectors/inject-navigation-state.ts +7 -4
  213. package/src/angular/injectors/inject-navigation.ts +12 -3
  214. package/src/angular/linking.ts +4 -1
  215. package/src/angular/navigation-context.service.ts +5 -2
  216. package/src/angular/navigation-scope.directive.ts +6 -2
  217. package/src/angular/screen.directive.ts +13 -3
  218. package/src/angular/search-bar-ref.directive.ts +10 -2
  219. package/src/angular/stack/index.ts +91 -24
  220. package/src/angular/tab-screen.directive.ts +3 -1
  221. package/src/angular/tabs/index.ts +54 -16
  222. package/src/core/constants.ts +6 -3
  223. package/src/core/drawer-options/index.ts +40 -13
  224. package/src/core/drawer-router-state/index.ts +11 -3
  225. package/src/core/guards.ts +3 -1
  226. package/src/core/header-bar-buttons.ts +41 -12
  227. package/src/core/index.ts +29 -5
  228. package/src/core/linking-config/index.ts +31 -10
  229. package/src/core/navigation-events/index.ts +8 -2
  230. package/src/core/navigator-handles/index.ts +8 -3
  231. package/src/core/navigator-props.ts +13 -4
  232. package/src/core/navigator-state/index.ts +24 -7
  233. package/src/core/render-drawer.ts +24 -9
  234. package/src/core/render-stack.ts +52 -13
  235. package/src/core/render-tabs.ts +24 -6
  236. package/src/core/screen-options.ts +15 -5
  237. package/src/core/search-bar-commands.ts +12 -3
  238. package/src/core/stack-router-state/index.ts +52 -0
  239. package/src/core/state-persistence/index.ts +13 -3
  240. package/src/core/tab-router-state/index.ts +35 -3
  241. package/src/react/drawer/index.ts +409 -330
  242. package/src/react/drawer-screen.ts +8 -2
  243. package/src/react/hooks/use-focus-effect.ts +8 -2
  244. package/src/react/hooks/use-is-focused.ts +6 -2
  245. package/src/react/hooks/use-navigation.ts +8 -2
  246. package/src/react/index.ts +37 -7
  247. package/src/react/linking.ts +4 -1
  248. package/src/react/navigation-context.ts +6 -2
  249. package/src/react/screen.ts +12 -3
  250. package/src/react/stack/index.ts +297 -220
  251. package/src/react/tab-screen.ts +3 -1
  252. package/src/react/tabs/index.ts +202 -139
  253. package/src/solid/drawer/index.ts +570 -0
  254. package/src/solid/drawer-screen-props.ts +24 -0
  255. package/src/solid/host.ts +29 -0
  256. package/src/solid/index.ts +87 -0
  257. package/src/solid/linking.ts +74 -0
  258. package/src/solid/navigation-context.ts +56 -0
  259. package/src/solid/primitives/create-focus-effect.ts +41 -0
  260. package/src/solid/primitives/create-is-focused.ts +41 -0
  261. package/src/solid/primitives/create-navigation-state.ts +44 -0
  262. package/src/solid/primitives/index.ts +12 -0
  263. package/src/solid/primitives/use-drawer-navigation.ts +25 -0
  264. package/src/solid/primitives/use-navigation.ts +44 -0
  265. package/src/solid/primitives/use-route.ts +18 -0
  266. package/src/solid/primitives/use-stack-navigation.ts +29 -0
  267. package/src/solid/primitives/use-tab-navigation.ts +25 -0
  268. package/src/solid/screen-props.ts +51 -0
  269. package/src/solid/screen-registry.ts +159 -0
  270. package/src/solid/screen.ts +80 -0
  271. package/src/solid/stack/index.ts +528 -0
  272. package/src/solid/tab-screen-props.ts +21 -0
  273. package/src/solid/tabs/index.ts +327 -0
  274. package/src/svelte/attachments.ts +70 -0
  275. package/src/svelte/descriptor-subtree.ts +51 -0
  276. package/src/svelte/drawer/drawer-props.ts +28 -0
  277. package/src/svelte/drawer/index.svelte +559 -0
  278. package/src/svelte/drawer/index.ts +10 -0
  279. package/src/svelte/drawer-screen-props.ts +26 -0
  280. package/src/svelte/drawer-screen.svelte +25 -0
  281. package/src/svelte/fabric-tree.test-helper.ts +95 -0
  282. package/src/svelte/index.ts +83 -0
  283. package/src/svelte/linking.svelte.ts +72 -0
  284. package/src/svelte/navigation-context.ts +62 -0
  285. package/src/svelte/navigation-scope.svelte +27 -0
  286. package/src/svelte/registry-host.ts +26 -0
  287. package/src/svelte/runes/index.ts +17 -0
  288. package/src/svelte/runes/use-drawer-navigation.svelte.ts +31 -0
  289. package/src/svelte/runes/use-focus-effect.svelte.ts +41 -0
  290. package/src/svelte/runes/use-is-focused.svelte.ts +38 -0
  291. package/src/svelte/runes/use-navigation-state.svelte.ts +48 -0
  292. package/src/svelte/runes/use-navigation.svelte.ts +49 -0
  293. package/src/svelte/runes/use-route.svelte.ts +16 -0
  294. package/src/svelte/runes/use-stack-navigation.svelte.ts +34 -0
  295. package/src/svelte/runes/use-tab-navigation.svelte.ts +29 -0
  296. package/src/svelte/screen-props.ts +50 -0
  297. package/src/svelte/screen-registry.ts +98 -0
  298. package/src/svelte/screen.svelte +28 -0
  299. package/src/svelte/stack/index.svelte +261 -0
  300. package/src/svelte/stack/index.ts +11 -0
  301. package/src/svelte/stack/stack-props.ts +31 -0
  302. package/src/svelte/stack/stack-screen.svelte +205 -0
  303. package/src/svelte/svelte-ambient.d.ts +6 -0
  304. package/src/svelte/svelte-compile.test-helper.ts +147 -0
  305. package/src/svelte/tab-screen-props.ts +22 -0
  306. package/src/svelte/tab-screen.svelte +26 -0
  307. package/src/svelte/tabs/index.svelte +269 -0
  308. package/src/svelte/tabs/index.ts +10 -0
  309. package/src/svelte/tabs/tab-props.ts +13 -0
  310. package/src/vue/composables/use-navigation-state.ts +7 -4
  311. package/src/vue/composables/use-navigation.ts +13 -3
  312. package/src/vue/drawer/index.ts +128 -41
  313. package/src/vue/drawer-screen.ts +12 -5
  314. package/src/vue/index.ts +15 -3
  315. package/src/vue/linking.ts +7 -2
  316. package/src/vue/navigation-context.ts +17 -5
  317. package/src/vue/screen.ts +7 -2
  318. package/src/vue/stack/index.ts +108 -43
  319. package/src/vue/tab-screen.ts +3 -1
  320. package/src/vue/tabs/index.ts +109 -68
@@ -0,0 +1,95 @@
1
+ // Live-tree walkers the smoke tests in this folder share.
2
+ //
3
+ // `fabric.find()` (from @symbiote-native/test-utils) walks the CREATION log, not the current
4
+ // tree: a node it returns stays "found" forever, and its `props` reflect creation time rather
5
+ // than what is currently committed (svelte-adapter-dom-shim skill §15). Every assertion about
6
+ // what is on screen NOW - "this route is gone after a pop", "this header currently carries this
7
+ // title" - therefore walks `fabric.appRoot()` instead.
8
+
9
+ export type IFabricNode = {
10
+ viewName?: string;
11
+ props?: Record<string, unknown>;
12
+ children?: IFabricNode[];
13
+ instanceHandle?: unknown;
14
+ };
15
+
16
+ function isFabricNode(value: unknown): value is IFabricNode {
17
+ return typeof value === 'object' && value !== null;
18
+ }
19
+
20
+ export function walkLive(
21
+ root: unknown,
22
+ visit: (node: IFabricNode) => void,
23
+ ): void {
24
+ if (!isFabricNode(root)) return;
25
+ visit(root);
26
+ for (const child of root.children ?? []) walkLive(child, visit);
27
+ }
28
+
29
+ export function findAllLive(root: unknown, viewName: string): IFabricNode[] {
30
+ const found: IFabricNode[] = [];
31
+ walkLive(root, node => {
32
+ if (node.viewName === viewName) found.push(node);
33
+ });
34
+ return found;
35
+ }
36
+
37
+ export function findLive(
38
+ root: unknown,
39
+ viewName: string,
40
+ ): IFabricNode | undefined {
41
+ return findAllLive(root, viewName)[0];
42
+ }
43
+
44
+ export function countLive(root: unknown, viewName: string): number {
45
+ return findAllLive(root, viewName).length;
46
+ }
47
+
48
+ export function findLiveByTestId(
49
+ root: unknown,
50
+ testID: string,
51
+ ): IFabricNode | undefined {
52
+ let found: IFabricNode | undefined;
53
+ walkLive(root, node => {
54
+ if (found === undefined && node.props?.testID === testID) found = node;
55
+ });
56
+ return found;
57
+ }
58
+
59
+ const TEXT_CONTAINER_VIEW_NAMES = new Set(['RCTText', 'RCTVirtualText']);
60
+
61
+ // Every raw text committed under a parent that cannot hold one - an invalid Fabric child.
62
+ // The gap Svelte leaves between two sibling tags compiles to a ' ' text node, and the shim
63
+ // drops it exactly when the parent takes no raw text (dom-shim/text.ts, svelte-adapter-dom-shim
64
+ // §16b); inside an RCTText the same string is a real word separator and must survive. So the
65
+ // parent, not the string, is what makes a raw text legal, and this returns the ones that are
66
+ // not. Formatted `viewName > "text"` so a failure names the offender.
67
+ export function rawTextsOutsideTextContainer(root: unknown): string[] {
68
+ const found: string[] = [];
69
+ const visit = (node: unknown, parent: IFabricNode | undefined): void => {
70
+ if (!isFabricNode(node)) return;
71
+ const parentName = parent?.viewName;
72
+ if (
73
+ node.viewName === 'RCTRawText' &&
74
+ (parentName === undefined || !TEXT_CONTAINER_VIEW_NAMES.has(parentName))
75
+ ) {
76
+ found.push(`${String(parentName)} > ${JSON.stringify(node.props?.text)}`);
77
+ }
78
+ for (const child of node.children ?? []) visit(child, node);
79
+ };
80
+ visit(root, undefined);
81
+ return found;
82
+ }
83
+
84
+ // A depth-indented `viewName` outline, for asserting the exact committed SHAPE (which is what
85
+ // catches a stray whitespace text node, an extra wrapper, or a missing header).
86
+ export function outline(root: unknown): string[] {
87
+ const lines: string[] = [];
88
+ const visit = (node: unknown, depth: number): void => {
89
+ if (!isFabricNode(node)) return;
90
+ lines.push(`${' '.repeat(depth)}${String(node.viewName)}`);
91
+ for (const child of node.children ?? []) visit(child, depth + 1);
92
+ };
93
+ visit(root, 0);
94
+ return lines;
95
+ }
@@ -0,0 +1,83 @@
1
+ // @symbiote-native/navigation/svelte: the Svelte native-stack navigator over
2
+ // react-native-screens' native view primitives. Importing this barrel first registers the native
3
+ // views' ViewConfigs (../register, a side-effect import of the codegen specs - never
4
+ // react-native-screens' own React components), then exposes Stack (with Stack.Screen attached)
5
+ // and the navigator handle. Mirrors vue/index.ts's barrel shape.
6
+
7
+ import '../register';
8
+
9
+ export { Stack } from './stack';
10
+ export type { INavigatorHandle, IStackProps } from './stack';
11
+ export { default as Screen } from './screen.svelte';
12
+ export type {
13
+ IScreenOptionsArgs,
14
+ IScreenOptionsResolver,
15
+ IScreenProps,
16
+ ISvelteScreenOptions,
17
+ ISvelteSearchBarOptions,
18
+ } from './screen-props';
19
+ export type {
20
+ IScreenOptions,
21
+ IStackAnimation,
22
+ IStackPresentation,
23
+ } from '../core';
24
+
25
+ export {
26
+ useNavigation,
27
+ useStackNavigation,
28
+ useTabNavigation,
29
+ useDrawerNavigation,
30
+ useRoute,
31
+ useIsFocused,
32
+ useFocusEffect,
33
+ useNavigationState,
34
+ } from './runes';
35
+ export type {
36
+ INavigationHandle,
37
+ IStackNavigationHandle,
38
+ ITabNavigationHandle,
39
+ IDrawerNavigationHandle,
40
+ } from './runes';
41
+ export type {
42
+ INavigationScopeValue,
43
+ IAnyNavigatorHandle,
44
+ } from './navigation-context';
45
+
46
+ export { useLinkingIntegration } from './linking.svelte';
47
+ export type { ILinkingConfig, IScreenLinkingConfig } from '../core';
48
+
49
+ // Tab: the bottom-tabs navigator, a PURE-JS UI (no react-native-screens views involved, so no
50
+ // extra ViewConfig registration is needed beyond the ../register import above).
51
+ export { Tab } from './tabs';
52
+ export type { ITabNavigatorHandle, ITabProps } from './tabs';
53
+ export { default as TabScreen } from './tab-screen.svelte';
54
+ export type {
55
+ ITabScreenOptionsArgs,
56
+ ITabScreenOptionsResolver,
57
+ ITabScreenProps,
58
+ } from './tab-screen-props';
59
+ export type { ITabOptions, ITabBarIcon } from '../core';
60
+
61
+ // Drawer: the swipeable drawer navigator, a PURE-JS UI (PanResponder + Animated, no
62
+ // react-native-screens views involved, so no extra ViewConfig registration is needed beyond the
63
+ // ../register import above). See drawer/index.svelte's header for the feasibility note re:
64
+ // react-native-gesture-handler / react-native-reanimated parity gaps.
65
+ export { Drawer } from './drawer';
66
+ export type {
67
+ IDrawerContentSlotProps,
68
+ IDrawerNavigatorHandle,
69
+ IDrawerProps,
70
+ IDrawerDescriptorMap,
71
+ } from './drawer';
72
+ export { default as DrawerScreen } from './drawer-screen.svelte';
73
+ export type {
74
+ IDrawerScreenOptionsArgs,
75
+ IDrawerScreenOptionsResolver,
76
+ IDrawerScreenProps,
77
+ } from './drawer-screen-props';
78
+ export type {
79
+ IDrawerOptions,
80
+ IDrawerScreenOptions,
81
+ IDrawerType,
82
+ IDrawerPosition,
83
+ } from '../core';
@@ -0,0 +1,72 @@
1
+ // useLinkingIntegration: wires the framework-agnostic linking config (../core's
2
+ // resolveRouteFromUrl) onto an existing Stack's INavigatorHandle. Deliberately NOT called from
3
+ // inside stack/index.svelte - app code owns the wiring, the same top-level-prop shape
4
+ // @react-navigation keeps `linking` in on NavigationContainer rather than baking it into the
5
+ // router itself.
6
+ //
7
+ // Svelte-shape difference from React and Vue: React's twin takes the RESOLVED
8
+ // `navigatorHandle: INavigatorHandle` (a React hook re-runs on every render with the caller's
9
+ // current value); Vue's takes the `Ref` object and reads `.value` inside onMounted. A Svelte
10
+ // component's script runs exactly ONCE and its `bind:this` target is only assigned during mount,
11
+ // so this takes a GETTER - the boxed-getter convention this adapter uses everywhere - and reads
12
+ // it INSIDE the effect, i.e. after mount. The read is deliberately TRACKED (no `untrack`, unlike
13
+ // the ./runes files): when the app declares its handle with `$state.raw`, the effect re-runs the
14
+ // moment `bind:this` lands, so the wiring never depends on a mount-ordering guarantee at all.
15
+ // Re-running is safe because the previous run's cleanup tears the subscription down first.
16
+ //
17
+ // `.svelte.ts`: `$effect` is only usable outside a component in a file with this extension.
18
+
19
+ import { Linking, dlog } from '@symbiote-native/engine';
20
+ import type { IEventSubscription, IUrlEvent } from '@symbiote-native/engine';
21
+ import { resolveRouteFromUrl } from '../core';
22
+ import type { ILinkingConfig, INavigatorHandle } from '../core';
23
+
24
+ export function useLinkingIntegration(
25
+ config: ILinkingConfig,
26
+ getNavigatorHandle: () => INavigatorHandle | null,
27
+ ): void {
28
+ $effect(() => {
29
+ const handle = getNavigatorHandle();
30
+ if (handle === null) {
31
+ dlog('useLinkingIntegration: navigatorHandle is not available yet');
32
+ return;
33
+ }
34
+
35
+ let cancelled = false;
36
+
37
+ const applyRoute = (
38
+ url: string,
39
+ dispatch: (name: string, params?: unknown) => void,
40
+ ): void => {
41
+ const route = resolveRouteFromUrl(config, url);
42
+ if (route === null) {
43
+ dlog(`useLinkingIntegration: no route resolved for "${url}"`);
44
+ return;
45
+ }
46
+ dispatch(route.name, route.params);
47
+ };
48
+
49
+ Linking.getInitialURL()
50
+ .then(url => {
51
+ if (cancelled || url === null) return;
52
+ dlog(`useLinkingIntegration: initial URL "${url}"`);
53
+ applyRoute(url, handle.replace);
54
+ })
55
+ .catch((error: unknown) => {
56
+ dlog(`useLinkingIntegration: getInitialURL failed: ${String(error)}`);
57
+ });
58
+
59
+ const subscription: IEventSubscription = Linking.addEventListener(
60
+ 'url',
61
+ (event: IUrlEvent) => {
62
+ dlog(`useLinkingIntegration: url event "${event.url}"`);
63
+ applyRoute(event.url, handle.push);
64
+ },
65
+ );
66
+
67
+ return () => {
68
+ cancelled = true;
69
+ subscription.remove();
70
+ };
71
+ });
72
+ }
@@ -0,0 +1,62 @@
1
+ // The lifecycle half of the framework-agnostic emitter (../core/navigation-events). Svelte's twin
2
+ // of React's Context / Vue's provide-inject is setContext/getContext over one shared key, so a
3
+ // screen's subtree can read its own route, navigator handle, and per-route emitter without
4
+ // prop-drilling.
5
+ //
6
+ // Context in Svelte follows the RUNTIME render tree, not the lexical definition site: a component
7
+ // instantiated inside a snippet the app passed down to <Stack> still sees the context <Stack> (or
8
+ // the NavigationScope it renders) set - verified against the real compiler before this was built
9
+ // on. That is what lets a navigator hand each mounted route its OWN scope: Stack/Tab/Drawer mount
10
+ // one NavigationScope per route (keyed by route.key, exactly like React's per-route
11
+ // `<NavigationContext.Provider>`), and each instance's own init calls the setter once.
12
+ //
13
+ // The value is a BOXED GETTER (`{ get current() }`), never a bare value: Svelte 5 reactivity does
14
+ // not survive being handed out of the declaring scope as a plain value (svelte-adapter-dom-shim
15
+ // skill §21), and consumers must keep seeing the CURRENT route (a setParams that produces a new
16
+ // route object for the same key still has to reach them).
17
+ //
18
+ // `parent` threads the ambient value a navigator read on ITS OWN mount (undefined at the root)
19
+ // into the value it provides to its own screens, forming a linked list - this is what lets a
20
+ // screen nested inside e.g. a Stack-screen-renders-a-Tab composition reach the enclosing Stack via
21
+ // useNavigation().getParent().
22
+
23
+ import { getContext, setContext } from 'svelte';
24
+ import type { INavigationEmitter, IRoute, IAnyNavigatorHandle } from '../core';
25
+ export type { IAnyNavigatorHandle };
26
+
27
+ export type INavigationScopeValue = {
28
+ route: IRoute<unknown>;
29
+ navigation: IAnyNavigatorHandle;
30
+ emitter: INavigationEmitter;
31
+ parent?: INavigationScopeValue;
32
+ };
33
+
34
+ export type INavigationScope = { readonly current: INavigationScopeValue };
35
+
36
+ const NAVIGATION_SCOPE_KEY = Symbol('symbiote-navigation-scope');
37
+
38
+ // Written only from NavigationScope's own init (navigation-scope.svelte), never by app code.
39
+ export function setNavigationScope(scope: INavigationScope): void {
40
+ setContext(NAVIGATION_SCOPE_KEY, scope);
41
+ }
42
+
43
+ // undefined simply means "no ancestor NavigationScope", which is a legitimate state (the nesting
44
+ // root a navigator itself reads on mount) rather than an error - callers that require one go
45
+ // through requireNavigationScope below.
46
+ export function getNavigationScope(): INavigationScope | undefined {
47
+ return getContext<INavigationScope | undefined>(NAVIGATION_SCOPE_KEY);
48
+ }
49
+
50
+ // The "throw if missing" half every rune in ./runes needs (useNavigation, useRoute, useIsFocused,
51
+ // useFocusEffect, useNavigationState) - they all read the scope and threw the same shaped error,
52
+ // differing only in which rune name the message names. Centralized here so a wording change lands
53
+ // once instead of five times.
54
+ export function requireNavigationScope(runeName: string): INavigationScope {
55
+ const scope = getNavigationScope();
56
+ if (scope === undefined) {
57
+ throw new Error(
58
+ `${runeName} must be used within a screen rendered by <Stack>, <Tab>, or <Drawer>`,
59
+ );
60
+ }
61
+ return scope;
62
+ }
@@ -0,0 +1,27 @@
1
+ <script lang="ts">
2
+ // The per-route setup boundary: one instance per mounted route, so each route subtree reads its
3
+ // OWN scope (its own route/emitter) invisible to its siblings. Stack keeps every pushed route
4
+ // mounted as a sibling subtree; Tab/Drawer mount only the focused one - either way each needs a
5
+ // distinct provided value, which is an inherent property of any hierarchical
6
+ // dependency-injection scheme, Svelte's context included.
7
+ //
8
+ // `value` is a $props() field, so reading it inside the boxed getter below is a LIVE read: a
9
+ // later change (setParams producing a new route object for the same key) still reaches every
10
+ // consumer without re-running setContext, which Svelte only honours during init anyway.
11
+ import type { Snippet } from 'svelte';
12
+ import { setNavigationScope } from './navigation-context';
13
+ import type { INavigationScopeValue } from './navigation-context';
14
+
15
+ let {
16
+ value,
17
+ children,
18
+ }: { value: INavigationScopeValue; children?: Snippet } = $props();
19
+
20
+ setNavigationScope({
21
+ get current(): INavigationScopeValue {
22
+ return value;
23
+ },
24
+ });
25
+ </script>
26
+
27
+ {@render children?.()}
@@ -0,0 +1,26 @@
1
+ // Where a navigator renders its `<*.Screen>` markers so they can register themselves.
2
+ //
3
+ // The markers have to be RENDERED for their init to run (screen-registry.ts explains why
4
+ // registration is inverted on Svelte), and Svelte turns whitespace between two sibling non-text
5
+ // nodes into a REAL text node - svelte-adapter-dom-shim skill §16, a correctness bug rather than
6
+ // a formatting one. Naturally formatted app markup
7
+ //
8
+ // <Stack>
9
+ // <Stack.Screen name="home" component={Home} />
10
+ // <Stack.Screen name="details" component={Details} />
11
+ // </Stack>
12
+ //
13
+ // therefore compiles to `from_tree([marker, ' ', marker])`, and each ' ' becomes an RCTRawText
14
+ // engine node parented to whatever host tag holds the `{@render children?.()}` call. Inside a
15
+ // plain `symbiote-view` that is the invalid "text outside a <Text> component" shape; inside
16
+ // RNSScreenStack it is worse, since react-native-screens' native side expects only RNSScreen
17
+ // children there.
18
+ //
19
+ // So the markers are rendered inside a zero-size `symbiote-text` instead: raw text inside an
20
+ // RCTText is LEGAL, which turns the hazard from a device crash into a no-op and makes it
21
+ // structurally impossible rather than a rule app authors have to remember. The cost is one
22
+ // collapsed, non-interactive RCTText per navigator.
23
+ export const SCREEN_REGISTRY_HOST_PROPS: Record<string, unknown> = {
24
+ style: { position: 'absolute', width: 0, height: 0, opacity: 0 },
25
+ pointerEvents: 'none',
26
+ };
@@ -0,0 +1,17 @@
1
+ // The lifecycle bucket. `runes/` (with `*.svelte.ts` files) is Svelte's own term for
2
+ // $state/$effect, parallel to React's `hooks/` and Vue's `composables/` - per CLAUDE.md's
3
+ // <adapter_src_follows_framework_idioms>, every framework entry names this bucket after ITS
4
+ // framework's word for the concept, never a borrowed one.
5
+
6
+ export { useNavigation } from './use-navigation.svelte';
7
+ export type { INavigationHandle } from './use-navigation.svelte';
8
+ export { useStackNavigation } from './use-stack-navigation.svelte';
9
+ export type { IStackNavigationHandle } from './use-stack-navigation.svelte';
10
+ export { useTabNavigation } from './use-tab-navigation.svelte';
11
+ export type { ITabNavigationHandle } from './use-tab-navigation.svelte';
12
+ export { useDrawerNavigation } from './use-drawer-navigation.svelte';
13
+ export type { IDrawerNavigationHandle } from './use-drawer-navigation.svelte';
14
+ export { useRoute } from './use-route.svelte';
15
+ export { useIsFocused } from './use-is-focused.svelte';
16
+ export { useFocusEffect } from './use-focus-effect.svelte';
17
+ export { useNavigationState } from './use-navigation-state.svelte';
@@ -0,0 +1,31 @@
1
+ // Twin of use-stack-navigation.svelte.ts, narrowing to a Drawer handle instead.
2
+
3
+ import type { IDrawerNavigatorHandle } from '../../core';
4
+ import { isDrawerNavigatorHandle } from '../../core';
5
+ import { useNavigation } from './use-navigation.svelte';
6
+ import type { INavigationHandle } from './use-navigation.svelte';
7
+
8
+ export type IDrawerNavigationHandle = IDrawerNavigatorHandle &
9
+ Pick<INavigationHandle, 'addListener' | 'getParent'>;
10
+
11
+ export function useDrawerNavigation(): {
12
+ readonly current: IDrawerNavigationHandle;
13
+ } {
14
+ const navigation = useNavigation();
15
+ const narrowed = $derived.by<IDrawerNavigationHandle>(() => {
16
+ const value = navigation.current;
17
+ if (!isDrawerNavigatorHandle(value)) {
18
+ throw new Error(
19
+ 'useDrawerNavigation() was called from a component whose nearest navigator is not a Drawer. ' +
20
+ 'Use useNavigation() instead if this component can render under more than one navigator kind.',
21
+ );
22
+ }
23
+ return value;
24
+ });
25
+
26
+ return {
27
+ get current(): IDrawerNavigationHandle {
28
+ return narrowed;
29
+ },
30
+ };
31
+ }
@@ -0,0 +1,41 @@
1
+ // Mirrors @react-navigation's useFocusEffect; Svelte twin of vue/composables/use-focus-effect.ts.
2
+ // React's version requires memoizing `effect` (useCallback) since a new identity re-subscribes it
3
+ // like any other useEffect dependency. That doesn't apply here: a Svelte component's script runs
4
+ // once, so `effect` is read once at call time and closed over directly - there is no dependency
5
+ // array to go stale.
6
+
7
+ import { untrack } from 'svelte';
8
+ import { NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS } from '../../core';
9
+ import { requireNavigationScope } from '../navigation-context';
10
+
11
+ export function useFocusEffect(effect: () => (() => void) | void): void {
12
+ const scope = requireNavigationScope('useFocusEffect');
13
+
14
+ let cleanup: (() => void) | void;
15
+ const runEffect = (): void => {
16
+ cleanup = effect();
17
+ };
18
+ const runCleanup = (): void => {
19
+ cleanup?.();
20
+ cleanup = undefined;
21
+ };
22
+
23
+ // Empty dependency set (see use-is-focused.svelte.ts) so this subscribes once and tears down
24
+ // once. The teardown also runs any still-pending cleanup, matching Vue's onUnmounted.
25
+ $effect(() => {
26
+ const { emitter } = untrack(() => scope.current);
27
+ const unsubscribeFocus = emitter.addListener(
28
+ NAVIGATION_EVENT_FOCUS,
29
+ runEffect,
30
+ );
31
+ const unsubscribeBlur = emitter.addListener(
32
+ NAVIGATION_EVENT_BLUR,
33
+ runCleanup,
34
+ );
35
+ return () => {
36
+ unsubscribeFocus();
37
+ unsubscribeBlur();
38
+ runCleanup();
39
+ };
40
+ });
41
+ }
@@ -0,0 +1,38 @@
1
+ // Mirrors @react-navigation's useIsFocused; Svelte twin of vue/composables/use-is-focused.ts.
2
+ // Starts `false` rather than guessing from stack position: the route's emitter only fires 'focus'
3
+ // once RNSScreen's native onAppear lands (stack) or the screen mounts focused (tabs/drawer) - a
4
+ // screen genuinely isn't focused at the instant it mounts, the same async gap real native
5
+ // transitions have.
6
+
7
+ import { untrack } from 'svelte';
8
+ import { NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS } from '../../core';
9
+ import { requireNavigationScope } from '../navigation-context';
10
+
11
+ export function useIsFocused(): { readonly current: boolean } {
12
+ const scope = requireNavigationScope('useIsFocused');
13
+ let isFocused = $state(false);
14
+
15
+ // `untrack` keeps this effect's dependency set EMPTY, so it subscribes exactly once on mount
16
+ // and cleans up exactly once on unmount - matching Vue's onMounted/onUnmounted pair. Without
17
+ // it the effect would re-read `scope.current` and re-subscribe on every setParams (which
18
+ // produces a new route object for the same key, and therefore the same emitter).
19
+ $effect(() => {
20
+ const { emitter } = untrack(() => scope.current);
21
+ const unsubscribeFocus = emitter.addListener(NAVIGATION_EVENT_FOCUS, () => {
22
+ isFocused = true;
23
+ });
24
+ const unsubscribeBlur = emitter.addListener(NAVIGATION_EVENT_BLUR, () => {
25
+ isFocused = false;
26
+ });
27
+ return () => {
28
+ unsubscribeFocus();
29
+ unsubscribeBlur();
30
+ };
31
+ });
32
+
33
+ return {
34
+ get current(): boolean {
35
+ return isFocused;
36
+ },
37
+ };
38
+ }
@@ -0,0 +1,48 @@
1
+ // Mirrors @react-navigation's useNavigationState; Svelte twin of
2
+ // vue/composables/use-navigation-state.ts. The reducer/dispatch machinery lives in
3
+ // core/navigator-state.ts (tab-router-state.ts / drawer-router-state.ts for the other two
4
+ // navigators) - this rune only wires the subscription to the `state` broadcast each navigator
5
+ // re-emits after commit.
6
+ //
7
+ // Seeded from a single-route snapshot ({ routes: [route] }) rather than left undefined: the real
8
+ // broadcast lands after mount, so a selector reading e.g. `state.routes.at(-1)?.name` still
9
+ // resolves correctly on first paint for the common single-route case - the same async gap
10
+ // useIsFocused documents.
11
+ //
12
+ // `$state.raw`, not `$state`: TResult is arbitrary app data (often the router state itself, whose
13
+ // route objects the reducer compares by identity), and a deep reactive proxy would hand callers
14
+ // a different object than the navigator holds - the same identity rule Vue's shallowRef enforces
15
+ // here.
16
+
17
+ import { untrack } from 'svelte';
18
+ import type { INavigatorState } from '../../core';
19
+ import { NAVIGATION_EVENT_STATE, isRecord } from '../../core';
20
+ import { requireNavigationScope } from '../navigation-context';
21
+
22
+ function isNavigatorState(value: unknown): value is INavigatorState {
23
+ return isRecord(value) && Array.isArray(value.routes);
24
+ }
25
+
26
+ export function useNavigationState<TResult>(
27
+ selector: (state: INavigatorState) => TResult,
28
+ ): {
29
+ readonly current: TResult;
30
+ } {
31
+ const scope = requireNavigationScope('useNavigationState');
32
+ let result = $state.raw<TResult>(selector({ routes: [scope.current.route] }));
33
+
34
+ // Empty dependency set (see use-is-focused.svelte.ts): subscribe once, tear down once.
35
+ $effect(() => {
36
+ const { emitter } = untrack(() => scope.current);
37
+ return emitter.addListener(NAVIGATION_EVENT_STATE, (state: unknown) => {
38
+ if (!isNavigatorState(state)) return;
39
+ result = selector(state);
40
+ });
41
+ });
42
+
43
+ return {
44
+ get current(): TResult {
45
+ return result;
46
+ },
47
+ };
48
+ }
@@ -0,0 +1,49 @@
1
+ // Mirrors @react-navigation's `navigation.addListener('focus', cb)` surface; the Svelte twin of
2
+ // vue/composables/use-navigation.ts. All pub/sub logic lives in ../../core/navigation-events -
3
+ // this rune only reads the navigation scope off the context and binds identity.
4
+ //
5
+ // `.svelte.ts`, and a BOXED GETTER return (`{ get current() }`) rather than a bare value: runes
6
+ // are only usable outside a component in a `.svelte.ts` file, and Svelte 5 reactivity does not
7
+ // survive being returned as a plain value from a plain function (svelte-adapter-dom-shim skill
8
+ // §21). A caller reads `useNavigation().current` inside a `$derived`/template/`$effect`, exactly
9
+ // like unwrapping Vue's `ComputedRef` via `.value`.
10
+
11
+ import type {
12
+ INavigationEventListener,
13
+ INavigationEventName,
14
+ } from '../../core';
15
+ import { requireNavigationScope } from '../navigation-context';
16
+ import type { IAnyNavigatorHandle } from '../navigation-context';
17
+
18
+ export type INavigationHandle = IAnyNavigatorHandle & {
19
+ addListener: (
20
+ event: INavigationEventName,
21
+ listener: INavigationEventListener,
22
+ ) => () => void;
23
+ // Walks exactly ONE hop up the scope's `parent` chain to the enclosing navigator's handle -
24
+ // e.g. a Tab screen nested inside a Stack screen calling getParent() to push a new Stack route.
25
+ // Callers narrow the union themselves ('push' in parent, etc.). Deliberately NOT
26
+ // react-navigation's getParent(id) (named/targeted ancestor lookup) or a target-based dispatch
27
+ // to a specific nested navigator by name - plain immediate-parent walking is v1 scope; multi-hop
28
+ // ancestry would need each returned handle to carry its own getParent, which the plain
29
+ // per-navigator handle types (INavigatorHandle/ITabNavigatorHandle/IDrawerNavigatorHandle) don't.
30
+ getParent: () => IAnyNavigatorHandle | undefined;
31
+ };
32
+
33
+ export function useNavigation(): { readonly current: INavigationHandle } {
34
+ const scope = requireNavigationScope('useNavigation');
35
+ const handle = $derived.by<INavigationHandle>(() => {
36
+ const { navigation, emitter, parent } = scope.current;
37
+ return {
38
+ ...navigation,
39
+ addListener: emitter.addListener,
40
+ getParent: () => parent?.navigation,
41
+ };
42
+ });
43
+
44
+ return {
45
+ get current(): INavigationHandle {
46
+ return handle;
47
+ },
48
+ };
49
+ }
@@ -0,0 +1,16 @@
1
+ // Mirrors @react-navigation's useRoute; Svelte twin of vue/composables/use-route.ts. Zero logic
2
+ // of its own - the route object itself is built by Stack's/Tab's/Drawer's render loop from the
3
+ // shared core reducer's state.
4
+
5
+ import type { IRoute } from '../../core';
6
+ import { requireNavigationScope } from '../navigation-context';
7
+
8
+ export function useRoute(): { readonly current: IRoute<unknown> } {
9
+ const scope = requireNavigationScope('useRoute');
10
+
11
+ return {
12
+ get current(): IRoute<unknown> {
13
+ return scope.current.route;
14
+ },
15
+ };
16
+ }
@@ -0,0 +1,34 @@
1
+ // useNavigation() returns a union (IAnyNavigatorHandle) since it doesn't know which navigator
2
+ // mounted the calling component. When a component genuinely knows it only ever renders under a
3
+ // Stack, this rune narrows that union ONCE, here, so call sites never write their own
4
+ // 'push' in navigation check. Svelte twin of vue/composables/use-stack-navigation.ts.
5
+
6
+ import type { INavigatorHandle } from '../../core';
7
+ import { isStackNavigatorHandle } from '../../core';
8
+ import { useNavigation } from './use-navigation.svelte';
9
+ import type { INavigationHandle } from './use-navigation.svelte';
10
+
11
+ export type IStackNavigationHandle = INavigatorHandle &
12
+ Pick<INavigationHandle, 'addListener' | 'getParent'>;
13
+
14
+ export function useStackNavigation(): {
15
+ readonly current: IStackNavigationHandle;
16
+ } {
17
+ const navigation = useNavigation();
18
+ const narrowed = $derived.by<IStackNavigationHandle>(() => {
19
+ const value = navigation.current;
20
+ if (!isStackNavigatorHandle(value)) {
21
+ throw new Error(
22
+ 'useStackNavigation() was called from a component whose nearest navigator is not a Stack. ' +
23
+ 'Use useNavigation() instead if this component can render under more than one navigator kind.',
24
+ );
25
+ }
26
+ return value;
27
+ });
28
+
29
+ return {
30
+ get current(): IStackNavigationHandle {
31
+ return narrowed;
32
+ },
33
+ };
34
+ }