@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,269 @@
1
+ <script lang="ts" module>
2
+ // Tab, the Svelte lifecycle half. The focused-index router (tab-router-state) and the tab-bar
3
+ // Descriptor builder (render-tabs) live in @symbiote-native/navigation core, shared verbatim
4
+ // with the React/Vue/Angular entries; here Svelte supplies the lifecycle - `$state.raw` for the
5
+ // router (its twin of useReducer), a module counter for route-key generation, `export
6
+ // function`s for the jumpTo/setParams handle - plus the Descriptor bridge for the tab-bar leaf.
7
+ // Unlike Stack, a bottom-tabs bar is a PURE-JS UI: it paints ordinary `symbiote-view`/
8
+ // `symbiote-text` primitives via the shared render fn, so there is no react-native-screens
9
+ // ViewConfig to register here - Tab needs no `../../register` import.
10
+ //
11
+ // Screens are discovered through the context collector (../screen-registry.ts), not by reading
12
+ // `children` - see stack/index.svelte's header for why, and ../registry-host.ts for where the
13
+ // markers are rendered.
14
+ let tabInstanceCounter = 0;
15
+
16
+ const TAB_ROOT_PROPS: Record<string, unknown> = { style: { flex: 1 } };
17
+ const TAB_CONTENT_PROPS: Record<string, unknown> = { style: { flex: 1 } };
18
+ </script>
19
+
20
+ <script lang="ts">
21
+ import type { Component } from 'svelte';
22
+ import { onDestroy, tick } from 'svelte';
23
+ import { dlog } from '@symbiote-native/engine';
24
+ import type { ShimElement } from '@symbiote-native/svelte/native-view-bridge';
25
+ import {
26
+ NAVIGATION_EVENT_BLUR,
27
+ NAVIGATION_EVENT_FOCUS,
28
+ createInitialTabState,
29
+ createNavigationEmitter,
30
+ diffFocusedRoute,
31
+ isFocusedRoute,
32
+ reconcileTabRoutes,
33
+ renderTabBar,
34
+ tabRouterReducer,
35
+ } from '../../core';
36
+ import type {
37
+ IDescriptor,
38
+ INavigationEmitter,
39
+ IRoute,
40
+ ITabBarItemView,
41
+ ITabNavigatorHandle,
42
+ ITabOptions,
43
+ ITabRouterAction,
44
+ ITabRouterState,
45
+ } from '../../core';
46
+ import { createDescriptorSubtreeSync } from '../descriptor-subtree';
47
+ import { getNavigationScope } from '../navigation-context';
48
+ import type { INavigationScopeValue } from '../navigation-context';
49
+ import NavigationScope from '../navigation-scope.svelte';
50
+ import { SCREEN_REGISTRY_HOST_PROPS } from '../registry-host';
51
+ import {
52
+ setScreenCollector,
53
+ toRegistry,
54
+ withoutScreen,
55
+ } from '../screen-registry';
56
+ import type { IRegisteredScreen } from '../screen-registry';
57
+ import type { ITabScreenProps } from '../tab-screen-props';
58
+ import type { ITabProps } from './tab-props';
59
+
60
+ let { initialRouteName, screenOptions, children }: ITabProps = $props();
61
+
62
+ // Read BEFORE this Tab establishes its own per-screen NavigationScope - becomes the `parent`
63
+ // link a nested screen's useNavigation().getParent() walks. undefined at the nesting root.
64
+ const parentScope = getNavigationScope();
65
+
66
+ const routeIdPrefix = `tab-${(tabInstanceCounter += 1)}`;
67
+
68
+ let screens = $state.raw<IRegisteredScreen<ITabScreenProps['options']>[]>([]);
69
+ setScreenCollector<ITabScreenProps['options']>({
70
+ kind: 'tab',
71
+ register: screen => {
72
+ screens = [...screens, screen];
73
+ },
74
+ unregister: screen => {
75
+ screens = withoutScreen(screens, screen);
76
+ },
77
+ });
78
+
79
+ const registry = $derived(toRegistry(screens));
80
+
81
+ // A tab's route list is a PROJECTION of the registry, not navigation history the way Stack's
82
+ // is: a marker registering or unregistering must add or drop its tab. Unlike Stack, a route key
83
+ // here is derived from the route NAME rather than an incrementing sequence, so re-deriving this
84
+ // list on every registry change re-keys nothing.
85
+ const registeredRoutes = $derived.by<IRoute<unknown>[]>(() =>
86
+ [...registry.entries()].map(([name, entry]) => ({
87
+ key: `${routeIdPrefix}-${name}`,
88
+ name,
89
+ params: entry.initialParams,
90
+ })),
91
+ );
92
+
93
+ // The dispatched half is the only STATE here; the registry half is re-derived and reconciled
94
+ // against it on every change (reconcileTabRoutes, core - it is what preserves each surviving
95
+ // route's key/params and keeps the focus on the same route NAME). Nothing needs memoizing
96
+ // inside the derivation, so there is no state write during a derivation to avoid in the first
97
+ // place. Until something dispatches there is nothing to reconcile against, hence the seed.
98
+ let dispatchedState = $state.raw<ITabRouterState | null>(null);
99
+
100
+ const state = $derived.by<ITabRouterState>(() => {
101
+ if (registeredRoutes.length === 0)
102
+ dlog('Tab: no <Tab.Screen> children registered');
103
+ const dispatched = dispatchedState;
104
+ return dispatched === null
105
+ ? createInitialTabState(registeredRoutes, initialRouteName)
106
+ : reconcileTabRoutes(dispatched, registeredRoutes);
107
+ });
108
+
109
+ function dispatch(action: ITabRouterAction): void {
110
+ dispatchedState = tabRouterReducer(state, action);
111
+ }
112
+
113
+ export function jumpTo(name: string, params?: unknown): void {
114
+ dispatch({ type: 'jumpTo', name, params });
115
+ }
116
+ export function setParams(params: unknown, key: string): void {
117
+ dispatch({ type: 'setParams', key, params });
118
+ }
119
+
120
+ const handle: ITabNavigatorHandle = { jumpTo, setParams };
121
+
122
+ // One emitter per route.key, created lazily and cached for the navigator's whole lifetime -
123
+ // mirrors Stack's own `emitters` map. This decouples emitter IDENTITY (stable, looked up by
124
+ // key, read below when a route's NavigationScope is built) from emit TIMING (must wait until
125
+ // the focused screen has actually mounted and subscribed).
126
+ const emitters = new Map<string, INavigationEmitter>();
127
+ function emitterFor(routeKey: string): INavigationEmitter {
128
+ let emitter = emitters.get(routeKey);
129
+ if (emitter === undefined) {
130
+ emitter = createNavigationEmitter();
131
+ emitters.set(routeKey, emitter);
132
+ }
133
+ return emitter;
134
+ }
135
+
136
+ // Tab paints its own bar in pure JS - there is no native onAppear/onDisappear to hook (unlike
137
+ // Stack's RNSScreen), so focus/blur is synthesized here. Keyed on the route KEY (not the route
138
+ // object) so a setParams-only change doesn't spuriously re-fire focus/blur.
139
+ //
140
+ // The bookkeeping updates immediately, but the actual emit is deferred to `tick()` - Svelte's
141
+ // twin of Vue's nextTick, and needed for the same reason: the newly-focused screen's own
142
+ // `$effect`s (its useIsFocused/useFocusEffect subscriptions) are created during THIS flush,
143
+ // after this effect was, so emitting synchronously here would reach zero subscribers.
144
+ let previousFocusedKey: string | undefined;
145
+ $effect(() => {
146
+ const nextKey = state.routes[state.index]?.key;
147
+ const { blurKey, focusKey } = diffFocusedRoute(previousFocusedKey, nextKey);
148
+ if (blurKey === undefined && focusKey === undefined) return;
149
+ previousFocusedKey = nextKey;
150
+ void tick().then(() => {
151
+ if (blurKey !== undefined) {
152
+ dlog(`Tab: route "${blurKey}" blurred at t=${Date.now()}`);
153
+ emitterFor(blurKey).emit(NAVIGATION_EVENT_BLUR);
154
+ }
155
+ if (focusKey !== undefined) {
156
+ dlog(`Tab: route "${focusKey}" focused at t=${Date.now()}`);
157
+ emitterFor(focusKey).emit(NAVIGATION_EVENT_FOCUS);
158
+ }
159
+ });
160
+ });
161
+
162
+ onDestroy(() => {
163
+ if (previousFocusedKey !== undefined) {
164
+ emitterFor(previousFocusedKey).emit(NAVIGATION_EVENT_BLUR);
165
+ }
166
+ });
167
+
168
+ function resolveTabOptions(
169
+ entry: IRegisteredScreen<ITabScreenProps['options']> | undefined,
170
+ route: IRoute<unknown>,
171
+ ): ITabOptions {
172
+ const own =
173
+ entry === undefined
174
+ ? undefined
175
+ : typeof entry.options === 'function'
176
+ ? entry.options({ route, navigation: handle })
177
+ : entry.options;
178
+ return { ...screenOptions, ...own };
179
+ }
180
+
181
+ const items = $derived.by<ITabBarItemView[]>(() =>
182
+ state.routes.map((route, index) => {
183
+ const entry = registry.get(route.name);
184
+ const focused = isFocusedRoute(index, state.index);
185
+ if (entry === undefined) {
186
+ dlog(`Tab: no screen registered for route name "${route.name}"`);
187
+ return { key: route.key, focused, label: route.name, passthrough: {} };
188
+ }
189
+ const options = resolveTabOptions(entry, route);
190
+ return {
191
+ key: route.key,
192
+ focused,
193
+ label: options.tabBarLabel ?? options.title ?? route.name,
194
+ icon: options.tabBarIcon,
195
+ badge: options.tabBarBadge,
196
+ activeTintColor: options.tabBarActiveTintColor,
197
+ inactiveTintColor: options.tabBarInactiveTintColor,
198
+ passthrough: {
199
+ onPress: () => jumpTo(route.name),
200
+ accessibilityRole: 'tab',
201
+ accessibilityState: { selected: focused },
202
+ },
203
+ };
204
+ }),
205
+ );
206
+
207
+ const focusedRoute = $derived(state.routes[state.index]);
208
+ const focusedOptions = $derived(
209
+ focusedRoute === undefined
210
+ ? screenOptions
211
+ : resolveTabOptions(registry.get(focusedRoute.name), focusedRoute),
212
+ );
213
+
214
+ const tabBar = $derived.by<IDescriptor>(() =>
215
+ renderTabBar({
216
+ items,
217
+ style: focusedOptions?.tabBarStyle,
218
+ passthrough: {},
219
+ }),
220
+ );
221
+
222
+ // Only the focused route's screen is ever mounted (unlike Stack, which keeps every pushed route
223
+ // alive), so a fresh NavigationScope per focus change is sufficient - the previous screen's
224
+ // whole subtree (and any listeners it registered) is torn down by an ordinary unmount when
225
+ // focus moves on.
226
+ const focusedScreen = $derived.by<
227
+ { scope: INavigationScopeValue; component: Component } | undefined
228
+ >(() => {
229
+ if (focusedRoute === undefined) return undefined;
230
+ const entry = registry.get(focusedRoute.name);
231
+ if (entry === undefined) return undefined;
232
+ return {
233
+ component: entry.component,
234
+ scope: {
235
+ route: focusedRoute,
236
+ navigation: handle,
237
+ emitter: emitterFor(focusedRoute.key),
238
+ parent: parentScope?.current,
239
+ },
240
+ };
241
+ });
242
+
243
+ // The bar's ROOT stays a literal template tag (so `bind:this` has a statically known tag) and
244
+ // only its children go through the Descriptor bridge - the uniform shape every Svelte component
245
+ // consuming a `render-*.ts` uses (svelte-adapter-dom-shim skill §19). The item count varies with
246
+ // the registry, hence the shape-change-tolerant wrapper rather than the raw bridge.
247
+ let tabBarHost = $state.raw<ShimElement | null>(null);
248
+ const syncTabBarChildren = createDescriptorSubtreeSync();
249
+ $effect(() => {
250
+ const host = tabBarHost;
251
+ const barChildren = tabBar.children;
252
+ syncTabBarChildren(host, barChildren);
253
+ });
254
+ </script>
255
+
256
+ <symbiote-view p={TAB_ROOT_PROPS}>
257
+ <symbiote-text p={SCREEN_REGISTRY_HOST_PROPS}>
258
+ {@render children?.()}
259
+ </symbiote-text>
260
+ <symbiote-view p={TAB_CONTENT_PROPS}>
261
+ {#if focusedScreen !== undefined}
262
+ {@const FocusedComponent = focusedScreen.component}
263
+ <NavigationScope value={focusedScreen.scope}>
264
+ <FocusedComponent />
265
+ </NavigationScope>
266
+ {/if}
267
+ </symbiote-view>
268
+ <symbiote-view p={tabBar.props} bind:this={tabBarHost} />
269
+ </symbiote-view>
@@ -0,0 +1,8 @@
1
+ import type { Component } from 'svelte';
2
+ import type { ITabProps } from './tab-props';
3
+ declare const Index: Component<ITabProps, {
4
+ jumpTo: (name: string, params?: unknown) => void;
5
+ setParams: (params: unknown, key: string) => void;
6
+ }, "">;
7
+ type Index = ReturnType<typeof Index>;
8
+ export default Index;
@@ -0,0 +1,7 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { ITabOptions } from '../../core';
3
+ export type ITabProps = {
4
+ initialRouteName?: string;
5
+ screenOptions?: ITabOptions;
6
+ children?: Snippet;
7
+ };
@@ -0,0 +1,3 @@
1
+ // Tab's own public prop surface. Plain `.ts` for the tsc-cannot-see-a-`.svelte`-named-export
2
+ // reason ../screen-props.ts documents.
3
+ export {};
@@ -8,6 +8,10 @@ export function useNavigation() {
8
8
  const scope = requireNavigationScope('useNavigation');
9
9
  return computed(() => {
10
10
  const { navigation, emitter, parent } = scope.value;
11
- return { ...navigation, addListener: emitter.addListener, getParent: () => parent?.navigation };
11
+ return {
12
+ ...navigation,
13
+ addListener: emitter.addListener,
14
+ getParent: () => parent?.navigation,
15
+ };
12
16
  });
13
17
  }
@@ -35,7 +35,12 @@ function asBoolean(value) {
35
35
  function isStyleProp(value) {
36
36
  return isRecord(value);
37
37
  }
38
- const DRAWER_TYPES = ['front', 'back', 'slide', 'permanent'];
38
+ const DRAWER_TYPES = [
39
+ 'front',
40
+ 'back',
41
+ 'slide',
42
+ 'permanent',
43
+ ];
39
44
  // .some() itself doesn't narrow `value`'s type back at the call site - the `value is IDrawerType`
40
45
  // predicate on THIS function is what lets asDrawerType's ternary return `value` typed, mirroring
41
46
  // Modal's isOrientation guard (adapters/vue/src/components/modal.ts).
@@ -47,7 +52,8 @@ function asDrawerType(value) {
47
52
  }
48
53
  const DRAWER_POSITIONS = ['left', 'right'];
49
54
  function isDrawerPosition(value) {
50
- return typeof value === 'string' && DRAWER_POSITIONS.some(position => position === value);
55
+ return (typeof value === 'string' &&
56
+ DRAWER_POSITIONS.some(position => position === value));
51
57
  }
52
58
  function asDrawerPosition(value) {
53
59
  return isDrawerPosition(value) ? value : undefined;
@@ -55,7 +61,9 @@ function asDrawerPosition(value) {
55
61
  function asComponent(value) {
56
62
  if (typeof value === 'function')
57
63
  return value;
58
- return isRecord(value) ? value : undefined;
64
+ return isRecord(value)
65
+ ? value
66
+ : undefined;
59
67
  }
60
68
  // IDrawerScreenOptions carries only `title`/`drawerLabel`, small enough for direct field guards -
61
69
  // kept as a single cast-at-the-edge helper anyway for symmetry with stack.ts/tabs.ts's
@@ -166,13 +174,14 @@ const DrawerImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose })
166
174
  dispatch({ type: 'toggleDrawer' });
167
175
  },
168
176
  jumpTo: (name) => {
169
- // Captured BEFORE dispatch: unlike React's isOpenRef (only refreshed at the TOP of the
170
- // next render, so it still holds the pre-dispatch value here since React batches the
171
- // re-render asynchronously), this ref's `.value` mutates SYNCHRONOUSLY inside dispatch -
172
- // reading it after dispatch would already see the reducer's own isOpen: false.
177
+ // Both sides of the dispatch, because an unregistered name is a documented reducer no-op
178
+ // that hands the SAME state back: animating off the pre-dispatch snapshot alone would
179
+ // slide the panel shut while the router still says isOpen. The ref's `.value` mutates
180
+ // SYNCHRONOUSLY inside dispatch (unlike React's isOpenRef, refreshed only at the top of
181
+ // the next render), so the second read is already the reducer's own answer.
173
182
  const wasOpen = state.value.isOpen;
174
183
  dispatch({ type: 'jumpTo', name });
175
- if (wasOpen)
184
+ if (wasOpen && !state.value.isOpen)
176
185
  animateProgressTo(false);
177
186
  },
178
187
  };
