@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
@@ -1,9 +1,9 @@
1
1
  // Tab, the React lifecycle half. The focused-index router (tab-router-state) and the tab-bar
2
2
  // Descriptor builder (render-tabs) live in @symbiote-native/navigation core, shared verbatim
3
- // with the Vue/Angular adapters; here React supplies the lifecycle - useReducer for the focused
4
- // index, useId for route-key generation, useImperativeHandle for the jumpTo/setParams handle -
5
- // plus the descriptor bridge for the tab-bar leaf, exactly like Stack bridges its header config
6
- // (react/stack.ts). Unlike Stack, a bottom-tabs bar is a PURE-JS UI: it paints ordinary
3
+ // with the Vue/Angular adapters; here React supplies the lifecycle - useState for the dispatched
4
+ // router state, useId for route-key generation, useImperativeHandle for the jumpTo/setParams
5
+ // handle - plus the descriptor bridge for the tab-bar leaf, exactly like Stack bridges its header
6
+ // config (react/stack.ts). Unlike Stack, a bottom-tabs bar is a PURE-JS UI: it paints ordinary
7
7
  // `symbiote-view`/`symbiote-text` primitives via the shared render fn, so there is no
8
8
  // react-native-screens ViewConfig to register here - Tab needs no `../register` import.
9
9
 
@@ -17,7 +17,7 @@ import {
17
17
  useId,
18
18
  useImperativeHandle,
19
19
  useMemo,
20
- useReducer,
20
+ useState,
21
21
  } from 'react';
22
22
  import type { ReactElement, ReactNode } from 'react';
23
23
  import { descriptorToReact } from '@symbiote-native/react';
@@ -28,10 +28,18 @@ import {
28
28
  createInitialTabState,
29
29
  createNavigationEmitter,
30
30
  isFocusedRoute,
31
+ reconcileTabRoutes,
31
32
  renderTabBar,
32
33
  tabRouterReducer,
33
34
  } from '../../core';
34
- import type { IRoute, ITabBarItemView, ITabNavigatorHandle, ITabOptions } from '../../core';
35
+ import type {
36
+ IRoute,
37
+ ITabBarItemView,
38
+ ITabNavigatorHandle,
39
+ ITabOptions,
40
+ ITabRouterAction,
41
+ ITabRouterState,
42
+ } from '../../core';
35
43
  import { collectRegistry } from '../collect-registry';
36
44
  import { NavigationContext } from '../navigation-context';
37
45
  import { TabScreen } from '../tab-screen';
