@symbiote-native/navigation 1.0.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 (455) hide show
  1. package/LICENSE +21 -0
  2. package/build/angular/drawer/index.d.ts +76 -0
  3. package/build/angular/drawer/index.js +508 -0
  4. package/build/angular/drawer-screen.directive.d.ts +14 -0
  5. package/build/angular/drawer-screen.directive.js +87 -0
  6. package/build/angular/index.d.ts +23 -0
  7. package/build/angular/index.js +22 -0
  8. package/build/angular/injectors/index.d.ts +13 -0
  9. package/build/angular/injectors/index.js +8 -0
  10. package/build/angular/injectors/inject-drawer-navigation.d.ts +4 -0
  11. package/build/angular/injectors/inject-drawer-navigation.js +12 -0
  12. package/build/angular/injectors/inject-focus-effect.d.ts +2 -0
  13. package/build/angular/injectors/inject-focus-effect.js +29 -0
  14. package/build/angular/injectors/inject-is-focused.d.ts +2 -0
  15. package/build/angular/injectors/inject-is-focused.js +20 -0
  16. package/build/angular/injectors/inject-navigation-state.d.ts +3 -0
  17. package/build/angular/injectors/inject-navigation-state.js +35 -0
  18. package/build/angular/injectors/inject-navigation.d.ts +7 -0
  19. package/build/angular/injectors/inject-navigation.js +16 -0
  20. package/build/angular/injectors/inject-route.d.ts +3 -0
  21. package/build/angular/injectors/inject-route.js +20 -0
  22. package/build/angular/injectors/inject-stack-navigation.d.ts +4 -0
  23. package/build/angular/injectors/inject-stack-navigation.js +15 -0
  24. package/build/angular/injectors/inject-tab-navigation.d.ts +4 -0
  25. package/build/angular/injectors/inject-tab-navigation.js +11 -0
  26. package/build/angular/linking.d.ts +3 -0
  27. package/build/angular/linking.js +43 -0
  28. package/build/angular/navigation-context.service.d.ts +13 -0
  29. package/build/angular/navigation-context.service.js +105 -0
  30. package/build/angular/navigation-scope.directive.d.ts +10 -0
  31. package/build/angular/navigation-scope.directive.js +90 -0
  32. package/build/angular/screen.directive.d.ts +22 -0
  33. package/build/angular/screen.directive.js +90 -0
  34. package/build/angular/search-bar-ref.directive.d.ts +10 -0
  35. package/build/angular/search-bar-ref.directive.js +88 -0
  36. package/build/angular/stack/index.d.ts +55 -0
  37. package/build/angular/stack/index.js +386 -0
  38. package/build/angular/tab-screen.directive.d.ts +14 -0
  39. package/build/angular/tab-screen.directive.js +87 -0
  40. package/build/angular/tabs/index.d.ts +42 -0
  41. package/build/angular/tabs/index.js +263 -0
  42. package/build/core/constants.d.ts +27 -0
  43. package/build/core/constants.js +61 -0
  44. package/build/core/drawer-options/index.d.ts +64 -0
  45. package/build/core/drawer-options/index.js +200 -0
  46. package/build/core/drawer-router-state/index.d.ts +21 -0
  47. package/build/core/drawer-router-state/index.js +40 -0
  48. package/build/core/guards.d.ts +2 -0
  49. package/build/core/guards.js +22 -0
  50. package/build/core/header-bar-buttons.d.ts +9 -0
  51. package/build/core/header-bar-buttons.js +163 -0
  52. package/build/core/index.d.ts +29 -0
  53. package/build/core/index.js +32 -0
  54. package/build/core/linking-config/index.d.ts +13 -0
  55. package/build/core/linking-config/index.js +171 -0
  56. package/build/core/navigation-events/index.d.ts +16 -0
  57. package/build/core/navigation-events/index.js +57 -0
  58. package/build/core/navigator-handles/index.d.ts +30 -0
  59. package/build/core/navigator-handles/index.js +15 -0
  60. package/build/core/navigator-props.d.ts +216 -0
  61. package/build/core/navigator-props.js +7 -0
  62. package/build/core/navigator-state/index.d.ts +36 -0
  63. package/build/core/navigator-state/index.js +81 -0
  64. package/build/core/render-drawer.d.ts +13 -0
  65. package/build/core/render-drawer.js +88 -0
  66. package/build/core/render-stack.d.ts +35 -0
  67. package/build/core/render-stack.js +294 -0
  68. package/build/core/render-tabs.d.ts +20 -0
  69. package/build/core/render-tabs.js +97 -0
  70. package/build/core/screen-options.d.ts +4 -0
  71. package/build/core/screen-options.js +133 -0
  72. package/build/core/search-bar-commands.d.ts +10 -0
  73. package/build/core/search-bar-commands.js +42 -0
  74. package/build/core/state-persistence/index.d.ts +3 -0
  75. package/build/core/state-persistence/index.js +23 -0
  76. package/build/core/tab-options.d.ts +13 -0
  77. package/build/core/tab-options.js +10 -0
  78. package/build/core/tab-router-state/index.d.ts +19 -0
  79. package/build/core/tab-router-state/index.js +51 -0
  80. package/build/react/collect-registry.d.ts +4 -0
  81. package/build/react/collect-registry.js +15 -0
  82. package/build/react/drawer/index.d.ts +29 -0
  83. package/build/react/drawer/index.js +277 -0
  84. package/build/react/drawer-screen.d.ts +14 -0
  85. package/build/react/drawer-screen.js +8 -0
  86. package/build/react/hooks/index.d.ts +12 -0
  87. package/build/react/hooks/index.js +8 -0
  88. package/build/react/hooks/use-drawer-navigation.d.ts +4 -0
  89. package/build/react/hooks/use-drawer-navigation.js +12 -0
  90. package/build/react/hooks/use-focus-effect.d.ts +2 -0
  91. package/build/react/hooks/use-focus-effect.js +29 -0
  92. package/build/react/hooks/use-is-focused.d.ts +1 -0
  93. package/build/react/hooks/use-is-focused.js +22 -0
  94. package/build/react/hooks/use-navigation-state/index.d.ts +2 -0
  95. package/build/react/hooks/use-navigation-state/index.js +39 -0
  96. package/build/react/hooks/use-navigation.d.ts +7 -0
  97. package/build/react/hooks/use-navigation.js +16 -0
  98. package/build/react/hooks/use-route.d.ts +2 -0
  99. package/build/react/hooks/use-route.js +8 -0
  100. package/build/react/hooks/use-stack-navigation.d.ts +4 -0
  101. package/build/react/hooks/use-stack-navigation.js +14 -0
  102. package/build/react/hooks/use-tab-navigation.d.ts +4 -0
  103. package/build/react/hooks/use-tab-navigation.js +12 -0
  104. package/build/react/index.d.ts +24 -0
  105. package/build/react/index.js +21 -0
  106. package/build/react/linking.d.ts +3 -0
  107. package/build/react/linking.js +47 -0
  108. package/build/react/navigation-context.d.ts +10 -0
  109. package/build/react/navigation-context.js +22 -0
  110. package/build/react/screen.d.ts +22 -0
  111. package/build/react/screen.js +7 -0
  112. package/build/react/stack/index.d.ts +13 -0
  113. package/build/react/stack/index.js +202 -0
  114. package/build/react/tab-screen.d.ts +15 -0
  115. package/build/react/tab-screen.js +8 -0
  116. package/build/react/tabs/index.d.ts +12 -0
  117. package/build/react/tabs/index.js +116 -0
  118. package/build/register.d.ts +7 -0
  119. package/build/register.js +58 -0
  120. package/build/vue/composables/index.d.ts +12 -0
  121. package/build/vue/composables/index.js +8 -0
  122. package/build/vue/composables/use-drawer-navigation.d.ts +5 -0
  123. package/build/vue/composables/use-drawer-navigation.js +15 -0
  124. package/build/vue/composables/use-focus-effect.d.ts +1 -0
  125. package/build/vue/composables/use-focus-effect.js +31 -0
  126. package/build/vue/composables/use-is-focused.d.ts +2 -0
  127. package/build/vue/composables/use-is-focused.js +28 -0
  128. package/build/vue/composables/use-navigation-state.d.ts +3 -0
  129. package/build/vue/composables/use-navigation-state.js +29 -0
  130. package/build/vue/composables/use-navigation.d.ts +8 -0
  131. package/build/vue/composables/use-navigation.js +13 -0
  132. package/build/vue/composables/use-route.d.ts +3 -0
  133. package/build/vue/composables/use-route.js +9 -0
  134. package/build/vue/composables/use-stack-navigation.d.ts +5 -0
  135. package/build/vue/composables/use-stack-navigation.js +18 -0
  136. package/build/vue/composables/use-tab-navigation.d.ts +5 -0
  137. package/build/vue/composables/use-tab-navigation.js +15 -0
  138. package/build/vue/drawer/index.d.ts +19 -0
  139. package/build/vue/drawer/index.js +390 -0
  140. package/build/vue/drawer-screen.d.ts +14 -0
  141. package/build/vue/drawer-screen.js +12 -0
  142. package/build/vue/index.d.ts +21 -0
  143. package/build/vue/index.js +19 -0
  144. package/build/vue/linking.d.ts +3 -0
  145. package/build/vue/linking.js +57 -0
  146. package/build/vue/navigation-context.d.ts +16 -0
  147. package/build/vue/navigation-context.js +65 -0
  148. package/build/vue/screen.d.ts +21 -0
  149. package/build/vue/screen.js +12 -0
  150. package/build/vue/stack/index.d.ts +9 -0
  151. package/build/vue/stack/index.js +279 -0
  152. package/build/vue/tab-screen.d.ts +14 -0
  153. package/build/vue/tab-screen.js +11 -0
  154. package/build/vue/tabs/index.d.ts +10 -0
  155. package/build/vue/tabs/index.js +209 -0
  156. package/build-ngc/angular/drawer/index.d.ts +79 -0
  157. package/build-ngc/angular/drawer/index.js +479 -0
  158. package/build-ngc/angular/drawer/index.js.map +1 -0
  159. package/build-ngc/angular/drawer-screen.directive.d.ts +17 -0
  160. package/build-ngc/angular/drawer-screen.directive.js +32 -0
  161. package/build-ngc/angular/drawer-screen.directive.js.map +1 -0
  162. package/build-ngc/angular/drawer.d.ts +79 -0
  163. package/build-ngc/angular/drawer.js +475 -0
  164. package/build-ngc/angular/drawer.js.map +1 -0
  165. package/build-ngc/angular/index.d.ts +23 -0
  166. package/build-ngc/angular/index.js +23 -0
  167. package/build-ngc/angular/index.js.map +1 -0
  168. package/build-ngc/angular/injectors/index.d.ts +13 -0
  169. package/build-ngc/angular/injectors/index.js +9 -0
  170. package/build-ngc/angular/injectors/index.js.map +1 -0
  171. package/build-ngc/angular/injectors/inject-drawer-navigation.d.ts +4 -0
  172. package/build-ngc/angular/injectors/inject-drawer-navigation.js +13 -0
  173. package/build-ngc/angular/injectors/inject-drawer-navigation.js.map +1 -0
  174. package/build-ngc/angular/injectors/inject-focus-effect.d.ts +2 -0
  175. package/build-ngc/angular/injectors/inject-focus-effect.js +30 -0
  176. package/build-ngc/angular/injectors/inject-focus-effect.js.map +1 -0
  177. package/build-ngc/angular/injectors/inject-is-focused.d.ts +2 -0
  178. package/build-ngc/angular/injectors/inject-is-focused.js +22 -0
  179. package/build-ngc/angular/injectors/inject-is-focused.js.map +1 -0
  180. package/build-ngc/angular/injectors/inject-navigation-state.d.ts +3 -0
  181. package/build-ngc/angular/injectors/inject-navigation-state.js +37 -0
  182. package/build-ngc/angular/injectors/inject-navigation-state.js.map +1 -0
  183. package/build-ngc/angular/injectors/inject-navigation.d.ts +7 -0
  184. package/build-ngc/angular/injectors/inject-navigation.js +17 -0
  185. package/build-ngc/angular/injectors/inject-navigation.js.map +1 -0
  186. package/build-ngc/angular/injectors/inject-route.d.ts +3 -0
  187. package/build-ngc/angular/injectors/inject-route.js +21 -0
  188. package/build-ngc/angular/injectors/inject-route.js.map +1 -0
  189. package/build-ngc/angular/injectors/inject-stack-navigation.d.ts +4 -0
  190. package/build-ngc/angular/injectors/inject-stack-navigation.js +16 -0
  191. package/build-ngc/angular/injectors/inject-stack-navigation.js.map +1 -0
  192. package/build-ngc/angular/injectors/inject-tab-navigation.d.ts +4 -0
  193. package/build-ngc/angular/injectors/inject-tab-navigation.js +12 -0
  194. package/build-ngc/angular/injectors/inject-tab-navigation.js.map +1 -0
  195. package/build-ngc/angular/linking.d.ts +3 -0
  196. package/build-ngc/angular/linking.js +44 -0
  197. package/build-ngc/angular/linking.js.map +1 -0
  198. package/build-ngc/angular/navigation-context.service.d.ts +16 -0
  199. package/build-ngc/angular/navigation-context.service.js +63 -0
  200. package/build-ngc/angular/navigation-context.service.js.map +1 -0
  201. package/build-ngc/angular/navigation-scope.directive.d.ts +13 -0
  202. package/build-ngc/angular/navigation-scope.directive.js +42 -0
  203. package/build-ngc/angular/navigation-scope.directive.js.map +1 -0
  204. package/build-ngc/angular/screen.directive.d.ts +25 -0
  205. package/build-ngc/angular/screen.directive.js +35 -0
  206. package/build-ngc/angular/screen.directive.js.map +1 -0
  207. package/build-ngc/angular/search-bar-ref.directive.d.ts +13 -0
  208. package/build-ngc/angular/search-bar-ref.directive.js +44 -0
  209. package/build-ngc/angular/search-bar-ref.directive.js.map +1 -0
  210. package/build-ngc/angular/stack/index.d.ts +58 -0
  211. package/build-ngc/angular/stack/index.js +406 -0
  212. package/build-ngc/angular/stack/index.js.map +1 -0
  213. package/build-ngc/angular/stack.d.ts +58 -0
  214. package/build-ngc/angular/stack.js +402 -0
  215. package/build-ngc/angular/stack.js.map +1 -0
  216. package/build-ngc/angular/tab-screen.directive.d.ts +17 -0
  217. package/build-ngc/angular/tab-screen.directive.js +32 -0
  218. package/build-ngc/angular/tab-screen.directive.js.map +1 -0
  219. package/build-ngc/angular/tabs/index.d.ts +45 -0
  220. package/build-ngc/angular/tabs/index.js +231 -0
  221. package/build-ngc/angular/tabs/index.js.map +1 -0
  222. package/build-ngc/angular/tabs.d.ts +45 -0
  223. package/build-ngc/angular/tabs.js +231 -0
  224. package/build-ngc/angular/tabs.js.map +1 -0
  225. package/build-ngc/core/constants.d.ts +27 -0
  226. package/build-ngc/core/constants.js +62 -0
  227. package/build-ngc/core/constants.js.map +1 -0
  228. package/build-ngc/core/drawer-options/index.d.ts +64 -0
  229. package/build-ngc/core/drawer-options/index.js +201 -0
  230. package/build-ngc/core/drawer-options/index.js.map +1 -0
  231. package/build-ngc/core/drawer-options.d.ts +64 -0
  232. package/build-ngc/core/drawer-options.js +201 -0
  233. package/build-ngc/core/drawer-options.js.map +1 -0
  234. package/build-ngc/core/drawer-router-state/index.d.ts +21 -0
  235. package/build-ngc/core/drawer-router-state/index.js +41 -0
  236. package/build-ngc/core/drawer-router-state/index.js.map +1 -0
  237. package/build-ngc/core/drawer-router-state.d.ts +21 -0
  238. package/build-ngc/core/drawer-router-state.js +41 -0
  239. package/build-ngc/core/drawer-router-state.js.map +1 -0
  240. package/build-ngc/core/guards.d.ts +2 -0
  241. package/build-ngc/core/guards.js +23 -0
  242. package/build-ngc/core/guards.js.map +1 -0
  243. package/build-ngc/core/header-bar-buttons.d.ts +9 -0
  244. package/build-ngc/core/header-bar-buttons.js +164 -0
  245. package/build-ngc/core/header-bar-buttons.js.map +1 -0
  246. package/build-ngc/core/href.d.ts +16 -0
  247. package/build-ngc/core/href.js +55 -0
  248. package/build-ngc/core/href.js.map +1 -0
  249. package/build-ngc/core/index.d.ts +29 -0
  250. package/build-ngc/core/index.js +33 -0
  251. package/build-ngc/core/index.js.map +1 -0
  252. package/build-ngc/core/linking-config/index.d.ts +13 -0
  253. package/build-ngc/core/linking-config/index.js +172 -0
  254. package/build-ngc/core/linking-config/index.js.map +1 -0
  255. package/build-ngc/core/linking-config.d.ts +13 -0
  256. package/build-ngc/core/linking-config.js +172 -0
  257. package/build-ngc/core/linking-config.js.map +1 -0
  258. package/build-ngc/core/navigation-events/index.d.ts +16 -0
  259. package/build-ngc/core/navigation-events/index.js +58 -0
  260. package/build-ngc/core/navigation-events/index.js.map +1 -0
  261. package/build-ngc/core/navigation-events.d.ts +16 -0
  262. package/build-ngc/core/navigation-events.js +58 -0
  263. package/build-ngc/core/navigation-events.js.map +1 -0
  264. package/build-ngc/core/navigator-handles/index.d.ts +30 -0
  265. package/build-ngc/core/navigator-handles/index.js +16 -0
  266. package/build-ngc/core/navigator-handles/index.js.map +1 -0
  267. package/build-ngc/core/navigator-handles.d.ts +30 -0
  268. package/build-ngc/core/navigator-handles.js +16 -0
  269. package/build-ngc/core/navigator-handles.js.map +1 -0
  270. package/build-ngc/core/navigator-props.d.ts +216 -0
  271. package/build-ngc/core/navigator-props.js +8 -0
  272. package/build-ngc/core/navigator-props.js.map +1 -0
  273. package/build-ngc/core/navigator-state/index.d.ts +36 -0
  274. package/build-ngc/core/navigator-state/index.js +82 -0
  275. package/build-ngc/core/navigator-state/index.js.map +1 -0
  276. package/build-ngc/core/navigator-state.d.ts +36 -0
  277. package/build-ngc/core/navigator-state.js +82 -0
  278. package/build-ngc/core/navigator-state.js.map +1 -0
  279. package/build-ngc/core/render-drawer.d.ts +13 -0
  280. package/build-ngc/core/render-drawer.js +89 -0
  281. package/build-ngc/core/render-drawer.js.map +1 -0
  282. package/build-ngc/core/render-stack.d.ts +35 -0
  283. package/build-ngc/core/render-stack.js +295 -0
  284. package/build-ngc/core/render-stack.js.map +1 -0
  285. package/build-ngc/core/render-tabs.d.ts +20 -0
  286. package/build-ngc/core/render-tabs.js +98 -0
  287. package/build-ngc/core/render-tabs.js.map +1 -0
  288. package/build-ngc/core/screen-options.d.ts +4 -0
  289. package/build-ngc/core/screen-options.js +134 -0
  290. package/build-ngc/core/screen-options.js.map +1 -0
  291. package/build-ngc/core/search-bar-commands.d.ts +10 -0
  292. package/build-ngc/core/search-bar-commands.js +43 -0
  293. package/build-ngc/core/search-bar-commands.js.map +1 -0
  294. package/build-ngc/core/search-params.d.ts +9 -0
  295. package/build-ngc/core/search-params.js +60 -0
  296. package/build-ngc/core/search-params.js.map +1 -0
  297. package/build-ngc/core/state-persistence/index.d.ts +3 -0
  298. package/build-ngc/core/state-persistence/index.js +24 -0
  299. package/build-ngc/core/state-persistence/index.js.map +1 -0
  300. package/build-ngc/core/state-persistence.d.ts +3 -0
  301. package/build-ngc/core/state-persistence.js +24 -0
  302. package/build-ngc/core/state-persistence.js.map +1 -0
  303. package/build-ngc/core/tab-options.d.ts +13 -0
  304. package/build-ngc/core/tab-options.js +11 -0
  305. package/build-ngc/core/tab-options.js.map +1 -0
  306. package/build-ngc/core/tab-router-state/index.d.ts +19 -0
  307. package/build-ngc/core/tab-router-state/index.js +52 -0
  308. package/build-ngc/core/tab-router-state/index.js.map +1 -0
  309. package/build-ngc/core/tab-router-state.d.ts +19 -0
  310. package/build-ngc/core/tab-router-state.js +52 -0
  311. package/build-ngc/core/tab-router-state.js.map +1 -0
  312. package/build-ngc/register.d.ts +7 -0
  313. package/build-ngc/register.js +59 -0
  314. package/build-ngc/register.js.map +1 -0
  315. package/codegen-specs/FullWindowOverlayNativeComponent.ts +13 -0
  316. package/codegen-specs/ModalScreenNativeComponent.ts +129 -0
  317. package/codegen-specs/NativeScreensModule.ts +8 -0
  318. package/codegen-specs/NativeScreensModule.web.ts +1 -0
  319. package/codegen-specs/ScreenContainerNativeComponent.ts +8 -0
  320. package/codegen-specs/ScreenContentWrapperNativeComponent.ts +9 -0
  321. package/codegen-specs/ScreenFooterNativeComponent.ts +6 -0
  322. package/codegen-specs/ScreenNativeComponent.ts +133 -0
  323. package/codegen-specs/ScreenNavigationContainerNativeComponent.ts +11 -0
  324. package/codegen-specs/ScreenStackHeaderConfigNativeComponent.ts +99 -0
  325. package/codegen-specs/ScreenStackHeaderSubviewNativeComponent.ts +25 -0
  326. package/codegen-specs/ScreenStackNativeComponent.ts +17 -0
  327. package/codegen-specs/SearchBarNativeComponent.ts +101 -0
  328. package/codegen-specs/codegenUtils.ts +6 -0
  329. package/codegen-specs/gamma/ScrollViewMarkerNativeComponent.ts +15 -0
  330. package/codegen-specs/gamma/modals/form-sheet/FormSheetContentWrapperNativeComponent.ts +10 -0
  331. package/codegen-specs/gamma/modals/form-sheet/FormSheetHostNativeComponent.ts +47 -0
  332. package/codegen-specs/gamma/split/SplitHostNativeComponent.ts +124 -0
  333. package/codegen-specs/gamma/split/SplitScreenNativeComponent.ts +24 -0
  334. package/codegen-specs/gamma/stack/StackHeaderConfigAndroidNativeComponent.ts +144 -0
  335. package/codegen-specs/gamma/stack/StackHeaderConfigIOSNativeComponent.ts +34 -0
  336. package/codegen-specs/gamma/stack/StackHeaderItemIOSNativeComponent.ts +47 -0
  337. package/codegen-specs/gamma/stack/StackHeaderItemSpacerIOSNativeComponent.ts +20 -0
  338. package/codegen-specs/gamma/stack/StackHeaderSubviewAndroidNativeComponent.ts +27 -0
  339. package/codegen-specs/gamma/stack/StackHostNativeComponent.ts +8 -0
  340. package/codegen-specs/gamma/stack/StackScreenNativeComponent.ts +42 -0
  341. package/codegen-specs/safe-area/SafeAreaViewNativeComponent.ts +24 -0
  342. package/codegen-specs/tabs/TabsBottomAccessoryContentNativeComponent.ts +15 -0
  343. package/codegen-specs/tabs/TabsBottomAccessoryNativeComponent.ts +19 -0
  344. package/codegen-specs/tabs/TabsHostAndroidNativeComponent.ts +69 -0
  345. package/codegen-specs/tabs/TabsHostIOSNativeComponent.ts +92 -0
  346. package/codegen-specs/tabs/TabsScreenAndroidNativeComponent.ts +112 -0
  347. package/codegen-specs/tabs/TabsScreenIOSNativeComponent.ts +171 -0
  348. package/package.json +201 -0
  349. package/react-native.config.cjs +58 -0
  350. package/src/angular/drawer/drawer.test.ts +299 -0
  351. package/src/angular/drawer/index.ts +546 -0
  352. package/src/angular/drawer-screen.directive.ts +28 -0
  353. package/src/angular/index.ts +63 -0
  354. package/src/angular/injectors/index.ts +13 -0
  355. package/src/angular/injectors/inject-drawer-navigation.ts +21 -0
  356. package/src/angular/injectors/inject-focus-effect.ts +37 -0
  357. package/src/angular/injectors/inject-is-focused.ts +28 -0
  358. package/src/angular/injectors/inject-navigation-state.ts +46 -0
  359. package/src/angular/injectors/inject-navigation.ts +26 -0
  360. package/src/angular/injectors/inject-route.ts +25 -0
  361. package/src/angular/injectors/inject-stack-navigation.ts +24 -0
  362. package/src/angular/injectors/inject-tab-navigation.ts +20 -0
  363. package/src/angular/injectors/injectors.test.ts +247 -0
  364. package/src/angular/injectors/use-typed-navigation.test.ts +229 -0
  365. package/src/angular/linking.ts +55 -0
  366. package/src/angular/navigation-context.service.ts +68 -0
  367. package/src/angular/navigation-scope.directive.ts +31 -0
  368. package/src/angular/nested-navigation.test.ts +189 -0
  369. package/src/angular/screen.directive.ts +48 -0
  370. package/src/angular/search-bar-ref.directive.ts +38 -0
  371. package/src/angular/stack/index.ts +401 -0
  372. package/src/angular/stack/stack.test.ts +419 -0
  373. package/src/angular/tab-screen.directive.ts +26 -0
  374. package/src/angular/tabs/index.ts +254 -0
  375. package/src/angular/tabs/tabs.test.ts +249 -0
  376. package/src/core/constants.ts +68 -0
  377. package/src/core/drawer-options/drawer-options.test.ts +415 -0
  378. package/src/core/drawer-options/index.ts +331 -0
  379. package/src/core/drawer-router-state/drawer-router-state.test.ts +116 -0
  380. package/src/core/drawer-router-state/index.ts +71 -0
  381. package/src/core/guards.ts +23 -0
  382. package/src/core/header-bar-buttons.ts +212 -0
  383. package/src/core/index.ts +170 -0
  384. package/src/core/linking-config/index.ts +209 -0
  385. package/src/core/linking-config/linking-config.test.ts +109 -0
  386. package/src/core/navigation-events/index.ts +94 -0
  387. package/src/core/navigation-events/navigation-events.test.ts +56 -0
  388. package/src/core/navigator-handles/index.ts +63 -0
  389. package/src/core/navigator-handles/navigator-handles.test.ts +46 -0
  390. package/src/core/navigator-props.ts +323 -0
  391. package/src/core/navigator-state/index.ts +112 -0
  392. package/src/core/navigator-state/navigator-state.test.ts +80 -0
  393. package/src/core/render-drawer.ts +123 -0
  394. package/src/core/render-stack.ts +414 -0
  395. package/src/core/render-tabs.ts +138 -0
  396. package/src/core/screen-options.ts +181 -0
  397. package/src/core/search-bar-commands.ts +57 -0
  398. package/src/core/state-persistence/index.ts +30 -0
  399. package/src/core/state-persistence/state-persistence.test.ts +42 -0
  400. package/src/core/tab-options.ts +25 -0
  401. package/src/core/tab-router-state/index.ts +75 -0
  402. package/src/core/tab-router-state/tab-router-state.test.ts +34 -0
  403. package/src/globals.d.ts +4 -0
  404. package/src/react/collect-registry.ts +19 -0
  405. package/src/react/drawer/drawer.test.tsx +472 -0
  406. package/src/react/drawer/index.ts +451 -0
  407. package/src/react/drawer-screen.ts +29 -0
  408. package/src/react/hooks/hooks.test.tsx +199 -0
  409. package/src/react/hooks/index.ts +12 -0
  410. package/src/react/hooks/use-drawer-navigation.ts +21 -0
  411. package/src/react/hooks/use-focus-effect.ts +36 -0
  412. package/src/react/hooks/use-is-focused.ts +26 -0
  413. package/src/react/hooks/use-navigation-state/index.ts +47 -0
  414. package/src/react/hooks/use-navigation-state/use-navigation-state.test.ts +85 -0
  415. package/src/react/hooks/use-navigation.ts +36 -0
  416. package/src/react/hooks/use-route.ts +11 -0
  417. package/src/react/hooks/use-stack-navigation.ts +23 -0
  418. package/src/react/hooks/use-tab-navigation.ts +21 -0
  419. package/src/react/hooks/use-typed-navigation.test.tsx +187 -0
  420. package/src/react/index.ts +52 -0
  421. package/src/react/linking.ts +59 -0
  422. package/src/react/navigation-context.ts +40 -0
  423. package/src/react/nested-navigation.test.tsx +145 -0
  424. package/src/react/screen.ts +44 -0
  425. package/src/react/stack/index.ts +323 -0
  426. package/src/react/stack/stack.test.tsx +514 -0
  427. package/src/react/tab-screen.ts +30 -0
  428. package/src/react/tabs/index.ts +204 -0
  429. package/src/react/tabs/tabs.test.tsx +256 -0
  430. package/src/register.ts +59 -0
  431. package/src/vue/composables/composables.test.ts +221 -0
  432. package/src/vue/composables/index.ts +12 -0
  433. package/src/vue/composables/use-drawer-navigation.ts +25 -0
  434. package/src/vue/composables/use-focus-effect.ts +37 -0
  435. package/src/vue/composables/use-is-focused.ts +34 -0
  436. package/src/vue/composables/use-navigation-state.ts +38 -0
  437. package/src/vue/composables/use-navigation.ts +30 -0
  438. package/src/vue/composables/use-route.ts +13 -0
  439. package/src/vue/composables/use-stack-navigation.ts +28 -0
  440. package/src/vue/composables/use-tab-navigation.ts +25 -0
  441. package/src/vue/composables/use-typed-navigation.test.ts +205 -0
  442. package/src/vue/drawer/drawer.test.ts +451 -0
  443. package/src/vue/drawer/index.ts +550 -0
  444. package/src/vue/drawer-screen.ts +32 -0
  445. package/src/vue/index.ts +70 -0
  446. package/src/vue/linking.ts +69 -0
  447. package/src/vue/navigation-context.ts +87 -0
  448. package/src/vue/nested-navigation.test.ts +155 -0
  449. package/src/vue/screen.ts +52 -0
  450. package/src/vue/stack/index.ts +376 -0
  451. package/src/vue/stack/stack.test.ts +501 -0
  452. package/src/vue/tab-screen.ts +29 -0
  453. package/src/vue/tabs/index.ts +302 -0
  454. package/src/vue/tabs/tabs.test.ts +278 -0
  455. package/symbiote-navigation.podspec +84 -0