@@ -256,7 +265,9 @@ const DrawerImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose })
256
265
  return () => {
257
266
  const registry = collectRegistry(slots.default?.() ?? []);
258
267
  const options = currentOptions();
259
- const drawerStyle = isStyleProp(attrs.drawerStyle) ? attrs.drawerStyle : undefined;
268
+ const drawerStyle = isStyleProp(attrs.drawerStyle)
269
+ ? attrs.drawerStyle
270
+ : undefined;
260
271
  if (registry.size === 0)
261
272
  dlog('Drawer: no <Drawer.Screen> children registered');
262
273
  const animated = isDrawerAnimated(options);
@@ -283,7 +294,9 @@ const DrawerImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose })
283
294
  ? progress.interpolate(overlayInterpolation.translateX)
284
295
  : undefined;
285
296
  const focusedRoute = state.value.routes[state.value.index];
286
- const focusedEntry = focusedRoute ? registry.get(focusedRoute.name) : undefined;
297
+ const focusedEntry = focusedRoute
298
+ ? registry.get(focusedRoute.name)
299
+ : undefined;
287
300
  if (focusedRoute && !focusedEntry) {
288
301
  dlog(`Drawer: no screen registered for route name "${focusedRoute.name}"`);
289
302
  }
@@ -306,11 +319,18 @@ const DrawerImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose })
306
319
  if (entry === undefined)
