@symbiote-native/navigation 1.0.0 → 2.0.1

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 (195) hide show
  1. package/README.md +159 -0
  2. package/package.json +14 -12
  3. package/build/angular/drawer/index.d.ts +0 -76
  4. package/build/angular/drawer/index.js +0 -508
  5. package/build/angular/drawer-screen.directive.d.ts +0 -14
  6. package/build/angular/drawer-screen.directive.js +0 -87
  7. package/build/angular/index.d.ts +0 -23
  8. package/build/angular/index.js +0 -22
  9. package/build/angular/injectors/index.d.ts +0 -13
  10. package/build/angular/injectors/index.js +0 -8
  11. package/build/angular/injectors/inject-drawer-navigation.d.ts +0 -4
  12. package/build/angular/injectors/inject-drawer-navigation.js +0 -12
  13. package/build/angular/injectors/inject-focus-effect.d.ts +0 -2
  14. package/build/angular/injectors/inject-focus-effect.js +0 -29
  15. package/build/angular/injectors/inject-is-focused.d.ts +0 -2
  16. package/build/angular/injectors/inject-is-focused.js +0 -20
  17. package/build/angular/injectors/inject-navigation-state.d.ts +0 -3
  18. package/build/angular/injectors/inject-navigation-state.js +0 -35
  19. package/build/angular/injectors/inject-navigation.d.ts +0 -7
  20. package/build/angular/injectors/inject-navigation.js +0 -16
  21. package/build/angular/injectors/inject-route.d.ts +0 -3
  22. package/build/angular/injectors/inject-route.js +0 -20
  23. package/build/angular/injectors/inject-stack-navigation.d.ts +0 -4
  24. package/build/angular/injectors/inject-stack-navigation.js +0 -15
  25. package/build/angular/injectors/inject-tab-navigation.d.ts +0 -4
  26. package/build/angular/injectors/inject-tab-navigation.js +0 -11
  27. package/build/angular/linking.d.ts +0 -3
  28. package/build/angular/linking.js +0 -43
  29. package/build/angular/navigation-context.service.d.ts +0 -13
  30. package/build/angular/navigation-context.service.js +0 -105
  31. package/build/angular/navigation-scope.directive.d.ts +0 -10
  32. package/build/angular/navigation-scope.directive.js +0 -90
  33. package/build/angular/screen.directive.d.ts +0 -22
  34. package/build/angular/screen.directive.js +0 -90
  35. package/build/angular/search-bar-ref.directive.d.ts +0 -10
  36. package/build/angular/search-bar-ref.directive.js +0 -88
  37. package/build/angular/stack/index.d.ts +0 -55
  38. package/build/angular/stack/index.js +0 -386
  39. package/build/angular/tab-screen.directive.d.ts +0 -14
  40. package/build/angular/tab-screen.directive.js +0 -87
  41. package/build/angular/tabs/index.d.ts +0 -42
  42. package/build/angular/tabs/index.js +0 -263
  43. package/build/core/constants.d.ts +0 -27
  44. package/build/core/constants.js +0 -61
  45. package/build/core/drawer-options/index.d.ts +0 -64
  46. package/build/core/drawer-options/index.js +0 -200
  47. package/build/core/drawer-router-state/index.d.ts +0 -21
  48. package/build/core/drawer-router-state/index.js +0 -40
  49. package/build/core/guards.d.ts +0 -2
  50. package/build/core/guards.js +0 -22
  51. package/build/core/header-bar-buttons.d.ts +0 -9
  52. package/build/core/header-bar-buttons.js +0 -163
  53. package/build/core/index.d.ts +0 -29
  54. package/build/core/index.js +0 -32
  55. package/build/core/linking-config/index.d.ts +0 -13
  56. package/build/core/linking-config/index.js +0 -171
  57. package/build/core/navigation-events/index.d.ts +0 -16
  58. package/build/core/navigation-events/index.js +0 -57
  59. package/build/core/navigator-handles/index.d.ts +0 -30
  60. package/build/core/navigator-handles/index.js +0 -15
  61. package/build/core/navigator-props.d.ts +0 -216
  62. package/build/core/navigator-props.js +0 -7
  63. package/build/core/navigator-state/index.d.ts +0 -36
  64. package/build/core/navigator-state/index.js +0 -81
  65. package/build/core/render-drawer.d.ts +0 -13
  66. package/build/core/render-drawer.js +0 -88
  67. package/build/core/render-stack.d.ts +0 -35
  68. package/build/core/render-stack.js +0 -294
  69. package/build/core/render-tabs.d.ts +0 -20
  70. package/build/core/render-tabs.js +0 -97
  71. package/build/core/screen-options.d.ts +0 -4
  72. package/build/core/screen-options.js +0 -133
  73. package/build/core/search-bar-commands.d.ts +0 -10
  74. package/build/core/search-bar-commands.js +0 -42
  75. package/build/core/state-persistence/index.d.ts +0 -3
  76. package/build/core/state-persistence/index.js +0 -23
  77. package/build/core/tab-options.d.ts +0 -13
  78. package/build/core/tab-options.js +0 -10
  79. package/build/core/tab-router-state/index.d.ts +0 -19
  80. package/build/core/tab-router-state/index.js +0 -51
  81. package/build/react/collect-registry.d.ts +0 -4
  82. package/build/react/collect-registry.js +0 -15
  83. package/build/react/drawer/index.d.ts +0 -29
  84. package/build/react/drawer/index.js +0 -277
  85. package/build/react/drawer-screen.d.ts +0 -14
  86. package/build/react/drawer-screen.js +0 -8
  87. package/build/react/hooks/index.d.ts +0 -12
  88. package/build/react/hooks/index.js +0 -8
  89. package/build/react/hooks/use-drawer-navigation.d.ts +0 -4
  90. package/build/react/hooks/use-drawer-navigation.js +0 -12
  91. package/build/react/hooks/use-focus-effect.d.ts +0 -2
  92. package/build/react/hooks/use-focus-effect.js +0 -29
  93. package/build/react/hooks/use-is-focused.d.ts +0 -1
  94. package/build/react/hooks/use-is-focused.js +0 -22
  95. package/build/react/hooks/use-navigation-state/index.d.ts +0 -2
  96. package/build/react/hooks/use-navigation-state/index.js +0 -39
  97. package/build/react/hooks/use-navigation.d.ts +0 -7
  98. package/build/react/hooks/use-navigation.js +0 -16
  99. package/build/react/hooks/use-route.d.ts +0 -2
  100. package/build/react/hooks/use-route.js +0 -8
  101. package/build/react/hooks/use-stack-navigation.d.ts +0 -4
  102. package/build/react/hooks/use-stack-navigation.js +0 -14
  103. package/build/react/hooks/use-tab-navigation.d.ts +0 -4
  104. package/build/react/hooks/use-tab-navigation.js +0 -12
  105. package/build/react/index.d.ts +0 -24
  106. package/build/react/index.js +0 -21
  107. package/build/react/linking.d.ts +0 -3
  108. package/build/react/linking.js +0 -47
  109. package/build/react/navigation-context.d.ts +0 -10
  110. package/build/react/navigation-context.js +0 -22
  111. package/build/react/screen.d.ts +0 -22
  112. package/build/react/screen.js +0 -7
  113. package/build/react/stack/index.d.ts +0 -13
  114. package/build/react/stack/index.js +0 -202
  115. package/build/react/tab-screen.d.ts +0 -15
  116. package/build/react/tab-screen.js +0 -8
  117. package/build/react/tabs/index.d.ts +0 -12
  118. package/build/react/tabs/index.js +0 -116
  119. package/build/register.d.ts +0 -7
  120. package/build/register.js +0 -58
  121. package/build/vue/composables/index.d.ts +0 -12
  122. package/build/vue/composables/index.js +0 -8
  123. package/build/vue/composables/use-drawer-navigation.d.ts +0 -5
  124. package/build/vue/composables/use-drawer-navigation.js +0 -15
  125. package/build/vue/composables/use-focus-effect.d.ts +0 -1
  126. package/build/vue/composables/use-focus-effect.js +0 -31
  127. package/build/vue/composables/use-is-focused.d.ts +0 -2
  128. package/build/vue/composables/use-is-focused.js +0 -28
  129. package/build/vue/composables/use-navigation-state.d.ts +0 -3
  130. package/build/vue/composables/use-navigation-state.js +0 -29
  131. package/build/vue/composables/use-navigation.d.ts +0 -8
  132. package/build/vue/composables/use-navigation.js +0 -13
  133. package/build/vue/composables/use-route.d.ts +0 -3
  134. package/build/vue/composables/use-route.js +0 -9
  135. package/build/vue/composables/use-stack-navigation.d.ts +0 -5
  136. package/build/vue/composables/use-stack-navigation.js +0 -18
  137. package/build/vue/composables/use-tab-navigation.d.ts +0 -5
  138. package/build/vue/composables/use-tab-navigation.js +0 -15
  139. package/build/vue/drawer/index.d.ts +0 -19
  140. package/build/vue/drawer/index.js +0 -390
  141. package/build/vue/drawer-screen.d.ts +0 -14
  142. package/build/vue/drawer-screen.js +0 -12
  143. package/build/vue/index.d.ts +0 -21
  144. package/build/vue/index.js +0 -19
  145. package/build/vue/linking.d.ts +0 -3
  146. package/build/vue/linking.js +0 -57
  147. package/build/vue/navigation-context.d.ts +0 -16
  148. package/build/vue/navigation-context.js +0 -65
  149. package/build/vue/screen.d.ts +0 -21
  150. package/build/vue/screen.js +0 -12
  151. package/build/vue/stack/index.d.ts +0 -9
  152. package/build/vue/stack/index.js +0 -279
  153. package/build/vue/tab-screen.d.ts +0 -14
  154. package/build/vue/tab-screen.js +0 -11
  155. package/build/vue/tabs/index.d.ts +0 -10
  156. package/build/vue/tabs/index.js +0 -209
  157. package/build-ngc/angular/drawer.d.ts +0 -79
  158. package/build-ngc/angular/drawer.js +0 -475
  159. package/build-ngc/angular/drawer.js.map +0 -1
  160. package/build-ngc/angular/stack.d.ts +0 -58
  161. package/build-ngc/angular/stack.js +0 -402
  162. package/build-ngc/angular/stack.js.map +0 -1
  163. package/build-ngc/angular/tabs.d.ts +0 -45
  164. package/build-ngc/angular/tabs.js +0 -231
  165. package/build-ngc/angular/tabs.js.map +0 -1
  166. package/build-ngc/core/drawer-options.d.ts +0 -64
  167. package/build-ngc/core/drawer-options.js +0 -201
  168. package/build-ngc/core/drawer-options.js.map +0 -1
  169. package/build-ngc/core/drawer-router-state.d.ts +0 -21
  170. package/build-ngc/core/drawer-router-state.js +0 -41
  171. package/build-ngc/core/drawer-router-state.js.map +0 -1
  172. package/build-ngc/core/href.d.ts +0 -16
  173. package/build-ngc/core/href.js +0 -55
  174. package/build-ngc/core/href.js.map +0 -1
  175. package/build-ngc/core/linking-config.d.ts +0 -13
  176. package/build-ngc/core/linking-config.js +0 -172
  177. package/build-ngc/core/linking-config.js.map +0 -1
  178. package/build-ngc/core/navigation-events.d.ts +0 -16
  179. package/build-ngc/core/navigation-events.js +0 -58
  180. package/build-ngc/core/navigation-events.js.map +0 -1
  181. package/build-ngc/core/navigator-handles.d.ts +0 -30
  182. package/build-ngc/core/navigator-handles.js +0 -16
  183. package/build-ngc/core/navigator-handles.js.map +0 -1
  184. package/build-ngc/core/navigator-state.d.ts +0 -36
  185. package/build-ngc/core/navigator-state.js +0 -82
  186. package/build-ngc/core/navigator-state.js.map +0 -1
  187. package/build-ngc/core/search-params.d.ts +0 -9
  188. package/build-ngc/core/search-params.js +0 -60
  189. package/build-ngc/core/search-params.js.map +0 -1
  190. package/build-ngc/core/state-persistence.d.ts +0 -3
  191. package/build-ngc/core/state-persistence.js +0 -24
  192. package/build-ngc/core/state-persistence.js.map +0 -1
  193. package/build-ngc/core/tab-router-state.d.ts +0 -19
  194. package/build-ngc/core/tab-router-state.js +0 -52
  195. package/build-ngc/core/tab-router-state.js.map +0 -1
