@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,327 @@
1
+ // Tab, the Solid lifecycle half. The focused-index router (tab-router-state) and the tab-bar
2
+ // Descriptor builder (render-tabs) live in @symbiote-native/navigation core, shared verbatim with
3
+ // every other adapter; here Solid supplies the lifecycle - a signal for the dispatched router
4
+ // state, a callback `ref` for the jumpTo/setParams handle - plus the descriptor bridge for the
5
+ // tab-bar leaf. Unlike Stack, a bottom-tabs bar is a PURE-JS UI painting ordinary
6
+ // `symbiote-view`/`symbiote-text` primitives, so there is no react-native-screens ViewConfig to
7
+ // register here.
8
+ //
9
+ // TWO SOLID-ONLY REBUILD BOUNDARIES, both invisible in every other adapter:
10
+ //
11
+ // 1. THE TAB BAR'S SHAPE FOLLOWS ITS DATA. renderTabBar emits one child per item, and each item
12
+ // emits an icon wrapper only when it has an icon or a badge - so adding a screen, or a badge
13
+ // appearing, CHANGES the Descriptor's shape, which descriptorToSolid's shape guard rejects by
14
+ // design (it builds once and updates props in place). `barSignature` captures exactly the parts
15
+ // of the data the shape depends on, and the bar is rebuilt when that string changes and only
16
+ // then. Every other adapter re-renders the whole bar every time and never meets this.
17
+ //
18
+ // 2. ONLY THE FOCUSED SCREEN IS MOUNTED, so a focus change is a genuine rebuild while a
19
+ // `setParams` on the focused route must NOT be. The content memo therefore depends on the
20
+ // focused route KEY and its component identity alone, with the build `untrack`ed
21
+ // (.claude/rules/solid-descriptor-bridge.md §5); the route object itself reaches the screen
22
+ // through the scope accessor, which is what keeps useRoute() live without rebuilding anything.
23
+
24
+ import { createEffect, createMemo, createSignal, untrack } from 'solid-js';
25
+ import { descriptorToSolid } from '@symbiote-native/solid';
26
+ import type { JSX } from '@symbiote-native/solid/jsx-runtime';
27
+ // createComponent from solid-js, insert from the renderer - see stack/index.ts's note.
28
+ import { createComponent } from 'solid-js';
29
+ import { insert } from '@symbiote-native/solid/renderer';
30
+ import type { ISymbioteNode } from '@symbiote-native/engine';
31
+ import { dlog } from '@symbiote-native/engine';
32
+ import {
33
+ NAVIGATION_EVENT_BLUR,
34
+ NAVIGATION_EVENT_FOCUS,
35
+ createInitialTabState,
36
+ createNavigationEmitter,
37
+ diffFocusedRoute,
38
+ isFocusedRoute,
39
+ reconcileTabRoutes,
40
+ renderTabBar,
41
+ tabRouterReducer,
42
+ } from '../../core';
43
+ import type {
44
+ INavigationEmitter,
45
+ IRoute,
46
+ ITabBarIcon,
47
+ ITabBarItemView,
48
+ ITabNavigatorHandle,
49
+ ITabOptions,
50
+ ITabRouterAction,
51
+ ITabRouterState,
52
+ } from '../../core';
53
+ import { hostElement } from '../host';
54
+ import {
55
+ NavigationScopeProvider,
56
+ useNavigationScope,
57
+ } from '../navigation-context';
58
+ import type { INavigationScope } from '../navigation-context';
59
+ import {
60
+ ScreenCollectorProvider,
61
+ createScreenSignal,
62
+ toRegistry,
63
+ } from '../screen-registry';
64
+ import type { IRegisteredScreen } from '../screen-registry';
65
+ import { TabScreen } from '../screen';
66
+ import type {
67
+ ITabScreenOptionsArgs,
68
+ ITabScreenProps,
69
+ } from '../tab-screen-props';
70
+
71
+ export type { ITabNavigatorHandle } from '../../core';
72
+
73
+ export type ITabProps = {
74
+ initialRouteName?: string;
75
+ screenOptions?: ITabOptions;
76
+ ref?: (handle: ITabNavigatorHandle) => void;
77
+ children?: JSX.Element;
78
+ };
79
+
80
+ const TAB_CONTENT_STYLE = { flex: 1 };
81
+ const TAB_ROOT_STYLE = { flex: 1 };
82
+
83
+ let navigatorSequence = 0;
84
+
85
+ type ITabScreenEntry = IRegisteredScreen<ITabScreenProps['options']>;
86
+
87
+ function resolveTabOptions(
88
+ entry: ITabScreenEntry,
89
+ args: ITabScreenOptionsArgs,
90
+ screenOptions: ITabOptions | undefined,
91
+ ): ITabOptions {
92
+ const own =
93
+ typeof entry.options === 'function' ? entry.options(args) : entry.options;
94
+ return { ...screenOptions, ...own };
95
+ }
96
+
97
+ // A Descriptor icon is spliced into the bar verbatim, so only its ROOT type is visible here - an
98
+ // app that swaps one Descriptor icon for another of the same type but a different child count
99
+ // would still trip the bridge's shape guard, with the guard's own message. That is the honest
100
+ // boundary: this signature covers the shape decisions render-tabs.ts itself makes.
101
+ function iconKind(icon: ITabBarIcon | undefined): string {
102
+ if (icon === undefined) return '-';
103
+ return typeof icon === 'string' ? 'glyph' : icon.type;
104
+ }
105
+
106
+ function barSignature(items: readonly ITabBarItemView[]): string {
107
+ return items
108
+ .map(
109
+ item =>
110
+ `${item.key}:${iconKind(item.icon)}:${item.badge === undefined ? 0 : 1}`,
111
+ )
112
+ .join('|');
113
+ }
114
+
115
+ function TabImpl(props: ITabProps): JSX.Element {
116
+ // Read BEFORE this Tab provides its own per-screen scope - becomes the `parent` link a nested
117
+ // screen's useNavigation().getParent() walks. undefined when this Tab is the nesting root.
118
+ const parentScope = useNavigationScope();
119
+
120
+ const { screens, collector } = createScreenSignal<
121
+ 'tab',
122
+ ITabScreenProps['options']
123
+ >('tab');
124
+ const registry = createMemo(() => toRegistry(screens()));
125
+
126
+ navigatorSequence += 1;
127
+ const routeIdPrefix = `tab-${navigatorSequence}`;
128
+
129
+ // Route keys are derived from the screen NAME, never a counter: a Tab's route list is rebuilt
130
+ // from the registry on every registration change, and a counter would mint a fresh key each time,
131
+ // re-mounting every screen for an unrelated marker edit.
132
+ const routes = createMemo<readonly IRoute<unknown>[]>(() =>
133
+ [...registry().entries()].map(([name, entry]) => ({
134
+ key: `${routeIdPrefix}-${name}`,
135
+ name,
136
+ params: entry.initialParams,
137
+ })),
138
+ );
139
+
140
+ // `null` means nothing has dispatched yet, which is what keeps initialRouteName honored when the
141
+ // markers arrive after the body has already run (screen-registry.ts).
142
+ const [dispatched, setDispatched] = createSignal<ITabRouterState | null>(
143
+ null,
144
+ );
145
+
146
+ const state = createMemo<ITabRouterState>(() => {
147
+ const current = routes();
148
+ const previous = dispatched();
149
+ return previous === null
150
+ ? createInitialTabState(current, props.initialRouteName)
151
+ : reconcileTabRoutes(previous, current);
152
+ });
153
+
154
+ function dispatch(action: ITabRouterAction): void {
155
+ setDispatched(tabRouterReducer(state(), action));
156
+ }
157
+
158
+ const jumpTo = (name: string, params?: unknown): void =>
159
+ dispatch({ type: 'jumpTo', name, params });
160
+ const setParams = (params: unknown, key: string): void =>
161
+ dispatch({ type: 'setParams', key, params });
162
+
163
+ const handle: ITabNavigatorHandle = { jumpTo, setParams };
164
+ props.ref?.(handle);
165
+
166
+ // One emitter per route.key, created lazily and cached for the navigator's whole lifetime. Emitter
167
+ // IDENTITY (stable, looked up by key) is deliberately decoupled from emit TIMING (deferred below):
168
+ // a scheme that recreated the emitter on each focus change raced the build of the very screen that
169
+ // was supposed to subscribe to it.
170
+ const emitters = new Map<string, INavigationEmitter>();
171
+ function emitterFor(routeKey: string): INavigationEmitter {
172
+ let emitter = emitters.get(routeKey);
173
+ if (emitter === undefined) {
174
+ emitter = createNavigationEmitter();
175
+ emitters.set(routeKey, emitter);
176
+ }
177
+ return emitter;
178
+ }
179
+
180
+ const focusedKey = createMemo(() => {
181
+ const current = state();
182
+ return current.routes[current.index]?.key;
183
+ });
184
+
185
+ // Tab paints its own bar in pure JS - there is no native onAppear/onDisappear to hook (unlike
186
+ // Stack's RNSScreen), so focus/blur is synthesized here. The emit is deferred to a microtask,
187
+ // which resolves only after Solid's whole effect flush has drained - including the build of the
188
+ // newly focused screen and the subscriptions createIsFocused/createFocusEffect register inside
189
+ // it. Emitting inline would reach zero subscribers.
190
+ let lastFocusedKey: string | undefined;
191
+ createEffect(() => {
192
+ const nextKey = focusedKey();
193
+ const { blurKey, focusKey } = diffFocusedRoute(lastFocusedKey, nextKey);
194
+ if (blurKey === undefined && focusKey === undefined) return;
195
+ lastFocusedKey = nextKey;
196
+ queueMicrotask(() => {
197
+ if (blurKey !== undefined) {
198
+ dlog(`Tab: route "${blurKey}" blurred at t=${Date.now()}`);
199
+ emitterFor(blurKey).emit(NAVIGATION_EVENT_BLUR);
200
+ }
201
+ if (focusKey !== undefined) {
202
+ dlog(`Tab: route "${focusKey}" focused at t=${Date.now()}`);
203
+ emitterFor(focusKey).emit(NAVIGATION_EVENT_FOCUS);
204
+ }
205
+ });
206
+ });
207
+
208
+ const items = createMemo<readonly ITabBarItemView[]>(() => {
209
+ const current = state();
210
+ const entries = registry();
211
+ return current.routes.map((route, index) => {
212
+ const entry = entries.get(route.name);
213
+ const focused = isFocusedRoute(index, current.index);
214
+ if (entry === undefined) {
215
+ dlog(`Tab: no screen registered for route name "${route.name}"`);
216
+ return { key: route.key, focused, label: route.name, passthrough: {} };
217
+ }
218
+ const options = resolveTabOptions(
219
+ entry,
220
+ { route, navigation: handle },
221
+ props.screenOptions,
222
+ );
223
+ return {
224
+ key: route.key,
225
+ focused,
226
+ label: options.tabBarLabel ?? options.title ?? route.name,
227
+ icon: options.tabBarIcon,
228
+ badge: options.tabBarBadge,
229
+ activeTintColor: options.tabBarActiveTintColor,
230
+ inactiveTintColor: options.tabBarInactiveTintColor,
231
+ passthrough: {
232
+ onPress: () => jumpTo(route.name),
233
+ accessibilityRole: 'tab',
234
+ accessibilityState: { selected: focused },
235
+ },
236
+ };
237
+ });
238
+ });
239
+
240
+ const focusedOptions = createMemo<ITabOptions | undefined>(() => {
241
+ const current = state();
242
+ const route = current.routes[current.index];
243
+ const entry = route === undefined ? undefined : registry().get(route.name);
244
+ if (entry === undefined || route === undefined) return props.screenOptions;
245
+ return resolveTabOptions(
246
+ entry,
247
+ { route, navigation: handle },
248
+ props.screenOptions,
249
+ );
250
+ });
251
+
252
+ // Header note 1: the bar is rebuilt only when its Descriptor SHAPE would change; every tint,
253
+ // label and badge-value change flows through descriptorToSolid's prop diff on the live nodes.
254
+ const barShape = createMemo(() => barSignature(items()));
255
+ const tabBar = createMemo(() => {
256
+ barShape();
257
+ return untrack(() =>
258
+ descriptorToSolid(() =>
259
+ renderTabBar({
260
+ items: items(),
261
+ style: focusedOptions()?.tabBarStyle,
262
+ passthrough: {},
263
+ }),
264
+ ),
265
+ );
266
+ });
267
+
268
+ // Header note 2. Only the focused route's screen is ever mounted (unlike Stack, which keeps every
269
+ // pushed route alive), so the previous screen's whole subtree - and every listener it registered
270
+ // - is torn down by Solid's ordinary disposal when focus moves on.
271
+ const focusedComponent = createMemo(() => {
272
+ const current = state();
273
+ const route = current.routes[current.index];
274
+ return route === undefined
275
+ ? undefined
276
+ : registry().get(route.name)?.component;
277
+ });
278
+
279
+ const content = createMemo(() => {
280
+ const routeKey = focusedKey();
281
+ const Component = focusedComponent();
282
+ return untrack(() => {
283
+ if (routeKey === undefined || Component === undefined) return undefined;
284
+ const scope: INavigationScope = () => ({
285
+ route: routeFor(routeKey),
286
+ navigation: handle,
287
+ emitter: emitterFor(routeKey),
288
+ parent: parentScope?.(),
289
+ });
290
+ return createComponent(NavigationScopeProvider, {
291
+ value: scope,
292
+ // No route/navigation props: the screen reads both through the primitives off the scope
293
+ // provided here (useRoute / useTabNavigation).
294
+ get children(): JSX.Element {
295
+ return createComponent(Component, {});
296
+ },
297
+ });
298
+ });
299
+ });
300
+
301
+ // Reads the CURRENT object for a key rather than closing over the one that was focused at build
302
+ // time - the accessor is what makes `setParams` reach a mounted screen's useRoute().
303
+ function routeFor(routeKey: string): IRoute<unknown> {
304
+ const found = state().routes.find(route => route.key === routeKey);
305
+ return found ?? { key: routeKey, name: '', params: undefined };
306
+ }
307
+
308
+ const contentHost = hostElement('symbiote-view', () => ({
309
+ style: TAB_CONTENT_STYLE,
310
+ }));
311
+ insert(contentHost, content);
312
+
313
+ const root = hostElement('symbiote-view', () => ({ style: TAB_ROOT_STYLE }));
314
+ // ONE array insert rather than two: a second `insert` on the same parent would find the first
315
+ // child already there and REPLACE it (solid-js/universal's insertExpression), while an array is
316
+ // reconciled by identity - contentHost keeps its node, only the bar is swapped when it rebuilds.
317
+ insert(root, (): readonly ISymbioteNode[] => [contentHost, tabBar()]);
318
+
319
+ return createComponent(ScreenCollectorProvider, {
320
+ value: collector,
321
+ get children(): JSX.Element {
322
+ return [props.children, root];
323
+ },
324
+ });
325
+ }
326
+
327
+ export const Tab = Object.assign(TabImpl, { Screen: TabScreen });
@@ -0,0 +1,70 @@
1
+ // Svelte attachments (`{@attach ...}`) for the react-native-screens native leaves.
2
+ //
3
+ // WHY THESE EXIST AT ALL: every react-native-screens Fabric view name is capitalized and
4
+ // un-hyphenated (RNSScreen, RNSScreenStack, RNSScreenStackHeaderConfig, RNSSearchBar, ...), so a
5
+ // literal `<RNSScreen>` in a Svelte template parses as a COMPONENT reference, not an element -
6
+ // the same wall @symbiote-native/slider's 'RNCSlider' leaf hit. Slider sidestepped it by mounting
7
+ // the whole leaf through the Descriptor bridge, which works because that leaf carries no live
8
+ // framework children. A stack screen DOES (the app's own screen component lives inside
9
+ // RNSScreenContentWrapper), so the bridge is not an option here.
10
+ //
11
+ // The way through is `<svelte:element this={'RNSScreen'}>`: it creates the element via
12
+ // `document.createElement(tag)` with a plain runtime string the compiler never inspects, and it
13
+ // accepts ordinary framework children. Its one catch is documented in svelte-adapter-dom-shim
14
+ // skill §15 - a dynamic tag compiles through Svelte's generic setAttribute/property-diffing
15
+ // codegen, NOT the custom-element `p=` property-SET path, so writing `p={bag}` as an ATTRIBUTE on
16
+ // it silently fails. An attachment bypasses that entirely: it is handed the raw element and
17
+ // assigns the property from plain JS, then re-runs whenever the props it read change. Verified
18
+ // against the real compiler + the real shim before this was built on.
19
+
20
+ import { dlog, isSymbioteNode } from '@symbiote-native/engine';
21
+ import type { ShimElement } from '@symbiote-native/svelte/native-view-bridge';
22
+ import { buildSearchBarHandle } from '../core';
23
+ import type { ISearchBarCommands } from '../core';
24
+
25
+ // `unknown` rather than `Element`: the value Svelte hands an attachment is one of the DOM shim's
26
+ // own element classes, not a real DOM Element, and a function accepting `unknown` is still
27
+ // assignable everywhere Svelte expects `(element: Element) => ...`.
28
+ function isShimElement(value: unknown): value is ShimElement {
29
+ return typeof value === 'object' && value !== null && 'engineNode' in value;
30
+ }
31
+
32
+ // The object-bag prop set, the same entry point every adapter's props go through (routeProp).
33
+ // Re-runs on every change of `props`, and the shim's own `p` setter diffs per key from there.
34
+ export function hostProps(
35
+ props: Record<string, unknown>,
36
+ ): (node: unknown) => void {
37
+ return node => {
38
+ if (!isShimElement(node)) {
39
+ dlog('navigation: hostProps attached to a non-shim element, ignored');
40
+ return;
41
+ }
42
+ node.p = props;
43
+ };
44
+ }
45
+
46
+ // The Svelte twin of Angular's SearchBarRefDirective / React's and Vue's callback `ref` on the
47
+ // RNSSearchBar element: fills the app-supplied cell once the native node exists and clears it on
48
+ // teardown. buildSearchBarHandle's own getter is LAZY, so handing it the node here is safe even
49
+ // before the first commit.
50
+ export function searchBarRef(
51
+ ref: { current: ISearchBarCommands | null } | undefined,
52
+ ): (node: unknown) => (() => void) | void {
53
+ return node => {
54
+ if (ref === undefined) return;
55
+ if (!isShimElement(node)) {
56
+ ref.current = null;
57
+ return;
58
+ }
59
+ const engineNode = node.engineNode;
60
+ if (!isSymbioteNode(engineNode)) {
61
+ dlog('navigation: search bar attachment ran before the shim went live');
62
+ ref.current = null;
63
+ return;
64
+ }
65
+ ref.current = buildSearchBarHandle(() => engineNode);
66
+ return () => {
67
+ ref.current = null;
68
+ };
69
+ };
70
+ }
@@ -0,0 +1,51 @@
1
+ // A shape-CHANGE-tolerant wrapper over @symbiote-native/svelte's Descriptor bridge.
2
+ //
3
+ // `mountDescriptorChildren` (svelte-adapter-dom-shim skill §19) is deliberately shape-stable: it
4
+ // builds each shim node once and only re-sets props thereafter, throwing if the tree shape ever
5
+ // changes, because every `core/components` render-*.ts produces a constant shape. The navigation
6
+ // package's tab bar is the exception - `renderTabBar` emits one child per registered route, and
7
+ // that count genuinely varies (it is 0 until the `<Tab.Screen>` markers have registered, and
8
+ // changes again if a screen is added or removed). Same situation @symbiote-native/slider's own
9
+ // steps-indicator overlay is in, and the same resolution: keep the fast path for the common
10
+ // "same shape, new prop values" case and rebuild only when the shape actually changed.
11
+ //
12
+ // The shape signature covers types and nesting, not just the top-level count, because a tab bar
13
+ // item's own children vary too (an item gains an icon wrapper only when it has an icon or badge).
14
+
15
+ import { mountDescriptorChildren } from '@symbiote-native/svelte/native-view-bridge';
16
+ import type {
17
+ IDescriptorChildrenMount,
18
+ ShimElement,
19
+ } from '@symbiote-native/svelte/native-view-bridge';
20
+ import type { IDescriptorChild } from '@symbiote-native/components';
21
+
22
+ const TEXT_CHILD_MARK = '#';
23
+
24
+ function shapeOf(children: readonly IDescriptorChild[]): string {
25
+ return children
26
+ .map(child =>
27
+ typeof child === 'string'
28
+ ? TEXT_CHILD_MARK
29
+ : `${child.type}(${shapeOf(child.children)})`,
30
+ )
31
+ .join(',');
32
+ }
33
+
34
+ export function createDescriptorSubtreeSync(): (
35
+ host: ShimElement | null,
36
+ children: IDescriptorChild[],
37
+ ) => void {
38
+ let mounted: IDescriptorChildrenMount | undefined;
39
+ let shape: string | undefined;
40
+ return (host, children) => {
41
+ if (host === null) return;
42
+ const nextShape = shapeOf(children);
43
+ if (mounted === undefined || nextShape !== shape) {
44
+ for (const child of host.children.slice()) host.removeChild(child);
45
+ mounted = mountDescriptorChildren(host, children);
46
+ shape = nextShape;
47
+ return;
48
+ }
49
+ mounted.update(children);
50
+ };
51
+ }
@@ -0,0 +1,28 @@
1
+ // Drawer's own public prop surface. Plain `.ts` for the tsc-cannot-see-a-`.svelte`-named-export
2
+ // reason ../screen-props.ts documents.
3
+
4
+ import type { Snippet } from 'svelte';
5
+ import type { IStyleProp, IViewStyle } from '@symbiote-native/engine';
6
+ import type {
7
+ IDrawerDescriptorMap,
8
+ IDrawerNavigatorHandle,
9
+ IDrawerOptions,
10
+ IDrawerRouterState,
11
+ } from '../../core';
12
+
13
+ // React's `renderDrawerContent` render-PROP becomes a Snippet WITH A PARAMETER here (Vue's twin
14
+ // is a scoped slot) - the drawer panel's content is app-authored and needs the router state, the
15
+ // per-route options map, and the navigator handle to paint a menu.
16
+ export type IDrawerContentSlotProps = {
17
+ state: IDrawerRouterState;
18
+ descriptors: IDrawerDescriptorMap;
19
+ navigation: IDrawerNavigatorHandle;
20
+ };
21
+
22
+ export type IDrawerProps = IDrawerOptions & {
23
+ initialRouteName?: string;
24
+ drawerStyle?: IStyleProp<IViewStyle>;
25
+ // The `<Drawer.Screen>` markers.
26
+ children?: Snippet;
27
+ drawerContent?: Snippet<[IDrawerContentSlotProps]>;
28
+ };