307
320
  continue;
308
321
  descriptors[route.key] = {
309
- options: resolveDrawerScreenOptions(entry, { route, navigation: handle }),
322
+ options: resolveDrawerScreenOptions(entry, {
323
+ route,
324
+ navigation: handle,
325
+ }),
310
326
  navigation: handle,
311
327
  };
312
328
  }
313
- const drawerContent = slots.drawerContent?.({ state: state.value, descriptors, navigation: handle }) ?? null;
329
+ const drawerContent = slots.drawerContent?.({
330
+ state: state.value,
331
+ descriptors,
332
+ navigation: handle,
333
+ }) ?? null;
314
334
  const root = renderDrawer({
315
335
  overlayColor: asString(attrs.overlayColor) ?? DRAWER_DEFAULT_OVERLAY_COLOR,
316
336
  drawerStyle,
@@ -348,7 +368,10 @@ const DrawerImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose })
348
368
  : { transform: [{ translateX: contentTranslateX }] },
349
369
  overlay: overlayOpacity === undefined
350
370
  ? undefined
351
- : { opacity: overlayOpacity, transform: [{ translateX: overlayTranslateX }] },
371
+ : {
372
+ opacity: overlayOpacity,
373
+ transform: [{ translateX: overlayTranslateX }],
374
+ },
352
375
  panel: panelTranslateX === undefined