@@ -1,65 +0,0 @@
1
- // The lifecycle half of the framework-agnostic emitter (../core/navigation-events): Vue has no
2
- // "Context" concept the way React does. The equivalent here is a plain provide/inject
3
- // getter/setter pair over one shared InjectionKey (mirrors this codebase's own pd-widgets
4
- // convention: `provideX(value)` writes, `injectX()` reads and throws if nothing was provided), so
5
- // a screen's subtree can read its own route, navigator handle, and per-route emitter without
6
- // prop-drilling - the Vue twin of react/navigation-context.ts's React Context, collapsed into one
7
- // key the same way (a symbiote screen only ever needs its OWN route, no per-navigator
8
- // param-list generics in v1 scope, see screen.ts).
9
- //
10
- // Vue's provide() must run once, synchronously, inside a component's OWN setup(). Unlike React,
11
- // which re-creates a fresh `<Context.Provider value={...}>` element (and therefore a fresh value)
12
- // on every render loop iteration, Vue's provide/inject is scoped to the component INSTANCE, not to
13
- // a render-time position. Stack/Tab/Drawer keep every mounted route's screen alive as a SEPARATE
14
- // sibling subtree (Stack keeps every pushed route mounted; see stack.ts), and each one needs its
15
- // OWN distinct provided value (its own route/emitter) invisible to its siblings - that is an
16
- // inherent property of any hierarchical dependency-injection scheme, provide/inject included, not
17
- // a React artifact. NavigationScope is the small setup-boundary component that gives each route
18
- // subtree its own instance to call provideNavigationScope from: stack.ts/tabs.ts/drawer.ts's
19
- // render loop mounts ONE NavigationScope per route (keyed by route.key, exactly like React's
20
- // per-route `<NavigationContext.Provider>`), and that instance's setup() calls the setter ONCE,
21
- // refreshed from the latest `value` prop on every one of ITS OWN re-renders - consumers read
22
- // `.value` off the injected ref, so a later change (e.g. setParams producing a new route object
23
- // for the same key) still reaches them reactively.
24
- //
25
- // `parent` threads the ambient value a navigator read on ITS OWN mount (undefined at the root)
26
- // into the value it provides to its own screens, forming a linked list - this is what lets a
27
- // screen nested inside e.g. a Stack-screen-renders-a-Tab composition reach the enclosing Stack via
28
- // useNavigation().getParent().
29
- import { defineComponent, inject, provide, shallowRef } from '@vue/runtime-core';
30
- const NAVIGATION_SCOPE_KEY = Symbol('navigation-scope');
31
- // The setter half of the pair - writes the CURRENT route's scope onto the shared key. Called only
32
- // from NavigationScope's own setup below (never directly by app code).
33
- function provideNavigationScope(value) {
34
- provide(NAVIGATION_SCOPE_KEY, value);
35
- }
36
- // The getter half of the pair - every composable in ./composables and every navigator
37
- // (stack.ts/tabs.ts/drawer.ts, reading the AMBIENT/parent scope on their own mount) calls this
38
- // directly; undefined simply means "no ancestor NavigationScope", which is a legitimate state (the
39
- // nesting root) rather than an error - callers that require one throw their own message.
40
- export function injectNavigationScope() {
41
- return inject(NAVIGATION_SCOPE_KEY, undefined);
42
- }
43
- // The "throw if missing" half every composable in ./composables needs (useNavigation, useRoute,
44
- // useIsFocused, useFocusEffect, useNavigationState) - they all called injectNavigationScope() and
45
- // threw the same shaped error, differing only in which hook name the message names. Centralized
46
- // here so a wording change lands once instead of five times.
47
- export function requireNavigationScope(hookName) {
48
- const scope = injectNavigationScope();
49
- if (scope === undefined) {
50
- throw new Error(`${hookName} must be used within a screen rendered by <Stack>, <Tab>, or <Drawer>`);
51
- }
52
- return scope;
53
- }
54
- // The setup-boundary component described above. `props: ['value']` is a REQUIRED runtime
55
- // declaration here, not stylistic - without it Vue would route the incoming `value` into `attrs`
56
- // instead of a tracked reactive `props` field, and the render closure's refresh below would
57
- // silently keep re-reading a stale initial snapshot.
58
- export const NavigationScope = defineComponent((props, { slots }) => {
59
- const current = shallowRef(props.value);
60
- provideNavigationScope(current);
61
- return () => {
62
- current.value = props.value;
63
- return slots.default?.() ?? null;
64
- };
65
- }, { name: 'NavigationScope', props: ['value'] });
@@ -1,21 +0,0 @@
1
- import type { Component, Ref } from '@vue/runtime-core';
2
- import type { IRoute } from '../core';
3
- import type { ISearchBarCommands, ISearchBarOptions, IScreenOptions, INavigatorHandle } from '../core';
4
- export type IVueSearchBarOptions = ISearchBarOptions & {
5
- ref?: Ref<ISearchBarCommands | null>;
6
- };
7
- export type IVueScreenOptions = Omit<IScreenOptions, 'headerSearchBarOptions'> & {
8
- headerSearchBarOptions?: IVueSearchBarOptions;
9
- };
10
- export type IScreenOptionsArgs = {
11
- route: IRoute<unknown>;
12
- navigation: INavigatorHandle;
13
- };
14
- export type IScreenOptionsResolver = (args: IScreenOptionsArgs) => IVueScreenOptions;
15
- export type IScreenProps = {
16
- name: string;
17
- component: Component;
18
- options?: IVueScreenOptions | IScreenOptionsResolver;
19
- initialParams?: unknown;
20
- };
21
- export declare const Screen: import("vue").DefineSetupFnComponent<IScreenProps, {}, {}, IScreenProps & {}, import("vue").PublicProps>;
@@ -1,12 +0,0 @@
1
- // Stack.Screen: a declarative marker, never rendered on its own - Stack reads its props via a
2
- // default-slot scan (see stack.ts's collectRegistry) to build the static name -> {component,
3
- // options} registry, then mounts the registered component itself for each pushed route. The Vue
4
- // twin of react/screen.ts's Screen, using the slot-scanning pattern real Vue component libraries
5
- // use for this exact declarative-marker-child shape (Element Plus's `<el-collapse-item>`, Ant
6
- // Design Vue's `<a-menu-item>`): the parent inspects `vnode.type === Screen` and reads
7
- // `vnode.props` directly, so Screen's own setup() never actually runs.
8
- import { defineComponent } from '@vue/runtime-core';
9
- export const Screen = defineComponent(() => () => null, {
10
- name: 'Screen',
11
- inheritAttrs: false,
12
- });
@@ -1,9 +0,0 @@
1
- import type { IScreenProps, IVueScreenOptions } from '../screen';
2
- export type { INavigatorHandle } from '../../core';
3
- export type IStackProps = {
4
- initialRouteName?: string;
5
- screenOptions?: IVueScreenOptions;
6
- };
7
- export declare const Stack: import("vue").DefineSetupFnComponent<IStackProps, {}, {}, IStackProps & {}, import("vue").PublicProps> & {
8
- Screen: import("vue").DefineSetupFnComponent<IScreenProps, {}, {}, IScreenProps & {}, import("vue").PublicProps>;
9
- };
@@ -1,279 +0,0 @@
1
- // Stack, the Vue lifecycle half. The route-stack transitions (navigator-state) and the
2
- // options/props folds (screen-options, render-stack) live in @symbiote-native/navigation core,
3
- // shared verbatim with the React/Angular adapters; here Vue supplies the lifecycle - a plain ref
4
- // for the pushed-route stack (Vue's twin of useReducer: reassign `.value` from the same pure
5
- // reducer), useId + a closure counter for route-key generation, expose() for the push/pop/replace
6
- // navigator handle - plus the descriptor bridge for the header config leaf. Pushing/popping a
7
- // route is an ordinary child mount/unmount: RNSScreenStack diffs its RNSScreen children natively,
8
- // so no imperative native command is needed here at all. Neither this nor the Screen marker
9
- // imports react-native-screens' own React components (ScreenStack.tsx et al - hooks, crashes a
10
- // non-React adapter); the native views are driven directly through the ViewConfig ../register
11
- // registers. See CLAUDE.md <third_party_rn_packages_are_react_only>.
12
- import { defineComponent, h, shallowRef, useId } from '@vue/runtime-core';
13
- import { descriptorToVue, normalizeVueAttrs } from '@symbiote-native/vue';
14
- import { Platform, debugNodeId, dlog, isSymbioteNode } from '@symbiote-native/engine';
15
- import { NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS, NAVIGATION_EVENT_STATE, RNS_SCREEN_CONTENT_WRAPPER_VIEW_NAME, RNS_SCREEN_STACK_VIEW_NAME, RNS_SCREEN_VIEW_NAME, SCREEN_ON_APPEAR, SCREEN_ON_DISAPPEAR, SCREEN_ON_DISMISSED, SCREEN_ON_HEADER_BACK_BUTTON_CLICKED, SCREEN_ON_WILL_APPEAR, SCREEN_ON_WILL_DISAPPEAR, STACK_ON_FINISH_TRANSITIONING, buildSearchBarHandle, buildSearchBarPassthrough, computeActivityState, createInitialNavigatorState, createNavigationEmitter, isRecord, navigatorReducer, resolveScreenRenderPlan, resolveStackProps, } from '../../core/index.js';
16
- import { NavigationScope, injectNavigationScope } from '../navigation-context.js';
17
- import { Screen } from '../screen.js';
18
- // backTitleVisible defaults to `true` on both platforms per the codegen spec's own default
19
- // (CT.WithDefault<boolean, 'true'>) - no ios/android divergence in v1 scope, so a single constant
20
- // stands in for the per-platform injection point ISliderPlatform-style adapters use elsewhere.
21
- const NAVIGATOR_PLATFORM = { defaultHeaderBackTitleVisible: true };
22
- function asString(value) {
23
- return typeof value === 'string' ? value : undefined;
24
- }
25
- function asComponent(value) {
26
- if (typeof value === 'function')
27
- return value;
28
- return isRecord(value) ? value : undefined;
29
- }
30
- // screenOptions/options each carry ~20 independently-optional fields (IScreenOptions,
31
- // navigator-props.ts) forwarded wholesale into the core resolvers below - a per-field guard would
32
- // just hand-reimplement TypeScript's own structural check, so this narrows only the object-ness at
33
- // the exact I/O edge where Vue's untyped attrs enter the strongly-typed core surface, per
34
- // CLAUDE.md's ts-js rule ("the I/O edge where data enters the type system, in the narrowest form").
35
- function asScreenOptions(value) {
36
- return isRecord(value) ? value : undefined;
37
- }
38
- function asScreenOptionsOrResolver(value) {
39
- if (typeof value === 'function')
40
- return value;
41
- return asScreenOptions(value);
42
- }
43
- function collectRegistry(vnodes) {
44
- const registry = new Map();
45
- for (const vnode of vnodes) {
46
- if (vnode.type !== Screen || !isRecord(vnode.props))
47
- continue;
48
- const name = asString(vnode.props.name);
49
- const component = asComponent(vnode.props.component);
50
- if (name === undefined || component === undefined)
51
- continue;
52
- registry.set(name, {
53
- component,
54
- options: asScreenOptionsOrResolver(vnode.props.options),
55
- initialParams: vnode.props.initialParams,
56
- });
57
- }
58
- return registry;
59
- }
60
- function resolveScreenOptions(entry, screenOptionsArgs, screenOptions) {
61
- const own = typeof entry.options === 'function' ? entry.options(screenOptionsArgs) : entry.options;
62
- return { ...screenOptions, ...own };
63
- }
64
- const StackImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose }) => {
65
- const attrs = normalizeVueAttrs(rawAttrs);
66
- // Read BEFORE this Stack establishes its own per-screen NavigationScope below - becomes
67
- // the `parent` link a nested screen's useNavigation().getParent() walks (e.g. this Stack
68
- // rendered as a Tab screen's content reaches that Tab via this value). undefined when this
69
- // Stack is the nesting root. Kept as the injected ref itself (not unwrapped here) so the
70
- // render loop below always reads its CURRENT `.value`, matching React's useContext
71
- // re-reading on every render.
72
- const ambientScopeRef = injectNavigationScope();
73
- const routeIdPrefix = useId();
74
- let routeSequence = 0;
75
- // One emitter per route.key, keyed exactly like routeSequence's counter is scoped - created
76
- // lazily the first time a route is rendered, pruned once it's popped off the stack (below).
77
- const emitters = new Map();
78
- // Investigation instrumentation (flicker-on-focus bug): route keys whose resolved
79
- // transition/animation-timing props have already been logged, so the once-per-mount dump
80
- // below doesn't spam on every re-render. Kept behind DEBUG, never removed.
81
- const loggedScreenPropKeys = new Set();
82
- function emitterFor(routeKey) {
83
- let emitter = emitters.get(routeKey);
84
- if (emitter === undefined) {
85
- emitter = createNavigationEmitter();
86
- emitters.set(routeKey, emitter);
87
- }
88
- return emitter;
89
- }
90
- function createRoute(name, params) {
91
- routeSequence += 1;
92
- return { key: `${routeIdPrefix}-${name}-${routeSequence}`, name, params };
93
- }
94
- const initialRegistry = collectRegistry(slots.default?.() ?? []);
95
- const initialRouteName = asString(attrs.initialRouteName) ?? initialRegistry.keys().next().value;
96
- let initialState;
97
- if (initialRouteName === undefined) {
98
- dlog('Stack: no <Stack.Screen> children registered');
99
- initialState = createInitialNavigatorState({
100
- key: routeIdPrefix,
101
- name: '',
102
- params: undefined,
103
- });
104
- }
105
- else {
106
- initialState = createInitialNavigatorState(createRoute(initialRouteName, initialRegistry.get(initialRouteName)?.initialParams));
107
- }
108
- const state = shallowRef(initialState);
109
- function dispatch(action) {
110
- state.value = navigatorReducer(state.value, action);
111
- }
112
- const handle = {
113
- push: (name, params) => dispatch({ type: 'push', route: createRoute(name, params) }),
114
- pop: count => dispatch({ type: 'pop', count }),
115
- popToTop: () => dispatch({ type: 'popToTop' }),
116
- popTo: key => dispatch({ type: 'popTo', key }),
117
- replace: (name, params) => dispatch({ type: 'replace', route: createRoute(name, params) }),
118
- setParams: (params, key) => dispatch({ type: 'setParams', key, params }),
119
- reset: nextState => dispatch({ type: 'reset', state: nextState }),
120
- canGoBack: () => state.value.routes.length > 1,
121
- };
122
- expose(handle);
123
- // Broadcasts the router state to every still-live route's emitter (useNavigationState's
124
- // source) after each commit, and prunes emitters for routes popped off the stack - mirroring
125
- // React's own subscribe-in-effect pattern. `watch` isn't needed here (unlike React's
126
- // useEffect, which must be deferred past render to avoid a descendant setState-during-render):
127
- // Vue's provide/inject + shallowRef.value writes are safe to perform directly inside the
128
- // render closure below, so the broadcast happens right there, once per render.
129
- function broadcastState() {
130
- for (const route of state.value.routes) {
131
- emitterFor(route.key).emit(NAVIGATION_EVENT_STATE, state.value);
132
- }
133
- for (const routeKey of emitters.keys()) {
134
- if (!state.value.routes.some(route => route.key === routeKey))
135
- emitters.delete(routeKey);
136
- }
137
- }
138
- return () => {
139
- broadcastState();
140
- const registry = collectRegistry(slots.default?.() ?? []);
141
- const screenOptions = asScreenOptions(attrs.screenOptions);
142
- // Investigation instrumentation (flicker-on-focus bug): STACK_ON_FINISH_TRANSITIONING is
143
- // the native signal that the WHOLE push/pop animation has finished (as opposed to
144
- // onAppear/onDisappear, which are per-screen) - logging it lets the per-screen
145
- // appear/disappear timestamps be checked against the actual transition-complete moment.
146
- // Kept behind DEBUG, never removed.
147
- const stackProps = resolveStackProps({
148
- passthrough: {
149
- [STACK_ON_FINISH_TRANSITIONING]: () => dlog(`Stack: onFinishTransitioning at t=${Date.now()}`),
150
- },
151
- });
152
- const children = state.value.routes.map((route, index) => {
153
- const entry = registry.get(route.name);
154
- if (entry === undefined) {
155
- dlog(`Stack: no screen registered for route name "${route.name}"`);
156
- return null;
157
- }
158
- const screenOptionsArgs = { route, navigation: handle };
159
- const mergedOptions = resolveScreenOptions(entry, screenOptionsArgs, screenOptions);
160
- const activityState = computeActivityState(index, state.value.routes.length);
161
- // Investigation instrumentation (flicker-on-focus bug): fires on EVERY Stack render, not
162
- // just on transitions, so the log stream shows whether a route's activityState/index ever
163
- // changes outside of a push/pop dispatch. Kept behind DEBUG, never removed.
164
- dlog(`Stack: render route "${route.name}" index=${index}/${state.value.routes.length - 1} ` +
165
- `activityState=${activityState} at t=${Date.now()}`);
166
- const routeEmitter = emitterFor(route.key);
167
- const searchBarOptions = mergedOptions.headerSearchBarOptions;
168
- const plan = resolveScreenRenderPlan({
169
- screenId: route.key,
170
- index,
171
- routeCount: state.value.routes.length,
172
- options: mergedOptions,
173
- platform: NAVIGATOR_PLATFORM,
174
- isAndroid: Platform.OS === 'android',
175
- screenPassthrough: {
176
- [SCREEN_ON_DISMISSED]: () => dispatch({ type: 'pop', count: 1 }),
177
- [SCREEN_ON_HEADER_BACK_BUTTON_CLICKED]: () => dispatch({ type: 'pop', count: 1 }),
178
- // onAppear/onDisappear are the definitive visibility boundary (post-transition-
179
- // animation), so 'focus'/'blur' fire exactly once per transition; onWillAppear/
180
- // onWillDisappear fire BEFORE the animation runs, so wiring them to emit() too would
181
- // double-invoke useFocusEffect per transition - they only get a debug log here.
182
- [SCREEN_ON_WILL_APPEAR]: () => dlog(`Stack: route "${route.name}" will appear at t=${Date.now()}`),
183
- [SCREEN_ON_APPEAR]: () => {
184
- dlog(`Stack: route "${route.name}" appeared (focus) at t=${Date.now()}`);
185
- routeEmitter.emit(NAVIGATION_EVENT_FOCUS);
186
- },
187
- [SCREEN_ON_WILL_DISAPPEAR]: () => dlog(`Stack: route "${route.name}" will disappear at t=${Date.now()}`),
188
- [SCREEN_ON_DISAPPEAR]: () => {
189
- dlog(`Stack: route "${route.name}" disappeared (blur) at t=${Date.now()}`);
190
- routeEmitter.emit(NAVIGATION_EVENT_BLUR);
191
- },
192
- },
193
- searchBarPassthrough: searchBarOptions
194
- ? {
195
- ...buildSearchBarPassthrough(searchBarOptions, message => dlog(`Stack: route "${route.name}" ${message}`)),
196
- // The imperative ref (SearchBarCommands): a function ref attached straight to the
197
- // RNSSearchBar host element via `passthrough.ref` - descriptorToVue's h() call
198
- // hands the raw engine node to a plain function ref same as Switch/TextInput's
199
- // setNodeRef. Built fresh per mount/unmount, never captured eagerly
200
- // (buildSearchBarHandle's own lazy-getter contract is satisfied trivially here
201
- // since `node` is already resolved).
202
- ref: (el) => {
203
- const node = isSymbioteNode(el) ? el : null;
204
- // Investigation instrumentation (search-bar-ref "node not committed" bug):
205
- // compare this debugNodeId against the mirror.set/dispatchViewCommand logs in
206
- // commit.ts - same id on both sides proves the ref really does hold the
207
- // committed node; a mismatch proves a stale closure instead. Kept behind DEBUG,
208
- // never removed.
209
- dlog(`Stack: search bar ref callback, node=${node === null ? 'null' : debugNodeId(node)} at t=${Date.now()}`);
210
- const appRef = searchBarOptions.ref;
211
- if (appRef === undefined)
212
- return;
213
- appRef.value = node === null ? null : buildSearchBarHandle(() => node);
214
- },
215
- }
216
- : undefined,
217
- });
218
- // Investigation instrumentation (flicker-on-focus bug): the actual timing/z-order-relevant
219
- // values resolved onto the native RNSScreen, once per route.key (not every render) -
220
- // rules a stackAnimation/transitionDuration mismatch against react-native-screens' own
221
- // native default in or out. Kept behind DEBUG, never removed.
222
- if (!loggedScreenPropKeys.has(route.key)) {
223
- loggedScreenPropKeys.add(route.key);
224
- dlog(`Stack: route "${route.name}" resolved screen props ` +
225
- `stackAnimation=${String(plan.screenProps.stackAnimation)} ` +
226
- `stackPresentation=${String(plan.screenProps.stackPresentation)} ` +
227
- `transitionDuration=${String(plan.screenProps.transitionDuration)} ` +
228
- `gestureEnabled=${String(plan.screenProps.gestureEnabled)} at t=${Date.now()}`);
229
- }
230
- // Must not be flattened away (collapsable: false) - react-native-screens' native side
231
- // finds THIS specific view type by class check to register a formSheet's content for
232
- // sizing (see RNS_SCREEN_CONTENT_WRAPPER_VIEW_NAME's comment in core/constants.ts). A
233
- // `push` screen doesn't need it, but a `formSheet` one is otherwise left with no content
234
- // ever attached natively.
235
- const content = h(RNS_SCREEN_CONTENT_WRAPPER_VIEW_NAME, plan.contentWrapperProps, [
236
- h(NavigationScope, {
237
- value: {
238
- route,
239
- navigation: handle,
240
- emitter: routeEmitter,
241
- parent: ambientScopeRef?.value,
242
- },
243
- },
244
- // No route/navigation props: the screen reads both through composables off the
245
- // NavigationScope provided just above (useRoute / useStackNavigation).
246
- () => h(entry.component)),
247
- ]);
248
- // react-native-screens' own Screen.tsx swaps in a DIFFERENT Fabric component
249
- // ('RNSModalScreen') for a modally-presented screen - its native updateLayoutMetrics:
250
- // relies on this exact class to know it must NOT apply Yoga's computed frame (see
251
- // resolveScreenViewName's comment in core/render-stack.ts). The nested Screen
252
- // isHeaderInModal adds below is always plain 'RNSScreen': react-native-screens' own inner
253
- // Screen never carries a presentation prop either, since it exists purely to host the
254
- // header, not to be modally presented itself.
255
- // A modal/formSheet screen has no UINavigationController of its own on iOS - nest an
256
- // inner RNSScreenStack/RNSScreen purely to host the native header bar (see
257
- // isHeaderInModal's comment in core/render-stack.ts). Skipping this leaves
258
- // RNSScreenStackHeaderConfig with no navigation controller to attach to, so the header
259
- // silently never renders.
260
- return plan.inModal
261
- ? h(plan.screenViewName, { key: route.key, ...plan.screenProps }, [
262
- h(RNS_SCREEN_STACK_VIEW_NAME, { style: plan.innerStackStyle }, [
263
- h(RNS_SCREEN_VIEW_NAME,
264
- // activityState mirrors the outer Screen's own value - react-native-screens'
265
- // RNSScreen.mm treats an unset/inactive nested screen as not yet pushed,
266
- // leaving it parked at its pre-push transition position (off past the bottom
267
- // edge) instead of its real, presented frame.
268
- { style: plan.innerScreenStyle, activityState: plan.activityState }, [descriptorToVue(plan.headerConfig), content]),
269
- ]),
270
- ])
271
- : h(plan.screenViewName, { key: route.key, ...plan.screenProps }, [
272
- descriptorToVue(plan.headerConfig),
273
- content,
274
- ]);
275
- });
276
- return h(RNS_SCREEN_STACK_VIEW_NAME, stackProps, children);
277
- };
278
- }, { name: 'Stack', inheritAttrs: false });
279
- export const Stack = Object.assign(StackImpl, { Screen });
@@ -1,14 +0,0 @@
1
- import type { Component } from '@vue/runtime-core';
2
- import type { IRoute, ITabOptions, ITabNavigatorHandle } from '../core';
3
- export type ITabScreenOptionsArgs = {
4
- route: IRoute<unknown>;
5
- navigation: ITabNavigatorHandle;
6
- };
7
- export type ITabScreenOptionsResolver = (props: ITabScreenOptionsArgs) => ITabOptions;
8
- export type ITabScreenProps = {
9
- name: string;
10
- component: Component;
11
- options?: ITabOptions | ITabScreenOptionsResolver;
12
- initialParams?: unknown;
13
- };
14
- export declare const TabScreen: import("vue").DefineSetupFnComponent<ITabScreenProps, {}, {}, ITabScreenProps & {}, import("vue").PublicProps>;
@@ -1,11 +0,0 @@
1
- // Tab.Screen: a declarative marker, never rendered on its own - Tab reads its props via a
2
- // default-slot scan (see tabs.ts's collectRegistry) to build the static name -> {component,
3
- // options} registry, then mounts the FOCUSED route's component itself. The Vue twin of
4
- // react/tab-screen.ts's TabScreen, mirroring screen.ts's slot-scanning marker pattern minus the
5
- // stack-only concepts (no push/pop lifecycle events to wire); see CLAUDE.md
6
- // <third_party_rn_packages_are_react_only> - this and Tab import nothing from react-native.
7
- import { defineComponent } from '@vue/runtime-core';
8
- export const TabScreen = defineComponent(() => () => null, {
9
- name: 'TabScreen',
10
- inheritAttrs: false,
11
- });
@@ -1,10 +0,0 @@
1
- import type { ITabOptions } from '../../core';
2
- import type { ITabScreenProps } from '../tab-screen';
3
- export type { ITabNavigatorHandle } from '../../core';
4
- export type ITabProps = {
5
- initialRouteName?: string;
6
- screenOptions?: ITabOptions;
7
- };
8
- export declare const Tab: import("vue").DefineSetupFnComponent<ITabProps, {}, {}, ITabProps & {}, import("vue").PublicProps> & {
9
- Screen: import("vue").DefineSetupFnComponent<ITabScreenProps, {}, {}, ITabScreenProps & {}, import("vue").PublicProps>;
10
- };
@@ -1,209 +0,0 @@
1
- // Tab, the Vue 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/Angular adapters; here Vue supplies the lifecycle - a plain ref for the focused index
4
- // (Vue's twin of useReducer), useId for route-key generation, expose() for the jumpTo/setParams
5
- // handle - plus the descriptor bridge for the tab-bar leaf, exactly like Stack bridges its header
6
- // config (stack.ts). Unlike Stack, a bottom-tabs bar is a PURE-JS UI: it paints ordinary
7
- // `symbiote-view`/`symbiote-text` primitives via the shared render fn, so there is no
8
- // react-native-screens ViewConfig to register here - Tab needs no `../register` import.
9
- import { defineComponent, h, nextTick, onMounted, onUnmounted, shallowRef, useId, watch, } from '@vue/runtime-core';
10
- import { descriptorToVue, normalizeVueAttrs } from '@symbiote-native/vue';
11
- import { dlog } from '@symbiote-native/engine';
12
- import { NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS, createInitialTabState, createNavigationEmitter, diffFocusedRoute, isFocusedRoute, isRecord, renderTabBar, tabRouterReducer, } from '../../core/index.js';
13
- import { NavigationScope, injectNavigationScope } from '../navigation-context.js';
14
- import { TabScreen } from '../tab-screen.js';
15
- function asString(value) {
16
- return typeof value === 'string' ? value : undefined;
17
- }
18
- function asComponent(value) {
19
- if (typeof value === 'function')
20
- return value;
21
- return isRecord(value) ? value : undefined;
22
- }
23
- // ITabOptions carries a handful of independently-optional fields forwarded wholesale into the
24
- // shared render fn below - same "narrow the object-ness, cast at the exact I/O edge" rationale
25
- // stack.ts's asScreenOptions documents (CLAUDE.md's ts-js rule).
26
- function asTabOptions(value) {
27
- return isRecord(value) ? value : undefined;
28
- }
29
- function asTabOptionsOrResolver(value) {
30
- if (typeof value === 'function')
31
- return value;
32
- return asTabOptions(value);
33
- }
34
- function collectRegistry(vnodes) {
35
- const registry = new Map();
36
- for (const vnode of vnodes) {
37
- if (vnode.type !== TabScreen || !isRecord(vnode.props))
38
- continue;
39
- const name = asString(vnode.props.name);
40
- const component = asComponent(vnode.props.component);
41
- if (name === undefined || component === undefined)
42
- continue;
43
- registry.set(name, {
44
- component,
45
- options: asTabOptionsOrResolver(vnode.props.options),
46
- initialParams: vnode.props.initialParams,
47
- });
48
- }
49
- return registry;
50
- }
51
- function resolveTabOptions(entry, screenComponentProps, screenOptions) {
52
- const own = typeof entry.options === 'function' ? entry.options(screenComponentProps) : entry.options;
53
- return { ...screenOptions, ...own };
54
- }
55
- const TAB_CONTENT_STYLE = { flex: 1 };
56
- const TAB_ROOT_STYLE = { flex: 1 };
57
- const TabImpl = defineComponent((_props, { attrs: rawAttrs, slots, expose }) => {
58
- const attrs = normalizeVueAttrs(rawAttrs);
59
- // Read BEFORE this Tab establishes its own per-screen NavigationScope below - becomes the
60
- // `parent` link a nested screen's useNavigation().getParent() walks (e.g. this Tab rendered
61
- // as a Stack screen's content reaches that Stack via this value). undefined when this Tab is
62
- // the nesting root.
63
- const ambientScopeRef = injectNavigationScope();
64
- const routeIdPrefix = useId();
65
- function buildRoutes(registry) {
66
- return Array.from(registry.entries()).map(([name, entry]) => ({
67
- key: `${routeIdPrefix}-${name}`,
68
- name,
69
- params: entry.initialParams,
70
- }));
71
- }
72
- const initialRegistry = collectRegistry(slots.default?.() ?? []);
73
- const initialRoutes = buildRoutes(initialRegistry);
74
- if (initialRoutes.length === 0)
75
- dlog('Tab: no <Tab.Screen> children registered');
76
- const state = shallowRef(createInitialTabState(initialRoutes, asString(attrs.initialRouteName)));
77
- function dispatch(action) {
78
- state.value = tabRouterReducer(state.value, action);
79
- }
80
- const jumpTo = (name, params) => dispatch({ type: 'jumpTo', name, params });
81
- const setParams = (params, key) => dispatch({ type: 'setParams', key, params });
82
- const handle = { jumpTo, setParams };
83
- expose(handle);
84
- // Tab paints its own bar in pure JS - there is no native onAppear/onDisappear to hook (unlike
85
- // Stack's RNSScreen), so focus/blur is synthesized here: emit 'focus' once the newly-focused
86
- // route's content has mounted, 'blur' when it's about to be replaced or Tab itself unmounts.
87
- // Keyed on the route KEY (not the route object) so a setParams-only change (new route object,
88
- // same key) doesn't spuriously re-fire focus/blur - mirrors tabs.ts's React twin exactly,
89
- // just expressed as a `watch` over the focused key instead of a useEffect dependency array.
90
- function focusedKeyOf(current) {
91
- return current.routes[current.index]?.key;
92
- }
93
- // One emitter per route.key, created lazily and cached for the navigator's whole lifetime -
94
- // mirrors stack.ts's own `emitters` map. This decouples emitter IDENTITY (stable, looked up
95
- // by key, read by the render closure below when it builds a route's NavigationScope) from
96
- // emit TIMING (must wait until the focused screen has actually mounted and subscribed): a
97
- // scheme that instead tried to recreate/swap `routeEmitter` synchronously on each focus
98
- // change raced Vue's own render/mount cycle no matter which watch `flush` timing it used -
99
- // 'pre' emits before the new screen's onMounted subscribes, 'post' emits AFTER the render
100
- // closure already needed the (still up-to-date) emitter to build that screen's NavigationScope
101
- // in the FIRST place. A stable per-key lookup sidesteps the race entirely.
102
- const emitters = new Map();
103
- function emitterFor(routeKey) {
104
- let emitter = emitters.get(routeKey);
105
- if (emitter === undefined) {
106
- emitter = createNavigationEmitter();
107
- emitters.set(routeKey, emitter);
108
- }
109
- return emitter;
110
- }
111
- // Tab paints its own bar in pure JS - there is no native onAppear/onDisappear to hook (unlike
112
- // Stack's RNSScreen), so focus/blur is synthesized here: emit 'focus' once the newly-focused
113
- // route's content has mounted, 'blur' when it's about to be replaced or Tab itself unmounts.
114
- // Keyed on the route KEY (not the route object) so a setParams-only change (new route object,
115
- // same key) doesn't spuriously re-fire focus/blur - mirrors tabs.ts's React twin exactly,
116
- // just expressed as a `watch` over the focused key instead of a useEffect dependency array.
117
- let focusedRouteKey = focusedKeyOf(state.value);
118
- onMounted(() => {
119
- if (focusedRouteKey !== undefined) {
120
- dlog(`Tab: route "${focusedRouteKey}" focused at t=${Date.now()}`);
121
- emitterFor(focusedRouteKey).emit(NAVIGATION_EVENT_FOCUS);
122
- }
123
- });
124
- // The bookkeeping (which key is focused) updates immediately so the render closure below
125
- // always reads the right route; the actual emit is deferred to nextTick(), which resolves
126
- // only after the CURRENT flush cycle fully drains - including the newly-focused screen's own
127
- // onMounted (subscribing its useIsFocused/useFocusEffect listeners). flush:'post' alone is
128
- // NOT enough here: it only guarantees this callback runs after THIS component's own render
129
- // effect, not after an arbitrary sibling post-flush job (a just-mounted child's onMounted)
130
- // that may be queued after it in the same batch - nextTick is the one API that waits for the
131
- // WHOLE cycle, not just this watcher's own slot in it.
132
- watch(() => focusedKeyOf(state.value), nextKey => {
133
- const { blurKey, focusKey } = diffFocusedRoute(focusedRouteKey, nextKey);
134
- if (blurKey === undefined && focusKey === undefined)
135
- return;
136
- focusedRouteKey = nextKey;
137
- nextTick(() => {
138
- if (blurKey !== undefined) {
139
- dlog(`Tab: route "${blurKey}" blurred at t=${Date.now()}`);
140
- emitterFor(blurKey).emit(NAVIGATION_EVENT_BLUR);
141
- }
142
- if (focusKey !== undefined) {
143
- dlog(`Tab: route "${focusKey}" focused at t=${Date.now()}`);
144
- emitterFor(focusKey).emit(NAVIGATION_EVENT_FOCUS);
145
- }
146
- });
147
- });
148
- onUnmounted(() => {
149
- if (focusedRouteKey !== undefined)
150
- emitterFor(focusedRouteKey).emit(NAVIGATION_EVENT_BLUR);
151
- });
152
- return () => {
153
- const registry = collectRegistry(slots.default?.() ?? []);
154
- const screenOptions = asTabOptions(attrs.screenOptions);
155
- if (registry.size === 0)
156
- dlog('Tab: no <Tab.Screen> children registered');
157
- const focusedRoute = state.value.routes[state.value.index];
158
- const items = state.value.routes.map((route, index) => {
159
- const entry = registry.get(route.name);
160
- const focused = isFocusedRoute(index, state.value.index);
161
- if (entry === undefined) {
162
- dlog(`Tab: no screen registered for route name "${route.name}"`);
163
- return { key: route.key, focused, label: route.name, passthrough: {} };
164
- }
165
- const options = resolveTabOptions(entry, { route, navigation: handle }, screenOptions);
166
- return {
167
- key: route.key,
168
- focused,
169
- label: options.tabBarLabel ?? options.title ?? route.name,
170
- icon: options.tabBarIcon,
171
- badge: options.tabBarBadge,
172
- activeTintColor: options.tabBarActiveTintColor,
173
- inactiveTintColor: options.tabBarInactiveTintColor,
174
- passthrough: {
175
- onPress: () => jumpTo(route.name),
176
- accessibilityRole: 'tab',
177
- accessibilityState: { selected: focused },
178
- },
179
- };
180
- });
181
- const focusedEntry = focusedRoute ? registry.get(focusedRoute.name) : undefined;
182
- const focusedOptions = focusedEntry && focusedRoute
183
- ? resolveTabOptions(focusedEntry, { route: focusedRoute, navigation: handle }, screenOptions)
184
- : screenOptions;
185
- const tabBar = descriptorToVue(renderTabBar({ items, style: focusedOptions?.tabBarStyle, passthrough: {} }));
186
- // Only the focused route's screen is ever mounted (unlike Stack, which keeps every pushed
187
- // route alive), so a fresh NavigationScope per focus change is sufficient - the previous
188
- // screen's whole subtree (and any listeners it registered) is torn down by an ordinary Vue
189
- // unmount when focus moves on.
190
- const content = focusedEntry && focusedRoute
191
- ? h(NavigationScope, {
192
- value: {
193
- route: focusedRoute,
194
- navigation: handle,
195
- emitter: emitterFor(focusedRoute.key),
196
- parent: ambientScopeRef?.value,
197
- },
198
- },
199
- // No route/navigation props: the screen reads both through composables off the
200
- // NavigationScope provided just above (useRoute / useTabNavigation).
201
- () => h(focusedEntry.component))
202
- : null;
203
- return h('symbiote-view', { style: TAB_ROOT_STYLE }, [
204
- h('symbiote-view', { style: TAB_CONTENT_STYLE }, content === null ? [] : [content]),
205
- tabBar,
206
- ]);
207
- };
208
- }, { name: 'Tab', inheritAttrs: false });
209
- export const Tab = Object.assign(TabImpl, { Screen: TabScreen });