@@ -47,7 +55,9 @@ export type ITabProps = {
47
55
 
48
56
  type ITabRegistryEntry = Omit<ITabScreenProps, 'name'>;
49
57
 
50
- function isTabScreenElement(child: ReactNode): child is ReactElement<ITabScreenProps> {
58
+ function isTabScreenElement(
59
+ child: ReactNode,
60
+ ): child is ReactElement<ITabScreenProps> {
51
61
  return isValidElement(child) && child.type === TabScreen;
52
62
  }
53
63
 
@@ -56,149 +66,202 @@ function resolveTabOptions(
56
66
  optionsArgs: ITabScreenOptionsArgs,
57
67
  screenOptions: ITabOptions | undefined,
58
68
  ): ITabOptions {
59
- const own = typeof entry.options === 'function' ? entry.options(optionsArgs) : entry.options;
69
+ const own =
70
+ typeof entry.options === 'function'
71
+ ? entry.options(optionsArgs)
72
+ : entry.options;
60
73
  return { ...screenOptions, ...own };
61
74
  }
62
75
 
63
76
  const TAB_CONTENT_STYLE = { flex: 1 };
64
77
  const TAB_ROOT_STYLE = { flex: 1 };
65
78
 
66
- const TabImpl = forwardRef<ITabNavigatorHandle, ITabProps>((props, forwardedRef) => {
67
- // Read BEFORE establishing this Tab's own Context value below - becomes the `parent` link a
68
- // nested screen's useNavigation().getParent() walks (e.g. this Tab rendered as a Stack screen's
69
- // content reaches that Stack via this value). undefined when this Tab is the nesting root.
70
- const ambientContext = useContext(NavigationContext);
71
- const registry = useMemo(
72
- () => collectRegistry(props.children, isTabScreenElement),
73
- [props.children],
74
- );
75
- const routeIdPrefix = useId();
76
-
77
- const routes = useMemo<IRoute<unknown>[]>(
78
- () =>
79
- Array.from(registry.entries()).map(([name, entry]) => ({
80
- key: `${routeIdPrefix}-${name}`,
81
- name,
82
- params: entry.initialParams,
83
- })),
84
- [registry, routeIdPrefix],
85
- );
86
-
87
- const [state, dispatch] = useReducer(tabRouterReducer, undefined, () =>
88
- createInitialTabState(routes, props.initialRouteName),
89
- );
90
-
91
- if (routes.length === 0) dlog('Tab: no <Tab.Screen> children registered');
92
-
93
- const jumpTo = useCallback(
94
- (name: string, params?: unknown) => dispatch({ type: 'jumpTo', name, params }),
95
- [],
96
- );
97
- const setParams = useCallback(
98
- (params: unknown, key: string) => dispatch({ type: 'setParams', key, params }),
99
- [],
100
- );
101
-
102
- const handle = useMemo<ITabNavigatorHandle>(() => ({ jumpTo, setParams }), [jumpTo, setParams]);
103
-
104
- useImperativeHandle(forwardedRef, () => handle, [handle]);
105
-
106
- const focusedRoute: IRoute<unknown> | undefined = state.routes[state.index];
107
-
108
- const items: ITabBarItemView[] = state.routes.map((route, index) => {
109
- const entry = registry.get(route.name);
110
- const focused = isFocusedRoute(index, state.index);
111
- if (!entry) {
112
- dlog(`Tab: no screen registered for route name "${route.name}"`);
79
+ const TabImpl = forwardRef<ITabNavigatorHandle, ITabProps>(
80
+ (props, forwardedRef) => {
81
+ // Read BEFORE establishing this Tab's own Context value below - becomes the `parent` link a
82
+ // nested screen's useNavigation().getParent() walks (e.g. this Tab rendered as a Stack screen's
83
+ // content reaches that Stack via this value). undefined when this Tab is the nesting root.
84
+ const ambientContext = useContext(NavigationContext);
85
+ const registry = useMemo(
86
+ () => collectRegistry(props.children, isTabScreenElement),
87
+ [props.children],
88
+ );
89
+ const routeIdPrefix = useId();
90
+
91
+ const routes = useMemo<IRoute<unknown>[]>(
92
+ () =>
93
+ Array.from(registry.entries()).map(([name, entry]) => ({
94
+ key: `${routeIdPrefix}-${name}`,
95
+ name,
96
+ params: entry.initialParams,
97
+ })),
98
+ [registry, routeIdPrefix],
99
+ );
100
+
101
+ // A <Tab.Screen> can appear or disappear after mount (a marker behind a conditional, a
102
+ // data-driven screen list), so the route list is re-derived from `children` on every render and
103
+ // the STORED state is reconciled against it (reconcileTabRoutes, core - it preserves each
104
+ // surviving route's key and accumulated params and keeps focus on the same route NAME) rather
105
+ // than staying frozen at whatever the first render saw. `null` means no marker had registered
106
+ // yet: seeding an empty list would resolve initialRouteName against nothing and lose it, so the
107
+ // state stays derived until either a marker arrives or something dispatches.
108
+ const [storedState, setStoredState] = useState<ITabRouterState | null>(
109
+ () =>
110
+ routes.length === 0
111
+ ? null
112
+ : createInitialTabState(routes, props.initialRouteName),
113
+ );
114
+
115
+ const resolveState = useCallback(
116
+ (stored: ITabRouterState | null): ITabRouterState =>
117
+ stored === null
118
+ ? createInitialTabState(routes, props.initialRouteName)
119
+ : reconcileTabRoutes(stored, routes),
120
+ [routes, props.initialRouteName],
121
+ );
122
+
123
+ const state = resolveState(storedState);
124
+
125
+ if (routes.length === 0) dlog('Tab: no <Tab.Screen> children registered');
126
+
127
+ // The updater re-resolves rather than closing over `state` so that two dispatches batched into
128
+ // one render still compose: the second reduces over the first's result, not a stale snapshot.
129
+ const dispatch = useCallback(
130
+ (action: ITabRouterAction) =>
131
+ setStoredState(current =>
132
+ tabRouterReducer(resolveState(current), action),
133
+ ),
134
+ [resolveState],
135
+ );
136
+
137
+ const jumpTo = useCallback(
138
+ (name: string, params?: unknown) =>
139
+ dispatch({ type: 'jumpTo', name, params }),
140
+ [dispatch],
141
+ );
142
+ const setParams = useCallback(
143
+ (params: unknown, key: string) =>
144
+ dispatch({ type: 'setParams', key, params }),
145
+ [dispatch],
146
+ );
147
+
148
+ const handle = useMemo<ITabNavigatorHandle>(
149
+ () => ({ jumpTo, setParams }),
150
+ [jumpTo, setParams],
151
+ );
152
+
153
+ useImperativeHandle(forwardedRef, () => handle, [handle]);
154
+
155
+ const focusedRoute: IRoute<unknown> | undefined = state.routes[state.index];
156
+
157
+ const items: ITabBarItemView[] = state.routes.map((route, index) => {
158
+ const entry = registry.get(route.name);
159
+ const focused = isFocusedRoute(index, state.index);
160
+ if (!entry) {
161
+ dlog(`Tab: no screen registered for route name "${route.name}"`);
162
+ return {
163
+ key: route.key,
164
+ focused,
165
+ label: route.name,
166
+ passthrough: {},
167
+ };
168
+ }
169
+
170
+ const options = resolveTabOptions(
171
+ entry,
172
+ { route, navigation: handle },
173
+ props.screenOptions,
174
+ );
175
+
113
176
  return {
114
177
  key: route.key,
115
178
  focused,
116
- label: route.name,
179
+ label: options.tabBarLabel ?? options.title ?? route.name,
180
+ icon: options.tabBarIcon,
181
+ badge: options.tabBarBadge,
182
+ activeTintColor: options.tabBarActiveTintColor,
183
+ inactiveTintColor: options.tabBarInactiveTintColor,
184
+ passthrough: {
185
+ onPress: () => jumpTo(route.name),
186
+ accessibilityRole: 'tab',
187
+ accessibilityState: { selected: focused },
188
+ },
189
+ };
190
+ });
191
+
192
+ const focusedEntry = focusedRoute
193
+ ? registry.get(focusedRoute.name)
194
+ : undefined;
195
+ const focusedOptions: ITabOptions | undefined =
196
+ focusedEntry && focusedRoute
197
+ ? resolveTabOptions(
198
+ focusedEntry,
199
+ { route: focusedRoute, navigation: handle },
200
+ props.screenOptions,
201
+ )
202
+ : props.screenOptions;
203
+
204
+ const tabBar = descriptorToReact(
205
+ renderTabBar({
206
+ items,
207
+ style: focusedOptions?.tabBarStyle,
117
208
  passthrough: {},
209
+ }),
210
+ );
211
+
212
+ // Only the focused route's screen is ever mounted (unlike Stack, which keeps every pushed route
213
+ // alive), so a fresh emitter per focus change is sufficient - no per-route emitter map to prune,
214
+ // because the previous screen's whole subtree (and any listeners it registered) is torn down by
215
+ // an ordinary React unmount when focus moves on. Keyed on the route KEY rather than the route
216
+ // object so a setParams-only change (new route object, same key) doesn't spuriously re-fire
217
+ // focus/blur.
218
+ const focusedRouteKey = focusedRoute?.key;
219
+ const routeEmitter = useMemo(
220
+ () => createNavigationEmitter(),
221
+ [focusedRouteKey],
222
+ );
223
+
224
+ // Tab paints its own bar in pure JS - there is no native onAppear/onDisappear to hook (unlike
225
+ // Stack's RNSScreen), so focus/blur is synthesized here: mount = focus, cleanup = blur, exactly
226
+ // what an effect keyed on focusedRouteKey already encodes - no diffFocusedRoute indirection
227
+ // needed (unlike Vue/Angular, which diff real prev/next keys inside an imperative watch/CD
228
+ // callback that has no mount/cleanup pairing of its own).
229
+ useEffect(() => {
230
+ if (focusedRouteKey === undefined) return undefined;
231
+ dlog(`Tab: route "${focusedRoute?.name}" focused`);
232
+ routeEmitter.emit(NAVIGATION_EVENT_FOCUS);
233
+ return () => {
234
+ dlog(`Tab: route "${focusedRoute?.name}" blurred`);
235
+ routeEmitter.emit(NAVIGATION_EVENT_BLUR);
118
236
  };
119
- }
120
-
121
- const options = resolveTabOptions(entry, { route, navigation: handle }, props.screenOptions);
122
-
123
- return {
124
- key: route.key,
125
- focused,
126
- label: options.tabBarLabel ?? options.title ?? route.name,
127
- icon: options.tabBarIcon,
128
- badge: options.tabBarBadge,
129
- activeTintColor: options.tabBarActiveTintColor,
130
- inactiveTintColor: options.tabBarInactiveTintColor,
131
- passthrough: {
132
- onPress: () => jumpTo(route.name),
133
- accessibilityRole: 'tab',
134
- accessibilityState: { selected: focused },
135
- },
136
- };
137
- });
138
-
139
- const focusedEntry = focusedRoute ? registry.get(focusedRoute.name) : undefined;
140
- const focusedOptions: ITabOptions | undefined =
141
- focusedEntry && focusedRoute
142
- ? resolveTabOptions(
143
- focusedEntry,
144
- { route: focusedRoute, navigation: handle },
145
- props.screenOptions,
146
- )
147
- : props.screenOptions;
148
-
149
- const tabBar = descriptorToReact(
150
- renderTabBar({ items, style: focusedOptions?.tabBarStyle, passthrough: {} }),
151
- );
152
-
153
- // Only the focused route's screen is ever mounted (unlike Stack, which keeps every pushed route
154
- // alive), so a fresh emitter per focus change is sufficient - no per-route emitter map to prune,
155
- // because the previous screen's whole subtree (and any listeners it registered) is torn down by
156
- // an ordinary React unmount when focus moves on. Keyed on the route KEY rather than the route
157
- // object so a setParams-only change (new route object, same key) doesn't spuriously re-fire
158
- // focus/blur.
159
- const focusedRouteKey = focusedRoute?.key;
160
- const routeEmitter = useMemo(() => createNavigationEmitter(), [focusedRouteKey]);
161
-
162
- // Tab paints its own bar in pure JS - there is no native onAppear/onDisappear to hook (unlike
163
- // Stack's RNSScreen), so focus/blur is synthesized here: mount = focus, cleanup = blur, exactly
164
- // what an effect keyed on focusedRouteKey already encodes - no diffFocusedRoute indirection
165
- // needed (unlike Vue/Angular, which diff real prev/next keys inside an imperative watch/CD
166
- // callback that has no mount/cleanup pairing of its own).
167
- useEffect(() => {
168
- if (focusedRouteKey === undefined) return undefined;
169
- dlog(`Tab: route "${focusedRoute?.name}" focused`);
170
- routeEmitter.emit(NAVIGATION_EVENT_FOCUS);
171
- return () => {
172
- dlog(`Tab: route "${focusedRoute?.name}" blurred`);
173
- routeEmitter.emit(NAVIGATION_EVENT_BLUR);
174
- };
175
- // focusedRoute omitted deliberately: only its .key (tracked via focusedRouteKey) should
176
- // re-run this effect - see the comment above on focusedRouteKey.
177
- // eslint-disable-next-line react-hooks/exhaustive-deps
178
- }, [routeEmitter, focusedRouteKey]);
179
-
180
- const content =
181
- focusedEntry && focusedRoute
182
- ? createElement(
183
- NavigationContext.Provider,
184
- {
185
- value: {
186
- route: focusedRoute,
187
- navigation: handle,
188
- emitter: routeEmitter,
189
- parent: ambientContext,
237
+ // focusedRoute omitted deliberately: only its .key (tracked via focusedRouteKey) should
238
+ // re-run this effect - see the comment above on focusedRouteKey.
239
+ // eslint-disable-next-line react-hooks/exhaustive-deps
240
+ }, [routeEmitter, focusedRouteKey]);
241
+
242
+ const content =
243
+ focusedEntry && focusedRoute
244
+ ? createElement(
245
+ NavigationContext.Provider,
246
+ {
247
+ value: {
248
+ route: focusedRoute,
249
+ navigation: handle,
250
+ emitter: routeEmitter,
251
+ parent: ambientContext,
252
+ },
190
253
  },
191
- },
192
- createElement(focusedEntry.component),
193
- )
194
- : null;
195
-
196
- return createElement(
197
- 'symbiote-view',
198
- { style: TAB_ROOT_STYLE },
199
- createElement('symbiote-view', { style: TAB_CONTENT_STYLE }, content),
200
- tabBar,
201
- );
202
- });
254
+ createElement(focusedEntry.component),
255
+ )
256
+ : null;
257
+
258
+ return createElement(
259
+ 'symbiote-view',
260
+ { style: TAB_ROOT_STYLE },
261
+ createElement('symbiote-view', { style: TAB_CONTENT_STYLE }, content),
262
+ tabBar,
263
+ );
264
+ },
265
+ );
203
266
 
204
267
  export const Tab = Object.assign(TabImpl, { Screen: TabScreen });