353
376
  ? undefined
354
377
  : { transform: [{ translateX: panelTranslateX }] },
@@ -3,10 +3,10 @@ export { Stack } from './stack';
3
3
  export type { INavigatorHandle, IStackProps } from './stack';
4
4
  export { Screen } from './screen';
5
5
  export type { IScreenOptionsArgs, IScreenOptionsResolver, IScreenProps, IVueScreenOptions, IVueSearchBarOptions, } from './screen';
6
- export type { IScreenOptions, IStackAnimation, IStackPresentation } from '../core';
6
+ export type { IScreenOptions, IStackAnimation, IStackPresentation, } from '../core';
7
7
  export { useNavigation, useStackNavigation, useTabNavigation, useDrawerNavigation, useRoute, useIsFocused, useFocusEffect, useNavigationState, } from './composables';
8
8
  export type { INavigationHandle, IStackNavigationHandle, ITabNavigationHandle, IDrawerNavigationHandle, } from './composables';
9
- export type { INavigationScopeValue, IAnyNavigatorHandle } from './navigation-context';
9
+ export type { INavigationScopeValue, IAnyNavigatorHandle, } from './navigation-context';
10
10
  export { useLinkingIntegration } from './linking';
11
11
  export type { ILinkingConfig, IScreenLinkingConfig } from '../core';