@@ -0,0 +1,419 @@
1
+ // Co-located Angular-driven test (ADR 0025) for the @symbiote-native/navigation Angular Stack.
2
+ // Proves the shared core drives Angular correctly against an INJECTED codegen-shaped ViewConfig
3
+ // (mirrors packages/navigation/src/react/stack.test.tsx and
4
+ // adapters/angular/src/components/switch/switch.test.ts's mount/unmount harness - this codebase's
5
+ // Angular tests drive the real renderer via `mount`/`unmount`, not TestBed). Stack/ScreenDirective
6
+ // are imported from their own modules (NOT the package barrel, './index') so the third-party
7
+ // native-spec side-effect (../register) never loads headless.
8
+
9
+ import '@angular/compiler';
10
+ import { Component, CUSTOM_ELEMENTS_SCHEMA, Input, ViewChild, type Signal } from '@angular/core';
11
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
12
+ import {
13
+ mount,
14
+ unmount,
15
+ registerComposedComponent,
16
+ setNativeViewConfigSource,
17
+ } from '@symbiote-native/angular';
18
+ import type { INativeViewConfig } from '@symbiote-native/engine';
19
+ import { installFabric, type IFakeNode } from '@symbiote-native/test-utils';
20
+ import { Stack } from './index';
21
+ import type { INavigatorHandle } from './index';
22
+ import { ScreenDirective } from '../screen.directive';
23
+ import { injectRoute } from '../injectors';
24
+ import type { INavigatorState, IRoute, ISearchBarCommands } from '../../core';
25
+
26
+ const ROOT_TAG = 5120;
27
+ const SCREEN_VIEW = 'RNSScreen';
28
+ const STACK_VIEW = 'RNSScreenStack';
29
+ const HEADER_CONFIG_VIEW = 'RNSScreenStackHeaderConfig';
30
+ const HEADER_SUBVIEW_VIEW = 'RNSScreenStackHeaderSubview';
31
+ const SEARCH_BAR_VIEW = 'RNSSearchBar';
32
+
33
+ function directEvent(registrationName: string): { registrationName: string } {
34
+ return { registrationName };
35
+ }
36
+
37
+ const RNS_SCREEN_VIEW_CONFIG: INativeViewConfig = {
38
+ directEventTypes: {
39
+ topAppear: directEvent('onAppear'),
40
+ topDisappear: directEvent('onDisappear'),
41
+ topDismissed: directEvent('onDismissed'),
42
+ topHeaderBackButtonClicked: directEvent('onHeaderBackButtonClicked'),
43
+ },
44
+ validAttributes: {
45
+ screenId: true,
46
+ activityState: true,
47
+ stackAnimation: true,
48
+ stackPresentation: true,
49
+ },
50
+ };
51
+
52
+ const RNS_SCREEN_STACK_VIEW_CONFIG: INativeViewConfig = {
53
+ directEventTypes: { topFinishTransitioning: directEvent('onFinishTransitioning') },
54
+ validAttributes: {},
55
+ };
56
+
57
+ const RNS_HEADER_CONFIG_VIEW_CONFIG: INativeViewConfig = {
58
+ directEventTypes: {},
59
+ validAttributes: { title: true, hidden: true, backTitleVisible: true },
60
+ };
61
+
62
+ const RNS_SEARCH_BAR_VIEW_CONFIG: INativeViewConfig = {
63
+ directEventTypes: { topChangeText: directEvent('onChangeText') },
64
+ validAttributes: { placeholder: true },
65
+ };
66
+
67
+ const VIEW_CONFIGS: Record<string, INativeViewConfig> = {
68
+ [SCREEN_VIEW]: RNS_SCREEN_VIEW_CONFIG,
69
+ [STACK_VIEW]: RNS_SCREEN_STACK_VIEW_CONFIG,
70
+ [HEADER_CONFIG_VIEW]: RNS_HEADER_CONFIG_VIEW_CONFIG,
71
+ [SEARCH_BAR_VIEW]: RNS_SEARCH_BAR_VIEW_CONFIG,
72
+ };
73
+
74
+ const fabric = installFabric();
75
+ setNativeViewConfigSource(name => VIEW_CONFIGS[name]);
76
+ // On a real Metro build, adapters/angular's babel-register-composed.cjs auto-registers `Stack`
77
+ // as an anchor host by scanning the AOT-compiled @Component's selector - vitest never runs that
78
+ // pipeline, so this test drives the same self-registration entry point by hand (mirrors
79
+ // renderer.test.ts's 'RefApiDemo' convention). Without it, `<Stack>` falls through to a raw
80
+ // Fabric createNode('Stack') call instead of a non-painting anchor.
81
+ registerComposedComponent('Stack');
82
+
83
+ const tick = (): Promise<void> => new Promise(resolve => setTimeout(resolve, 0));
84
+
85
+ beforeEach(() => fabric.reset());
86
+ afterEach(() => unmount(ROOT_TAG));
87
+
88
+ function findInTree(
89
+ predicate: (node: IFakeNode) => boolean,
90
+ nodes = fabric.committed,
91
+ ): IFakeNode | undefined {
92
+ for (const node of nodes) {
93
+ if (predicate(node)) return node;
94
+ const found = findInTree(predicate, node.children);
95
+ if (found) return found;
96
+ }
97
+ return undefined;
98
+ }
99
+
100
+ function screenNodes(): IFakeNode[] {
101
+ const found: IFakeNode[] = [];
102
+ const collect = (nodes: readonly IFakeNode[]): void => {
103
+ for (const node of nodes) {
104
+ if (node.viewName === SCREEN_VIEW) found.push(node);
105
+ collect(node.children);
106
+ }
107
+ };
108
+ collect(fabric.committed);
109
+ return found;
110
+ }
111
+
112
+ function headerConfigOf(screen: IFakeNode): IFakeNode {
113
+ const header = screen.children.find(child => child.viewName === HEADER_CONFIG_VIEW);
114
+ if (!header) throw new Error('no header config child on screen');
115
+ return header;
116
+ }
117
+
118
+ @Component({
119
+ selector: 'home-screen',
120
+ standalone: true,
121
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
122
+ template: `<symbiote-text>home</symbiote-text>`,
123
+ })
124
+ class HomeScreenComponent {}
125
+
126
+ @Component({
127
+ selector: 'details-screen',
128
+ standalone: true,
129
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
130
+ template: `<symbiote-text>details</symbiote-text>`,
131
+ })
132
+ class DetailsScreenComponent {}
133
+
134
+ // Each mounted instance captures its own live route Signal (injectRoute) in order - index 0 is
135
+ // Home, index 1 the pushed Details - so a test reads params/key straight off the screen's DI-
136
+ // provided route instead of a former @Input, and a setParams still updates it reactively.
137
+ const capturedParamRoutes: Signal<IRoute<unknown>>[] = [];
138
+
139
+ @Component({
140
+ selector: 'params-screen',
141
+ standalone: true,
142
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
143
+ template: `<symbiote-text>params</symbiote-text>`,
144
+ })
145
+ class ParamsScreenComponent {
146
+ constructor() {
147
+ capturedParamRoutes.push(injectRoute());
148
+ }
149
+ }
150
+
151
+ let capturedHost: StackTestHost | undefined;
152
+
153
+ @Component({
154
+ selector: 'stack-test-host',
155
+ standalone: true,
156
+ imports: [Stack, ScreenDirective],
157
+ template: `
158
+ <Stack #nav initialRouteName="Home">
159
+ <ng-template
160
+ symbioteScreen
161
+ name="Home"
162
+ [component]="homeComponent"
163
+ [options]="homeOptions"
164
+ ></ng-template>
165
+ <ng-template symbioteScreen name="Details" [component]="detailsComponent"></ng-template>
166
+ </Stack>
167
+ `,
168
+ })
169
+ class StackTestHost {
170
+ @ViewChild('nav') nav!: Stack;
171
+
172
+ homeComponent = HomeScreenComponent;
173
+ detailsComponent = DetailsScreenComponent;
174
+ homeOptions: Record<string, unknown> = { title: 'Home' };
175
+
176
+ constructor() {
177
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
178
+ capturedHost = this;
179
+ }
180
+ }
181
+
182
+ async function mountStack(): Promise<INavigatorHandle> {
183
+ capturedHost = undefined;
184
+ mount(ROOT_TAG, StackTestHost);
185
+ await tick();
186
+ const host = capturedHost;
187
+ if (!host) throw new Error('StackTestHost never mounted');
188
+ return host.nav;
189
+ }
190
+
191
+ // Dedicated hosts (fixed component wiring declared up front, mirroring react/stack.test.tsx's
192
+ // per-test `createElement(...)` calls) for the params/setParams/search-bar scenarios below -
193
+ // swapping a `[component]`/`[options]` binding value on an already-mounted host AFTER `mount()`
194
+ // is a real but separate correctness question (does a bare, non-signal field write on the host
195
+ // get picked up by the next CD pass at all); sidestepped here by declaring the desired wiring
196
+ // once, up front, exactly like every other test in this file already does.
197
+
198
+ let capturedParamsHost: ParamsStackTestHost | undefined;
199
+
200
+ @Component({
201
+ selector: 'params-stack-test-host',
202
+ standalone: true,
203
+ imports: [Stack, ScreenDirective],
204
+ template: `
205
+ <Stack #nav initialRouteName="Home">
206
+ <ng-template
207
+ symbioteScreen
208
+ name="Home"
209
+ [component]="homeComponent"
210
+ [initialParams]="homeInitialParams"
211
+ ></ng-template>
212
+ <ng-template symbioteScreen name="Details" [component]="detailsComponent"></ng-template>
213
+ </Stack>
214
+ `,
215
+ })
216
+ class ParamsStackTestHost {
217
+ @ViewChild('nav') nav!: Stack;
218
+
219
+ homeComponent = ParamsScreenComponent;
220
+ detailsComponent = ParamsScreenComponent;
221
+ homeInitialParams: unknown = { tab: 'feed' };
222
+
223
+ constructor() {
224
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
225
+ capturedParamsHost = this;
226
+ }
227
+ }
228
+
229
+ async function mountParamsStack(): Promise<INavigatorHandle> {
230
+ capturedParamsHost = undefined;
231
+ mount(ROOT_TAG, ParamsStackTestHost);
232
+ await tick();
233
+ const host = capturedParamsHost;
234
+ if (!host) throw new Error('ParamsStackTestHost never mounted');
235
+ return host.nav;
236
+ }
237
+
238
+ @Component({
239
+ selector: 'search-bar-stack-test-host',
240
+ standalone: true,
241
+ imports: [Stack, ScreenDirective],
242
+ template: `
243
+ <Stack #nav initialRouteName="Home">
244
+ <ng-template
245
+ symbioteScreen
246
+ name="Home"
247
+ [component]="homeComponent"
248
+ [options]="homeOptions"
249
+ ></ng-template>
250
+ </Stack>
251
+ `,
252
+ })
253
+ class SearchBarStackTestHost {
254
+ @ViewChild('nav') nav!: Stack;
255
+
256
+ homeComponent = HomeScreenComponent;
257
+ @Input() homeOptions: Record<string, unknown> = {
258
+ title: 'Home',
259
+ headerSearchBarOptions: { placeholder: 'Search' },
260
+ };
261
+ }
262
+
263
+ describe('Angular Stack navigator', () => {
264
+ it('mounts only the initial route as an RNSScreen, focused, with its header title', async () => {
265
+ await mountStack();
266
+ const screens = screenNodes();
267
+ expect(screens).toHaveLength(1);
268
+ expect(screens[0].props.activityState).toBe(2);
269
+ expect(fabric.find(n => n.viewName === STACK_VIEW)).toBeDefined();
270
+ expect(headerConfigOf(screens[0]).props.title).toBe('Home');
271
+ });
272
+
273
+ it('push() mounts a second RNSScreen and keeps both at activityState 2', async () => {
274
+ const handle = await mountStack();
275
+ handle.push('Details');
276
+ await tick();
277
+ const screens = screenNodes();
278
+ expect(screens).toHaveLength(2);
279
+ expect(screens[0].props.activityState).toBe(2);
280
+ expect(screens[1].props.activityState).toBe(2);
281
+ expect(handle.canGoBack()).toBe(true);
282
+ });
283
+
284
+ it('pop() unmounts back down to the previous route', async () => {
285
+ const handle = await mountStack();
286
+ handle.push('Details');
287
+ await tick();
288
+ expect(screenNodes()).toHaveLength(2);
289
+ handle.pop();
290
+ await tick();
291
+ expect(screenNodes()).toHaveLength(1);
292
+ expect(handle.canGoBack()).toBe(false);
293
+ });
294
+
295
+ it('refuses to pop the last route', async () => {
296
+ const handle = await mountStack();
297
+ handle.pop();
298
+ await tick();
299
+ expect(screenNodes()).toHaveLength(1);
300
+ });
301
+
302
+ it('drives a pop from the native onDismissed event (iOS swipe/interactive dismiss)', async () => {
303
+ const handle = await mountStack();
304
+ handle.push('Details');
305
+ await tick();
306
+ const top = screenNodes()[1];
307
+ fabric.fireEvent(top.instanceHandle, 'topDismissed', { dismissCount: 1 });
308
+ await tick();
309
+ expect(screenNodes()).toHaveLength(1);
310
+ });
311
+
312
+ it('drives a pop from the native onHeaderBackButtonClicked event', async () => {
313
+ const handle = await mountStack();
314
+ handle.push('Details');
315
+ await tick();
316
+ const top = screenNodes()[1];
317
+ fabric.fireEvent(top.instanceHandle, 'topHeaderBackButtonClicked', {});
318
+ await tick();
319
+ expect(screenNodes()).toHaveLength(1);
320
+ });
321
+
322
+ it('mounts the registered screen component as the RNSScreen content', async () => {
323
+ await mountStack();
324
+ expect(findInTree(n => n.viewName === 'RCTRawText' && n.props.text === 'home')).toBeDefined();
325
+ });
326
+
327
+ it('exposes route.params to the pushed screen via injectRoute', async () => {
328
+ capturedParamRoutes.length = 0;
329
+ const handle = await mountParamsStack();
330
+ handle.push('Details', { id: 42 });
331
+ await tick();
332
+ expect(capturedParamRoutes.at(-1)?.().params).toEqual({ id: 42 });
333
+ });
334
+
335
+ it('setParams() merges onto the focused route without changing the stack shape', async () => {
336
+ capturedParamRoutes.length = 0;
337
+ const handle = await mountParamsStack();
338
+ handle.push('Details', { id: 1 });
339
+ await tick();
340
+ handle.setParams({ id: 2 });
341
+ await tick();
342
+ expect(capturedParamRoutes.at(-1)?.().params).toEqual({ id: 2 });
343
+ expect(screenNodes()).toHaveLength(2);
344
+ });
345
+
346
+ it('setParams() targets a route by key when given, not just the focused one', async () => {
347
+ capturedParamRoutes.length = 0;
348
+ const handle = await mountParamsStack();
349
+ // Home mounts first (ParamsScreenComponent, initialParams: {tab:'feed'}), capturing its route.
350
+ const homeRoute = capturedParamRoutes[0];
351
+ if (homeRoute === undefined) throw new Error('home route was never captured');
352
+ const homeKey = homeRoute().key;
353
+ handle.push('Details');
354
+ await tick();
355
+ handle.setParams({ tab: 'search' }, homeKey);
356
+ await tick();
357
+ expect(homeRoute().params).toEqual({ tab: 'search' });
358
+ });
359
+
360
+ it('reset() replaces the whole stack with the given state', async () => {
361
+ const handle = await mountStack();
362
+ handle.push('Details');
363
+ await tick();
364
+ expect(screenNodes()).toHaveLength(2);
365
+
366
+ const nextState: INavigatorState = {
367
+ routes: [{ key: 'reset-1', name: 'Details', params: { id: 7 } }],
368
+ };
369
+ handle.reset(nextState);
370
+ await tick();
371
+
372
+ const screens = screenNodes();
373
+ expect(screens).toHaveLength(1);
374
+ expect(screens[0].props.activityState).toBe(2);
375
+ expect(handle.canGoBack()).toBe(false);
376
+ });
377
+
378
+ it('nests an RNSSearchBar child, wrapped in an RNSScreenStackHeaderSubview, when headerSearchBarOptions is set', async () => {
379
+ mount(ROOT_TAG, SearchBarStackTestHost, {
380
+ initialProps: {
381
+ homeOptions: { title: 'Home', headerSearchBarOptions: { placeholder: 'Search' } },
382
+ },
383
+ });
384
+ await tick();
385
+ const header = headerConfigOf(screenNodes()[0]);
386
+ expect(header.children).toHaveLength(1);
387
+ const subview = header.children[0];
388
+ expect(subview.viewName).toBe(HEADER_SUBVIEW_VIEW);
389
+ expect(subview.props.type).toBe('searchBar');
390
+ expect(subview.children).toHaveLength(1);
391
+ expect(subview.children[0].viewName).toBe(SEARCH_BAR_VIEW);
392
+ expect(subview.children[0].props.placeholder).toBe('Search');
393
+ });
394
+
395
+ it('drives imperative SearchBarCommands (focus/setText/…) through the app-supplied ref', async () => {
396
+ const searchBarRef: { current: ISearchBarCommands | null } = { current: null };
397
+ mount(ROOT_TAG, SearchBarStackTestHost, {
398
+ initialProps: {
399
+ homeOptions: {
400
+ title: 'Home',
401
+ headerSearchBarOptions: { placeholder: 'Search', ref: searchBarRef },
402
+ },
403
+ },
404
+ });
405
+ await tick();
406
+
407
+ expect(searchBarRef.current).not.toBeNull();
408
+ searchBarRef.current?.focus();
409
+ expect(fabric.commands.at(-1)).toMatchObject({ commandName: 'focus', args: [] });
410
+ searchBarRef.current?.setText('preset');
411
+ expect(fabric.commands.at(-1)).toMatchObject({ commandName: 'setText', args: ['preset'] });
412
+ });
413
+
414
+ it('renders the header config with zero children when there is no search bar', async () => {
415
+ await mountStack();
416
+ const header = headerConfigOf(screenNodes()[0]);
417
+ expect(header.children).toHaveLength(0);
418
+ });
419
+ });
@@ -0,0 +1,26 @@
1
+ // [symbioteTabScreen]: Tab's twin of screen.directive.ts's ScreenDirective - a declarative marker
2
+ // read via @ContentChildren to build the static name -> {component, options} registry, then Tab
3
+ // mounts the FOCUSED route's component itself. Mirrors react/tab-screen.ts's TabScreen minus the
4
+ // stack-only concepts (no push/pop lifecycle events to wire).
5
+
6
+ import { Directive, Input, type Type } from '@angular/core';
7
+ import type { IRoute, ITabOptions } from '../core';
8
+ import type { ITabNavigatorHandle } from '../core';
9
+
10
+ export type ITabScreenOptionsArgs = {
11
+ route: IRoute<unknown>;
12
+ navigation: ITabNavigatorHandle;
13
+ };
14
+
15
+ export type ITabScreenOptionsResolver = (props: ITabScreenOptionsArgs) => ITabOptions;
16
+
17
+ @Directive({
18
+ selector: 'ng-template[symbioteTabScreen]',
19
+ standalone: true,
20
+ })
21
+ export class TabScreenDirective {
22
+ @Input({ required: true }) name!: string;
23
+ @Input({ required: true }) component!: Type<unknown>;
24
+ @Input() options?: ITabOptions | ITabScreenOptionsResolver;
25
+ @Input() initialParams?: unknown;
26
+ }
@@ -0,0 +1,254 @@
1
+ // Tab, the Angular 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
+ // the React/Vue adapters; here Angular supplies the lifecycle - a signal for the focused index, a
4
+ // per-instance counter for route-key generation, `jumpTo`/`setParams` as plain public methods
5
+ // directly on the class (no ref forwarding needed, see stack.ts's header, same reasoning: `Tab
6
+ // implements ITabNavigatorHandle`) - plus the descriptor bridge (`symbiote-descriptor-outlet`,
7
+ // `@symbiote-native/angular`) for the tab-bar leaf, exactly like Stack bridges its header config.
8
+ // Unlike Stack, a bottom-tabs bar is a PURE-JS UI: it paints
9
+ // ordinary `symbiote-view`/`symbiote-text` primitives via the shared render fn, so there is no
10
+ // react-native-screens ViewConfig to register - Tab needs no `../register` import. Every tag this
11
+ // template names (`View`, `symbiote-descriptor-outlet`) is a REAL imported Angular component (no
12
+ // raw non-dashed native tag names the way stack.ts needs `NO_ERRORS_SCHEMA` for), so no loosened
13
+ // schema is needed here at all.
14
+ //
15
+ // RESOLVED (see stack.ts's header, identical reasoning): `'Tab'` has an `ANCHOR_HOST_COMPONENTS`
16
+ // entry in `adapters/angular/src/renderer.ts`, so a real device build paints `<Tab>` correctly as
17
+ // a nested tag.
18
+
19
+ import {
20
+ ChangeDetectionStrategy,
21
+ Component,
22
+ ContentChildren,
23
+ Input,
24
+ QueryList,
25
+ signal,
26
+ untracked,
27
+ type AfterContentInit,
28
+ type OnDestroy,
29
+ type Type,
30
+ } from '@angular/core';
31
+ import { NgComponentOutlet } from '@angular/common';
32
+ import { dlog } from '@symbiote-native/engine';
33
+ import { DescriptorOutlet, View } from '@symbiote-native/angular';
34
+ import type { IDescriptor } from '@symbiote-native/components';
35
+ import {
36
+ NAVIGATION_EVENT_BLUR,
37
+ NAVIGATION_EVENT_FOCUS,
38
+ createInitialTabState,
39
+ createNavigationEmitter,
40
+ diffFocusedRoute,
41
+ isFocusedRoute,
42
+ renderTabBar,
43
+ tabRouterReducer,
44
+ } from '../../core';
45
+ import type {
46
+ INavigationEmitter,
47
+ IRoute,
48
+ ITabBarItemView,
49
+ ITabNavigatorHandle,
50
+ ITabOptions,
51
+ ITabRouterAction,
52
+ ITabRouterState,
53
+ } from '../../core';
54
+ import { NavigationScopeDirective } from '../navigation-scope.directive';
55
+ import { TabScreenDirective } from '../tab-screen.directive';
56
+ import type { ITabScreenOptionsArgs } from '../tab-screen.directive';
57
+
58
+ export type { ITabNavigatorHandle } from '../../core';
59
+
60
+ const TAB_ROOT_STYLE = { flex: 1 };
61
+ const TAB_CONTENT_STYLE = { flex: 1 };
62
+
63
+ let tabInstanceCounter = 0;
64
+
65
+ @Component({
66
+ selector: 'Tab',
67
+ standalone: true,
68
+ imports: [NgComponentOutlet, NavigationScopeDirective, DescriptorOutlet, View],
69
+ changeDetection: ChangeDetectionStrategy.OnPush,
70
+ template: `
71
+ <View [style]="rootStyle">
72
+ <View [style]="contentStyle">
73
+ @if (focusedRoute(); as route) {
74
+ @if (componentForRoute(route); as component) {
75
+ <ng-container
76
+ [symbioteNavigationScope]="route"
77
+ [navigation]="this"
78
+ [emitter]="focusedRouteEmitter()"
79
+ >
80
+ <ng-container *ngComponentOutlet="component" />
81
+ </ng-container>
82
+ }
83
+ }
84
+ </View>
85
+ <symbiote-descriptor-outlet [node]="tabBarDescriptor()" />
86
+ </View>
87
+ `,
88
+ })
89
+ export class Tab implements AfterContentInit, OnDestroy, ITabNavigatorHandle {
90
+ @ContentChildren(TabScreenDirective)
91
+ private readonly tabScreenChildren!: QueryList<TabScreenDirective>;
92
+
93
+ @Input() initialRouteName?: string;
94
+ @Input() screenOptions?: ITabOptions;
95
+
96
+ readonly rootStyle = TAB_ROOT_STYLE;
97
+ readonly contentStyle = TAB_CONTENT_STYLE;
98
+
99
+ private readonly routeIdPrefix = `tab-${(tabInstanceCounter += 1)}`;
100
+ // Keyed by name -> the LIVE TabScreenDirective instance - see stack.ts's matching comment for
101
+ // why a snapshot copy would go stale on an in-place `[options]`/`[component]` change.
102
+ private readonly registry = new Map<string, TabScreenDirective>();
103
+ private tabScreenChildrenSubscription: { unsubscribe: () => void } | undefined;
104
+
105
+ private readonly stateSignal = signal<ITabRouterState | undefined>(undefined);
106
+ readonly state = this.stateSignal.asReadonly();
107
+
108
+ private currentEmitterKey: string | undefined;
109
+ private currentEmitter: INavigationEmitter | undefined;
110
+
111
+ readonly jumpTo = (name: string, params?: unknown): void =>
112
+ this.dispatch({ type: 'jumpTo', name, params });
113
+ readonly setParams = (params: unknown, key: string): void =>
114
+ this.dispatch({ type: 'setParams', key, params });
115
+
116
+ ngAfterContentInit(): void {
117
+ this.rebuildRegistry();
118
+ this.initializeState();
119
+ this.tabScreenChildrenSubscription = this.tabScreenChildren.changes.subscribe(() => {
120
+ this.rebuildRegistry();
121
+ });
122
+ }
123
+
124
+ ngOnDestroy(): void {
125
+ this.tabScreenChildrenSubscription?.unsubscribe();
126
+ if (this.currentEmitter) this.currentEmitter.emit(NAVIGATION_EVENT_BLUR);
127
+ }
128
+
129
+ private rebuildRegistry(): void {
130
+ this.registry.clear();
131
+ for (const screen of this.tabScreenChildren) {
132
+ this.registry.set(screen.name, screen);
133
+ }
134
+ }
135
+
136
+ private routesFromRegistry(): IRoute<unknown>[] {
137
+ return Array.from(this.registry.entries()).map(([name, entry]) => ({
138
+ key: `${this.routeIdPrefix}-${name}`,
139
+ name,
140
+ params: entry.initialParams,
141
+ }));
142
+ }
143
+
144
+ private initializeState(): void {
145
+ if (this.stateSignal() !== undefined) return;
146
+ const routes = this.routesFromRegistry();
147
+ if (routes.length === 0) dlog('Tab: no <ng-template symbioteTabScreen> children registered');
148
+ this.stateSignal.set(createInitialTabState(routes, this.initialRouteName));
149
+ }
150
+
151
+ private dispatch(action: ITabRouterAction): void {
152
+ const current = this.stateSignal();
153
+ if (current === undefined) return;
154
+ this.stateSignal.set(tabRouterReducer(current, action));
155
+ }
156
+
157
+ private resolveTabOptions(entry: TabScreenDirective, route: IRoute<unknown>): ITabOptions {
158
+ const props: ITabScreenOptionsArgs = { route, navigation: this };
159
+ const own = typeof entry.options === 'function' ? entry.options(props) : entry.options;
160
+ return { ...this.screenOptions, ...own };
161
+ }
162
+
163
+ focusedRoute(): IRoute<unknown> | undefined {
164
+ const state = this.stateSignal();
165
+ return state?.routes[state.index];
166
+ }
167
+
168
+ componentForRoute(route: IRoute<unknown>): Type<unknown> | null {
169
+ return this.registry.get(route.name)?.component ?? null;
170
+ }
171
+
172
+ // Lazily creates/replaces the focused route's emitter and synthesizes focus/blur - Tab paints
173
+ // its own bar in pure JS (no native onAppear/onDisappear the way Stack's RNSScreen has), so
174
+ // focus/blur is synthesized here exactly like react/tabs.ts's own useEffect does, just idempotent
175
+ // per read (called repeatedly with the same key during one CD pass) instead of dependency-array
176
+ // gated. Keyed on the route KEY, not the object, so a setParams-only change doesn't re-fire.
177
+ //
178
+ // Called from the template ([emitter]="focusedRouteEmitter()"), which runs inside Angular's
179
+ // reactive-read tracking context for the current CD pass - unlike React's useEffect, which runs
180
+ // in a separate post-commit phase. Two consequences of that, both fixed below:
181
+ //
182
+ // 1. Writing a signal from inside a tracked template read throws Angular's NG600 ("signal write
183
+ // during a template execution") - untracked() opts this whole synthesis out of that tracking
184
+ // context, matching what the post-commit useEffect phase gives React for free.
185
+ // 2. The NEW route's screen component (injectIsFocused's listener source) is created by
186
+ // *ngComponentOutlet AFTER this binding is evaluated, but still within the SAME synchronous
187
+ // template refresh - so emitting FOCUS right here fires to zero listeners and is silently
188
+ // lost forever (isFocused stays false permanently, even for the very first-focused screen).
189
+ // queueMicrotask defers the FOCUS emit past the end of the current synchronous refresh
190
+ // (which includes the new screen's construction), guaranteeing its injectIsFocused()
191
+ // listener is already attached by the time it fires. BLUR doesn't need this: the outgoing
192
+ // screen's listener was already attached on an earlier tick, so it's safe to fire now.
193
+ focusedRouteEmitter(): INavigationEmitter {
194
+ const key = this.focusedRoute()?.key;
195
+ return untracked(() => {
196
+ if (key === this.currentEmitterKey && this.currentEmitter) return this.currentEmitter;
197
+ const { blurKey, focusKey } = diffFocusedRoute(this.currentEmitterKey, key);
198
+ if (blurKey !== undefined && this.currentEmitter) {
199
+ dlog('Tab: previous route blurred');
200
+ this.currentEmitter.emit(NAVIGATION_EVENT_BLUR);
201
+ }
202
+ const emitter = createNavigationEmitter();
203
+ this.currentEmitter = emitter;
204
+ this.currentEmitterKey = key;
205
+ if (focusKey !== undefined) {
206
+ queueMicrotask(() => {
207
+ if (this.currentEmitter !== emitter) return; // superseded by a later switch
208
+ dlog(`Tab: route "${this.focusedRoute()?.name}" focused`);
209
+ emitter.emit(NAVIGATION_EVENT_FOCUS);
210
+ });
211
+ }
212
+ return emitter;
213
+ });
214
+ }
215
+
216
+ tabBarDescriptor(): IDescriptor {
217
+ const state = this.stateSignal();
218
+ const routes = state?.routes ?? [];
219
+ const focusedIndex = state?.index ?? 0;
220
+
221
+ const items: ITabBarItemView[] = routes.map((route, index) => {
222
+ const entry = this.registry.get(route.name);
223
+ const focused = isFocusedRoute(index, focusedIndex);
224
+ if (!entry) {
225
+ dlog(`Tab: no screen registered for route name "${route.name}"`);
226
+ return { key: route.key, focused, label: route.name, passthrough: {} };
227
+ }
228
+ const options = this.resolveTabOptions(entry, route);
229
+ return {
230
+ key: route.key,
231
+ focused,
232
+ label: options.tabBarLabel ?? options.title ?? route.name,
233
+ icon: options.tabBarIcon,
234
+ badge: options.tabBarBadge,
235
+ activeTintColor: options.tabBarActiveTintColor,
236
+ inactiveTintColor: options.tabBarInactiveTintColor,
237
+ passthrough: {
238
+ onPress: () => this.jumpTo(route.name),
239
+ accessibilityRole: 'tab',
240
+ accessibilityState: { selected: focused },
241
+ },
242
+ };
243
+ });
244
+
245
+ const focusedRoute = this.focusedRoute();
246
+ const focusedEntry = focusedRoute ? this.registry.get(focusedRoute.name) : undefined;
247
+ const focusedOptions =
248
+ focusedEntry && focusedRoute
249
+ ? this.resolveTabOptions(focusedEntry, focusedRoute)
250
+ : this.screenOptions;
251
+
252
+ return renderTabBar({ items, style: focusedOptions?.tabBarStyle, passthrough: {} });
253
+ }
254
+ }