@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,55 @@
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
+ import { Linking, dlog } from '@symbiote-native/engine';
19
+ import { resolveRouteFromUrl } from '../core/index.js';
20
+ export function useLinkingIntegration(config, getNavigatorHandle) {
21
+ $effect(() => {
22
+ const handle = getNavigatorHandle();
23
+ if (handle === null) {
24
+ dlog('useLinkingIntegration: navigatorHandle is not available yet');
25
+ return;
26
+ }
27
+ let cancelled = false;
28
+ const applyRoute = (url, dispatch) => {
29
+ const route = resolveRouteFromUrl(config, url);
30
+ if (route === null) {
31
+ dlog(`useLinkingIntegration: no route resolved for "${url}"`);
32
+ return;
33
+ }
34
+ dispatch(route.name, route.params);
35
+ };
36
+ Linking.getInitialURL()
37
+ .then(url => {
38
+ if (cancelled || url === null)
39
+ return;
40
+ dlog(`useLinkingIntegration: initial URL "${url}"`);
41
+ applyRoute(url, handle.replace);
42
+ })
43
+ .catch((error) => {
44
+ dlog(`useLinkingIntegration: getInitialURL failed: ${String(error)}`);
45
+ });
46
+ const subscription = Linking.addEventListener('url', (event) => {
47
+ dlog(`useLinkingIntegration: url event "${event.url}"`);
48
+ applyRoute(event.url, handle.push);
49
+ });
50
+ return () => {
51
+ cancelled = true;
52
+ subscription.remove();
53
+ };
54
+ });
55
+ }
@@ -0,0 +1,14 @@
1
+ import type { INavigationEmitter, IRoute, IAnyNavigatorHandle } from '../core';
2
+ export type { IAnyNavigatorHandle };
3
+ export type INavigationScopeValue = {
4
+ route: IRoute<unknown>;
5
+ navigation: IAnyNavigatorHandle;
6
+ emitter: INavigationEmitter;
7
+ parent?: INavigationScopeValue;
8
+ };
9
+ export type INavigationScope = {
10
+ readonly current: INavigationScopeValue;
11
+ };
12
+ export declare function setNavigationScope(scope: INavigationScope): void;
13
+ export declare function getNavigationScope(): INavigationScope | undefined;
14
+ export declare function requireNavigationScope(runeName: string): INavigationScope;
@@ -0,0 +1,44 @@
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
+ import { getContext, setContext } from 'svelte';
23
+ const NAVIGATION_SCOPE_KEY = Symbol('symbiote-navigation-scope');
24
+ // Written only from NavigationScope's own init (navigation-scope.svelte), never by app code.
25
+ export function setNavigationScope(scope) {
26
+ setContext(NAVIGATION_SCOPE_KEY, scope);
27
+ }
28
+ // undefined simply means "no ancestor NavigationScope", which is a legitimate state (the nesting
29
+ // root a navigator itself reads on mount) rather than an error - callers that require one go
30
+ // through requireNavigationScope below.
31
+ export function getNavigationScope() {
32
+ return getContext(NAVIGATION_SCOPE_KEY);
33
+ }
34
+ // The "throw if missing" half every rune in ./runes needs (useNavigation, useRoute, useIsFocused,
35
+ // useFocusEffect, useNavigationState) - they all read the scope and threw the same shaped error,
36
+ // differing only in which rune name the message names. Centralized here so a wording change lands
37
+ // once instead of five times.
38
+ export function requireNavigationScope(runeName) {
39
+ const scope = getNavigationScope();
40
+ if (scope === undefined) {
41
+ throw new Error(`${runeName} must be used within a screen rendered by <Stack>, <Tab>, or <Drawer>`);
42
+ }
43
+ return scope;
44
+ }
@@ -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,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { INavigationScopeValue } from './navigation-context';
3
+ type $$ComponentProps = {
4
+ value: INavigationScopeValue;
5
+ children?: Snippet;
6
+ };
7
+ declare const NavigationScope: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type NavigationScope = ReturnType<typeof NavigationScope>;
9
+ export default NavigationScope;
@@ -0,0 +1 @@
1
+ export declare const SCREEN_REGISTRY_HOST_PROPS: Record<string, unknown>;
@@ -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 = {
24
+ style: { position: 'absolute', width: 0, height: 0, opacity: 0 },
25
+ pointerEvents: 'none',
26
+ };
@@ -0,0 +1,12 @@
1
+ export { useNavigation } from './use-navigation.svelte';
2
+ export type { INavigationHandle } from './use-navigation.svelte';
3
+ export { useStackNavigation } from './use-stack-navigation.svelte';
4
+ export type { IStackNavigationHandle } from './use-stack-navigation.svelte';
5
+ export { useTabNavigation } from './use-tab-navigation.svelte';
6
+ export type { ITabNavigationHandle } from './use-tab-navigation.svelte';
7
+ export { useDrawerNavigation } from './use-drawer-navigation.svelte';
8
+ export type { IDrawerNavigationHandle } from './use-drawer-navigation.svelte';
9
+ export { useRoute } from './use-route.svelte';
10
+ export { useIsFocused } from './use-is-focused.svelte';
11
+ export { useFocusEffect } from './use-focus-effect.svelte';
12
+ export { useNavigationState } from './use-navigation-state.svelte';
@@ -0,0 +1,12 @@
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
+ export { useNavigation } from './use-navigation.svelte';
6
+ export { useStackNavigation } from './use-stack-navigation.svelte';
7
+ export { useTabNavigation } from './use-tab-navigation.svelte';
8
+ export { useDrawerNavigation } from './use-drawer-navigation.svelte';
9
+ export { useRoute } from './use-route.svelte';
10
+ export { useIsFocused } from './use-is-focused.svelte';
11
+ export { useFocusEffect } from './use-focus-effect.svelte';
12
+ export { useNavigationState } from './use-navigation-state.svelte';
@@ -0,0 +1,6 @@
1
+ import type { IDrawerNavigatorHandle } from '../../core';
2
+ import type { INavigationHandle } from './use-navigation.svelte';
3
+ export type IDrawerNavigationHandle = IDrawerNavigatorHandle & Pick<INavigationHandle, 'addListener' | 'getParent'>;
4
+ export declare function useDrawerNavigation(): {
5
+ readonly current: IDrawerNavigationHandle;
6
+ };
@@ -0,0 +1,19 @@
1
+ // Twin of use-stack-navigation.svelte.ts, narrowing to a Drawer handle instead.
2
+ import { isDrawerNavigatorHandle } from '../../core/index.js';
3
+ import { useNavigation } from './use-navigation.svelte';
4
+ export function useDrawerNavigation() {
5
+ const navigation = useNavigation();
6
+ const narrowed = $derived.by(() => {
7
+ const value = navigation.current;
8
+ if (!isDrawerNavigatorHandle(value)) {
9
+ throw new Error('useDrawerNavigation() was called from a component whose nearest navigator is not a Drawer. ' +
10
+ 'Use useNavigation() instead if this component can render under more than one navigator kind.');
11
+ }
12
+ return value;
13
+ });
14
+ return {
15
+ get current() {
16
+ return narrowed;
17
+ },
18
+ };
19
+ }
@@ -0,0 +1 @@
1
+ export declare function useFocusEffect(effect: () => (() => void) | void): void;
@@ -0,0 +1,31 @@
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
+ import { untrack } from 'svelte';
7
+ import { NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS } from '../../core/index.js';
8
+ import { requireNavigationScope } from '../navigation-context.js';
9
+ export function useFocusEffect(effect) {
10
+ const scope = requireNavigationScope('useFocusEffect');
11
+ let cleanup;
12
+ const runEffect = () => {
13
+ cleanup = effect();
14
+ };
15
+ const runCleanup = () => {
16
+ cleanup?.();
17
+ cleanup = undefined;
18
+ };
19
+ // Empty dependency set (see use-is-focused.svelte.ts) so this subscribes once and tears down
20
+ // once. The teardown also runs any still-pending cleanup, matching Vue's onUnmounted.
21
+ $effect(() => {
22
+ const { emitter } = untrack(() => scope.current);
23
+ const unsubscribeFocus = emitter.addListener(NAVIGATION_EVENT_FOCUS, runEffect);
24
+ const unsubscribeBlur = emitter.addListener(NAVIGATION_EVENT_BLUR, runCleanup);
25
+ return () => {
26
+ unsubscribeFocus();
27
+ unsubscribeBlur();
28
+ runCleanup();
29
+ };
30
+ });
31
+ }
@@ -0,0 +1,3 @@
1
+ export declare function useIsFocused(): {
2
+ readonly current: boolean;
3
+ };
@@ -0,0 +1,34 @@
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
+ import { untrack } from 'svelte';
7
+ import { NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS } from '../../core/index.js';
8
+ import { requireNavigationScope } from '../navigation-context.js';
9
+ export function useIsFocused() {
10
+ const scope = requireNavigationScope('useIsFocused');
11
+ let isFocused = $state(false);
12
+ // `untrack` keeps this effect's dependency set EMPTY, so it subscribes exactly once on mount
13
+ // and cleans up exactly once on unmount - matching Vue's onMounted/onUnmounted pair. Without
14
+ // it the effect would re-read `scope.current` and re-subscribe on every setParams (which
15
+ // produces a new route object for the same key, and therefore the same emitter).
16
+ $effect(() => {
17
+ const { emitter } = untrack(() => scope.current);
18
+ const unsubscribeFocus = emitter.addListener(NAVIGATION_EVENT_FOCUS, () => {
19
+ isFocused = true;
20
+ });
21
+ const unsubscribeBlur = emitter.addListener(NAVIGATION_EVENT_BLUR, () => {
22
+ isFocused = false;
23
+ });
24
+ return () => {
25
+ unsubscribeFocus();
26
+ unsubscribeBlur();
27
+ };
28
+ });
29
+ return {
30
+ get current() {
31
+ return isFocused;
32
+ },
33
+ };
34
+ }
@@ -0,0 +1,4 @@
1
+ import type { INavigatorState } from '../../core';
2
+ export declare function useNavigationState<TResult>(selector: (state: INavigatorState) => TResult): {
3
+ readonly current: TResult;
4
+ };
@@ -0,0 +1,39 @@
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
+ import { untrack } from 'svelte';
17
+ import { NAVIGATION_EVENT_STATE, isRecord } from '../../core/index.js';
18
+ import { requireNavigationScope } from '../navigation-context.js';
19
+ function isNavigatorState(value) {
20
+ return isRecord(value) && Array.isArray(value.routes);
21
+ }
22
+ export function useNavigationState(selector) {
23
+ const scope = requireNavigationScope('useNavigationState');
24
+ let result = $state.raw(selector({ routes: [scope.current.route] }));
25
+ // Empty dependency set (see use-is-focused.svelte.ts): subscribe once, tear down once.
26
+ $effect(() => {
27
+ const { emitter } = untrack(() => scope.current);
28
+ return emitter.addListener(NAVIGATION_EVENT_STATE, (state) => {
29
+ if (!isNavigatorState(state))
30
+ return;
31
+ result = selector(state);
32
+ });
33
+ });
34
+ return {
35
+ get current() {
36
+ return result;
37
+ },
38
+ };
39
+ }
@@ -0,0 +1,9 @@
1
+ import type { INavigationEventListener, INavigationEventName } from '../../core';
2
+ import type { IAnyNavigatorHandle } from '../navigation-context';
3
+ export type INavigationHandle = IAnyNavigatorHandle & {
4
+ addListener: (event: INavigationEventName, listener: INavigationEventListener) => () => void;
5
+ getParent: () => IAnyNavigatorHandle | undefined;
6
+ };
7
+ export declare function useNavigation(): {
8
+ readonly current: INavigationHandle;
9
+ };
@@ -0,0 +1,26 @@
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
+ import { requireNavigationScope } from '../navigation-context.js';
11
+ export function useNavigation() {
12
+ const scope = requireNavigationScope('useNavigation');
13
+ const handle = $derived.by(() => {
14
+ const { navigation, emitter, parent } = scope.current;
15
+ return {
16
+ ...navigation,
17
+ addListener: emitter.addListener,
18
+ getParent: () => parent?.navigation,
19
+ };
20
+ });
21
+ return {
22
+ get current() {
23
+ return handle;
24
+ },
25
+ };
26
+ }
@@ -0,0 +1,4 @@
1
+ import type { IRoute } from '../../core';
2
+ export declare function useRoute(): {
3
+ readonly current: IRoute<unknown>;
4
+ };
@@ -0,0 +1,12 @@
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
+ import { requireNavigationScope } from '../navigation-context.js';
5
+ export function useRoute() {
6
+ const scope = requireNavigationScope('useRoute');
7
+ return {
8
+ get current() {
9
+ return scope.current.route;
10
+ },
11
+ };
12
+ }
@@ -0,0 +1,6 @@
1
+ import type { INavigatorHandle } from '../../core';
2
+ import type { INavigationHandle } from './use-navigation.svelte';
3
+ export type IStackNavigationHandle = INavigatorHandle & Pick<INavigationHandle, 'addListener' | 'getParent'>;
4
+ export declare function useStackNavigation(): {
5
+ readonly current: IStackNavigationHandle;
6
+ };
@@ -0,0 +1,22 @@
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
+ import { isStackNavigatorHandle } from '../../core/index.js';
6
+ import { useNavigation } from './use-navigation.svelte';
7
+ export function useStackNavigation() {
8
+ const navigation = useNavigation();
9
+ const narrowed = $derived.by(() => {
10
+ const value = navigation.current;
11
+ if (!isStackNavigatorHandle(value)) {
12
+ throw new Error('useStackNavigation() was called from a component whose nearest navigator is not a Stack. ' +
13
+ 'Use useNavigation() instead if this component can render under more than one navigator kind.');
14
+ }
15
+ return value;
16
+ });
17
+ return {
18
+ get current() {
19
+ return narrowed;
20
+ },
21
+ };
22
+ }
@@ -0,0 +1,6 @@
1
+ import type { ITabNavigatorHandle } from '../../core';
2
+ import type { INavigationHandle } from './use-navigation.svelte';
3
+ export type ITabNavigationHandle = ITabNavigatorHandle & Pick<INavigationHandle, 'addListener' | 'getParent'>;
4
+ export declare function useTabNavigation(): {
5
+ readonly current: ITabNavigationHandle;
6
+ };
@@ -0,0 +1,19 @@
1
+ // Twin of use-stack-navigation.svelte.ts, narrowing to a Tab handle instead.
2
+ import { isTabNavigatorHandle } from '../../core/index.js';
3
+ import { useNavigation } from './use-navigation.svelte';
4
+ export function useTabNavigation() {
5
+ const navigation = useNavigation();
6
+ const narrowed = $derived.by(() => {
7
+ const value = navigation.current;
8
+ if (!isTabNavigatorHandle(value)) {
9
+ throw new Error('useTabNavigation() was called from a component whose nearest navigator is not a Tab. ' +
10
+ 'Use useNavigation() instead if this component can render under more than one navigator kind.');
11
+ }
12
+ return value;
13
+ });
14
+ return {
15
+ get current() {
16
+ return narrowed;
17
+ },
18
+ };
19
+ }
@@ -0,0 +1,21 @@
1
+ import type { Component } from 'svelte';
2
+ import type { INavigatorHandle, IRoute, IScreenOptions, ISearchBarCommands, ISearchBarOptions } from '../core';
3
+ export type ISvelteSearchBarOptions = ISearchBarOptions & {
4
+ ref?: {
5
+ current: ISearchBarCommands | null;
6
+ };
7
+ };
8
+ export type ISvelteScreenOptions = Omit<IScreenOptions, 'headerSearchBarOptions'> & {
9
+ headerSearchBarOptions?: ISvelteSearchBarOptions;
10
+ };
11
+ export type IScreenOptionsArgs = {
12
+ route: IRoute<unknown>;
13
+ navigation: INavigatorHandle;
14
+ };
15
+ export type IScreenOptionsResolver = (args: IScreenOptionsArgs) => ISvelteScreenOptions;
16
+ export type IScreenProps = {
17
+ name: string;
18
+ component: Component;
19
+ options?: ISvelteScreenOptions | IScreenOptionsResolver;
20
+ initialParams?: unknown;
21
+ };
@@ -0,0 +1,6 @@
1
+ // Stack.Screen's public prop surface. Split out of `screen.svelte` because plain `tsc --build`
2
+ // resolves an import of a `.svelte` file through svelte's own ambient `declare module '*.svelte'`
3
+ // fallback (a bare default export only) and never parses the real `<script module>` block - so a
4
+ // NAMED type re-exported from a `.svelte` file is invisible to `tsc`. Same rule the Svelte
5
+ // adapter's own `view-props.ts` / `switch-props.ts` follow.
6
+ export {};
@@ -0,0 +1,17 @@
1
+ import type { Component } from 'svelte';
2
+ export type INavigatorKind = 'stack' | 'tab' | 'drawer';
3
+ export type IRegisteredScreen<TOptions> = {
4
+ readonly name: string;
5
+ readonly component: Component;
6
+ readonly options: TOptions | undefined;
7
+ readonly initialParams: unknown;
8
+ };
9
+ export type IScreenCollector<TOptions> = {
10
+ readonly kind: INavigatorKind;
11
+ register(screen: IRegisteredScreen<TOptions>): void;
12
+ unregister(screen: IRegisteredScreen<TOptions>): void;
13
+ };
14
+ export declare function setScreenCollector<TOptions>(collector: IScreenCollector<TOptions>): void;
15
+ export declare function collectScreen<TOptions>(kind: INavigatorKind, screen: IRegisteredScreen<TOptions>): void;
16
+ export declare function withoutScreen<TOptions>(screens: readonly IRegisteredScreen<TOptions>[], screen: IRegisteredScreen<TOptions>): IRegisteredScreen<TOptions>[];
17
+ export declare function toRegistry<TOptions>(screens: readonly IRegisteredScreen<TOptions>[]): Map<string, IRegisteredScreen<TOptions>>;
@@ -0,0 +1,57 @@
1
+ // How a navigator learns about its `<Stack.Screen>` / `<Tab.Screen>` / `<Drawer.Screen>` markers.
2
+ //
3
+ // React reads `children` as an inspectable array; Vue scans the default slot's vnodes
4
+ // (`vnode.type === Screen`); Angular queries `@ContentChildren`. Svelte has NO equivalent: a
5
+ // component receives its children as an opaque `Snippet`, with no way to enumerate or inspect
6
+ // what is inside it (the same wall the Svelte adapter's ScrollView hit for
7
+ // `stickyHeaderIndices`, svelte-adapter-dom-shim skill §18). So registration is INVERTED here:
8
+ // the navigator publishes a collector on the context, each marker registers ITSELF during its own
9
+ // init, and the navigator's route list derives from what came back.
10
+ //
11
+ // Ordering works out because Svelte's initial render is synchronous and top-down: a navigator
12
+ // renders the children snippet BEFORE the `{#each}` that paints its routes, so by the time the
13
+ // route list is first read every marker has already registered. Verified against the real
14
+ // compiler, not assumed.
15
+ //
16
+ // ONE key, not one per navigator kind: nearest-navigator-wins shadowing falls out of Svelte's own
17
+ // context lookup, so a Stack nested inside a Tab screen automatically captures its own markers.
18
+ // The `kind` tag then rejects a MISMATCHED marker (a `<Tab.Screen>` written inside a `<Stack>`)
19
+ // instead of letting it silently register with some far-away ancestor Tab - the failure mode a
20
+ // per-kind key would have.
21
+ import { getContext, onDestroy, setContext } from 'svelte';
22
+ import { dlog } from '@symbiote-native/engine';
23
+ const SCREEN_COLLECTOR_KEY = Symbol('symbiote-navigation-screen-collector');
24
+ // Called once from a navigator's own init, before it renders the children snippet.
25
+ export function setScreenCollector(collector) {
26
+ setContext(SCREEN_COLLECTOR_KEY, collector);
27
+ }
28
+ // Called once from a marker's own init. Registration is undone on destroy, so a marker behind an
29
+ // `{#if}` disappears from the registry exactly like a Vue slot scan stops seeing a removed vnode.
30
+ export function collectScreen(kind, screen) {
31
+ const collector = getContext(SCREEN_COLLECTOR_KEY);
32
+ if (collector === undefined) {
33
+ dlog(`navigation: <${kind} screen "${screen.name}"> rendered outside any navigator, ignored`);
34
+ return;
35
+ }
36
+ if (collector.kind !== kind) {
37
+ dlog(`navigation: a ${kind} screen marker ("${screen.name}") was rendered inside a ` +
38
+ `${collector.kind} navigator, ignored`);
39
+ return;
40
+ }
41
+ collector.register(screen);
42
+ onDestroy(() => collector.unregister(screen));
43
+ }
44
+ // The navigator side of the pair: a plain, non-reactive list plus the two mutators the collector
45
+ // exposes. The navigator holds the list in `$state.raw` and reassigns it (never mutates in place)
46
+ // so a component reference - a plain function - is never wrapped in a deep reactive proxy, which
47
+ // would break both the getters above and Svelte's own component identity checks.
48
+ export function withoutScreen(screens, screen) {
49
+ return screens.filter(candidate => candidate !== screen);
50
+ }
51
+ // name -> entry, last registration wins, exactly like Vue's collectRegistry Map.
52
+ export function toRegistry(screens) {
53
+ const registry = new Map();
54
+ for (const screen of screens)
55
+ registry.set(screen.name, screen);
56
+ return registry;
57
+ }
@@ -0,0 +1,28 @@
1
+ <script lang="ts">
2
+ // Stack.Screen: a declarative marker that paints nothing. Stack reads it through the
3
+ // context-based collector (screen-registry.ts) rather than by scanning children the way React
4
+ // and Vue can, then mounts the registered component itself for each pushed route.
5
+ //
6
+ // Every field is handed over as a GETTER over this component's own `$props()` bindings, so the
7
+ // navigator always sees the CURRENT value - reassigning `options` on an already-mounted marker
8
+ // reaches the next options fold without re-registering.
9
+ import { collectScreen } from './screen-registry';
10
+ import type { IScreenProps } from './screen-props';
11
+
12
+ let { name, component, options, initialParams }: IScreenProps = $props();
13
+
14
+ collectScreen('stack', {
15
+ get name(): IScreenProps['name'] {
16
+ return name;
17
+ },
18
+ get component(): IScreenProps['component'] {
19
+ return component;
20
+ },
21
+ get options(): IScreenProps['options'] {
22
+ return options;
23
+ },
24
+ get initialParams(): unknown {
25
+ return initialParams;
26
+ },
27
+ });
28
+ </script>
@@ -0,0 +1,4 @@
1
+ import type { IScreenProps } from './screen-props';
2
+ declare const Screen: import("svelte").Component<IScreenProps, {}, "">;
3
+ type Screen = ReturnType<typeof Screen>;
4
+ export default Screen;