12
12
  export { Tab } from './tabs';
@@ -18,4 +18,4 @@ export { Drawer } from './drawer';
18
18
  export type { IDrawerContentSlotProps, IDrawerNavigatorHandle, IDrawerProps, IDrawerDescriptorMap, } from './drawer';
19
19
  export { DrawerScreen } from './drawer-screen';
20
20
  export type { IDrawerScreenOptionsArgs, IDrawerScreenOptionsResolver, IDrawerScreenProps, } from './drawer-screen';
21
- export type { IDrawerOptions, IDrawerScreenOptions, IDrawerType, IDrawerPosition } from '../core';
21
+ export type { IDrawerOptions, IDrawerScreenOptions, IDrawerType, IDrawerPosition, } from '../core';
@@ -26,7 +26,7 @@
26
26
  // into the value it provides to its own screens, forming a linked list - this is what lets a
27
27
  // screen nested inside e.g. a Stack-screen-renders-a-Tab composition reach the enclosing Stack via
28
28
  // useNavigation().getParent().
29
- import { defineComponent, inject, provide, shallowRef } from '@vue/runtime-core';
29
+ import { defineComponent, inject, provide, shallowRef, } from '@vue/runtime-core';
30
30
  const NAVIGATION_SCOPE_KEY = Symbol('navigation-scope');
31
31
  // The setter half of the pair - writes the CURRENT route's scope onto the shared key. Called only
32
32
  // from NavigationScope's own setup below (never directly by app code).
@@ -11,14 +11,16 @@
11
11
  // registers. See CLAUDE.md <third_party_rn_packages_are_react_only>.
12
12
  import { defineComponent, h, shallowRef, useId } from '@vue/runtime-core';
13
13
  import { descriptorToVue, normalizeVueAttrs } from '@symbiote-native/vue';
14
- import { Platform, debugNodeId, dlog, isSymbioteNode } from '@symbiote-native/engine';
15
- import { NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS, NAVIGATION_EVENT_STATE, RNS_SCREEN_CONTENT_WRAPPER_VIEW_NAME, RNS_SCREEN_STACK_VIEW_NAME, RNS_SCREEN_VIEW_NAME, SCREEN_ON_APPEAR, SCREEN_ON_DISAPPEAR, SCREEN_ON_DISMISSED, SCREEN_ON_HEADER_BACK_BUTTON_CLICKED, SCREEN_ON_WILL_APPEAR, SCREEN_ON_WILL_DISAPPEAR, STACK_ON_FINISH_TRANSITIONING, buildSearchBarHandle, buildSearchBarPassthrough, computeActivityState, createInitialNavigatorState, createNavigationEmitter, isRecord, navigatorReducer, resolveScreenRenderPlan, resolveStackProps, } from '../../core/index.js';
14
+ import { Platform, debugNodeId, dlog, isSymbioteNode, } from '@symbiote-native/engine';
15
+ import { NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS, NAVIGATION_EVENT_STATE, RNS_SCREEN_CONTENT_WRAPPER_VIEW_NAME, RNS_SCREEN_STACK_VIEW_NAME, RNS_SCREEN_VIEW_NAME, SCREEN_ON_APPEAR, SCREEN_ON_DISAPPEAR, SCREEN_ON_DISMISSED, SCREEN_ON_HEADER_BACK_BUTTON_CLICKED, SCREEN_ON_WILL_APPEAR, SCREEN_ON_WILL_DISAPPEAR, STACK_ON_FINISH_TRANSITIONING, buildSearchBarHandle, buildSearchBarPassthrough, computeActivityState, createInitialNavigatorState, createNavigationEmitter, isRecord, navigatorReducer, reconcileStackRoutes, resolveScreenRenderPlan, resolveStackProps, } from '../../core/index.js';
16
16
  import { NavigationScope, injectNavigationScope } from '../navigation-context.js';
17
17
  import { Screen } from '../screen.js';
18
18
  // backTitleVisible defaults to `true` on both platforms per the codegen spec's own default
19
19
  // (CT.WithDefault<boolean, 'true'>) - no ios/android divergence in v1 scope, so a single constant
20
20
  // stands in for the per-platform injection point ISliderPlatform-style adapters use elsewhere.
21
- const NAVIGATOR_PLATFORM = { defaultHeaderBackTitleVisible: true };
21
+ const NAVIGATOR_PLATFORM = {
22
+ defaultHeaderBackTitleVisible: true,
23
+ };
22
24
  function asString(value) {
23
25
  return typeof value === 'string' ? value : undefined;
24
26
  }
@@ -58,7 +60,9 @@ function collectRegistry(vnodes) {
58
60
  return registry;
59
61
  }
60
62
  function resolveScreenOptions(entry, screenOptionsArgs, screenOptions) {
61
- const own = typeof entry.options === 'function' ? entry.options(screenOptionsArgs) : entry.options;
63
+ const own = typeof entry.options === 'function'
64
+ ? entry.options(screenOptionsArgs)
65
+ : entry.options;
62
66
  return { ...screenOptions, ...own };
63
67
  }
64
68
  const StackImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose }) => {
@@ -106,8 +110,20 @@ const StackImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose }) =
106
110
  initialState = createInitialNavigatorState(createRoute(initialRouteName, initialRegistry.get(initialRouteName)?.initialParams));
107
111
  }
108
112
  const state = shallowRef(initialState);
113
+ // The screen names the render pass below last saw in the slot. A PLAIN local, not a ref: it is
114
+ // a cache of what was just rendered, never a reactive source, so refreshing it from inside the
115
+ // render closure is not a state write during render (which would re-trigger the very render
116
+ // effect that produced it).
117
+ let registeredNames = [];
118
+ // A <Stack.Screen> marker can vanish from the slot while its route is still in the pushed
119
+ // history, which would leave that entry with nothing to render (reconcileStackRoutes' header).
120
+ // Reconciling on READ rather than writing the ref keeps the repair out of the render pass; the
121
+ // dispatch below then persists it, so the next push builds on the pruned history.
122
+ function currentState() {
123
+ return reconcileStackRoutes(state.value, registeredNames);
124
+ }
109
125
  function dispatch(action) {
110
- state.value = navigatorReducer(state.value, action);
126
+ state.value = navigatorReducer(currentState(), action);
111
127
  }
112
128
  const handle = {
113
129
  push: (name, params) => dispatch({ type: 'push', route: createRoute(name, params) }),
@@ -117,7 +133,7 @@ const StackImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose }) =
117
133
  replace: (name, params) => dispatch({ type: 'replace', route: createRoute(name, params) }),
118
134
  setParams: (params, key) => dispatch({ type: 'setParams', key, params }),
119
135
  reset: nextState => dispatch({ type: 'reset', state: nextState }),
120
- canGoBack: () => state.value.routes.length > 1,
136
+ canGoBack: () => currentState().routes.length > 1,
121
137
  };
122
138
  expose(handle);
123
139
  // Broadcasts the router state to every still-live route's emitter (useNavigationState's
@@ -126,18 +142,20 @@ const StackImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose }) =
126
142
  // useEffect, which must be deferred past render to avoid a descendant setState-during-render):
127
143
  // Vue's provide/inject + shallowRef.value writes are safe to perform directly inside the
128
144
  // render closure below, so the broadcast happens right there, once per render.
129
- function broadcastState() {
130
- for (const route of state.value.routes) {
131
- emitterFor(route.key).emit(NAVIGATION_EVENT_STATE, state.value);
145
+ function broadcastState(current) {
146
+ for (const route of current.routes) {
147
+ emitterFor(route.key).emit(NAVIGATION_EVENT_STATE, current);
132
148
  }
133
149
  for (const routeKey of emitters.keys()) {
134
- if (!state.value.routes.some(route => route.key === routeKey))
150
+ if (!current.routes.some(route => route.key === routeKey))
135
151
  emitters.delete(routeKey);
136
152
  }
137
153
  }
138
154
  return () => {
139
- broadcastState();
140
155
  const registry = collectRegistry(slots.default?.() ?? []);
156
+ registeredNames = [...registry.keys()];
157
+ const current = currentState();
158
+ broadcastState(current);
141
159
  const screenOptions = asScreenOptions(attrs.screenOptions);
142
160
  // Investigation instrumentation (flicker-on-focus bug): STACK_ON_FINISH_TRANSITIONING is
143
161
  // the native signal that the WHOLE push/pop animation has finished (as opposed to
@@ -149,26 +167,29 @@ const StackImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose }) =
149
167
  [STACK_ON_FINISH_TRANSITIONING]: () => dlog(`Stack: onFinishTransitioning at t=${Date.now()}`),
150
168
  },
151
169
  });
152
- const children = state.value.routes.map((route, index) => {
170
+ const children = current.routes.map((route, index) => {
153
171
  const entry = registry.get(route.name);
154
172
  if (entry === undefined) {
155
173
  dlog(`Stack: no screen registered for route name "${route.name}"`);
156
174
  return null;
157
175
  }
158
- const screenOptionsArgs = { route, navigation: handle };
176
+ const screenOptionsArgs = {
177
+ route,
178
+ navigation: handle,
179
+ };
159
180
  const mergedOptions = resolveScreenOptions(entry, screenOptionsArgs, screenOptions);
160
- const activityState = computeActivityState(index, state.value.routes.length);
181
+ const activityState = computeActivityState(index, current.routes.length);
161
182
  // Investigation instrumentation (flicker-on-focus bug): fires on EVERY Stack render, not
162
183
  // just on transitions, so the log stream shows whether a route's activityState/index ever
163
184
  // changes outside of a push/pop dispatch. Kept behind DEBUG, never removed.
164
- dlog(`Stack: render route "${route.name}" index=${index}/${state.value.routes.length - 1} ` +
185
+ dlog(`Stack: render route "${route.name}" index=${index}/${current.routes.length - 1} ` +
165
186
  `activityState=${activityState} at t=${Date.now()}`);
166
187
  const routeEmitter = emitterFor(route.key);
167
188
  const searchBarOptions = mergedOptions.headerSearchBarOptions;
168
189
  const plan = resolveScreenRenderPlan({
169
190
  screenId: route.key,
170
191
  index,
171
- routeCount: state.value.routes.length,
192
+ routeCount: current.routes.length,
172
193
  options: mergedOptions,
173
194
  platform: NAVIGATOR_PLATFORM,
174
195
  isAndroid: Platform.OS === 'android',
@@ -210,7 +231,8 @@ const StackImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose }) =
210
231
  const appRef = searchBarOptions.ref;
211
232
  if (appRef === undefined)
212
233
  return;
213
- appRef.value = node === null ? null : buildSearchBarHandle(() => node);
234
+ appRef.value =
235
+ node === null ? null : buildSearchBarHandle(() => node);
214
236
  },
215
237
  }
216
238
  : undefined,
@@ -265,7 +287,10 @@ const StackImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose }) =
265
287
  // RNSScreen.mm treats an unset/inactive nested screen as not yet pushed,
266
288
  // leaving it parked at its pre-push transition position (off past the bottom
267
289
  // edge) instead of its real, presented frame.
268
- { style: plan.innerScreenStyle, activityState: plan.activityState }, [descriptorToVue(plan.headerConfig), content]),
290
+ {
291
+ style: plan.innerScreenStyle,
292
+ activityState: plan.activityState,
293
+ }, [descriptorToVue(plan.headerConfig), content]),
269
294
  ]),
270
295
  ])
271
296
  : h(plan.screenViewName, { key: route.key, ...plan.screenProps }, [