@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,88 +0,0 @@
1
- // Drawer: the render half (framework-agnostic). Like render-stack.ts's screen content, both the
2
- // drawer panel's content (renderDrawerContent) and the wrapped screen content are arbitrary
3
- // framework subtrees the adapter owns - so, mirroring render-modal.ts's container-slot precedent,
4
- // renderDrawer hands back one root Descriptor whose structural children are EMPTY placeholder
5
- // nodes (`content` / `overlay?` / `panel`) that the adapter fills with real framework children.
6
- // Paint order (and therefore which slot is present) is drawerType-dependent, so `drawerChildOrder`
7
- // is exported alongside renderDrawer as the single source of truth the adapter zips its own
8
- // children against - no adapter re-derives the ordering itself.
9
- //
10
- // All positioning here is STATIC (position/top/bottom/side/width) - the animated slide transform
11
- // and overlay opacity are NOT baked into this Descriptor. They are computed once per drawerType
12
- // via drawer-options.ts's resolveDrawerGeometry() and then driven per-frame by the adapter's own
13
- // Animated value (Animated.View), because "how to animate a style" is a framework-lifecycle
14
- // concern (React: Animated.View; a future Vue adapter: a reactive style binding) - see
15
- // CLAUDE.md <adapters_stay_thin>.
16
- import { el } from '@symbiote-native/components';
17
- import { isDrawerOverlayVisible, resolveDrawerPosition, resolveDrawerType, resolveDrawerWidth, } from './drawer-options/index.js';
18
- // front: content, overlay (dims it), panel (paints on top, last-sibling-on-top).
19
- // back: panel (stationary, behind), content (slides away to reveal it) - no overlay. Both are
20
- // absolutely positioned, so sibling order here only decides z-stacking, not screen side.
21
- // slide: panel, overlay, content (both panel and content animate; content stays visually on top).
22
- // permanent: NOT absolutely positioned (an ordinary flexDirection:'row' sidebar), so sibling
23
- // order IS the left-to-right screen position - it must follow drawerPosition.
24
- export function drawerChildOrder(options) {
25
- const type = resolveDrawerType(options);
26
- switch (type) {
27
- case 'back':
28
- return ['panel', 'content'];
29
- case 'slide':
30
- return ['panel', 'overlay', 'content'];
31
- case 'permanent':
32
- return resolveDrawerPosition(options) === 'left'
33
- ? ['panel', 'content']
34
- : ['content', 'panel'];
35
- case 'front':
36
- default:
37
- return ['content', 'overlay', 'panel'];
38
- }
39
- }
40
- const ROOT_STYLE = { flex: 1 };
41
- const CONTENT_STYLE = { flex: 1 };
42
- // Fabric paints later siblings on top; position:absolute + inset:0 is enough for the overlay
43
- // (its own sibling order, not zIndex, decides whether it sits above the content).
44
- const OVERLAY_BASE_STYLE = {
45
- position: 'absolute',
46
- top: 0,
47
- left: 0,
48
- right: 0,
49
- bottom: 0,
50
- };
51
- const DRAWER_PANEL_DEFAULT_BACKGROUND_COLOR = 'white';
52
- function panelBaseStyle(options) {
53
- const type = resolveDrawerType(options);
54
- const width = resolveDrawerWidth(options);
55
- const side = resolveDrawerPosition(options);
56
- if (type === 'permanent') {
57
- // A sidebar: an ordinary flex child, no absolute positioning, no translate needed.
58
- return { width, backgroundColor: DRAWER_PANEL_DEFAULT_BACKGROUND_COLOR };
59
- }
60
- return {
61
- position: 'absolute',
62
- top: 0,
63
- bottom: 0,
64
- width,
65
- backgroundColor: DRAWER_PANEL_DEFAULT_BACKGROUND_COLOR,
66
- [side]: 0,
67
- };
68
- }
69
- export function renderDrawer(view, options) {
70
- const type = resolveDrawerType(options);
71
- const content = el('symbiote-view', { ...view.contentPassthrough, style: CONTENT_STYLE }, []);
72
- const panel = el('symbiote-view', { ...view.panelPassthrough, style: [panelBaseStyle(options), view.drawerStyle] }, []);
73
- const slots = { content, panel };
74
- if (isDrawerOverlayVisible(options)) {
75
- slots.overlay = el('symbiote-view', {
76
- ...view.overlayPassthrough,
77
- style: [OVERLAY_BASE_STYLE, { backgroundColor: view.overlayColor }],
78
- }, []);
79
- }
80
- const children = drawerChildOrder(options).reduce((acc, slot) => {
81
- const descriptor = slots[slot];
82
- if (descriptor !== undefined)
83
- acc.push(descriptor);
84
- return acc;
85
- }, []);
86
- const rootStyle = type === 'permanent' ? { ...ROOT_STYLE, flexDirection: 'row' } : ROOT_STYLE;
87
- return el('symbiote-view', { style: rootStyle }, children);
88
- }
@@ -1,35 +0,0 @@
1
- import type { IDescriptor } from '@symbiote-native/components';
2
- import type { ISymbioteEvent } from '@symbiote-native/engine';
3
- import type { IHeaderConfigViewProps, INavigatorPlatform, IScreenOptions, IScreenViewProps, ISearchBarOptions, ISearchBarViewProps, IStackPresentation, IStackViewProps } from './navigator-props';
4
- export declare function resolveScreenViewName(stackPresentation: IStackPresentation | undefined, isAndroid: boolean): string;
5
- export declare function resolveScreenContentWrapperStyle(stackPresentation: IStackPresentation | undefined, headerHidden: boolean, headerTranslucent: boolean | undefined, isAndroid: boolean): Record<string, unknown>;
6
- export declare function isHeaderInModal(stackPresentation: IStackPresentation | undefined, headerHidden: boolean, isAndroid: boolean): boolean;
7
- export declare function resolveHeaderInModalStackStyle(): Record<string, unknown>;
8
- export declare function resolveHeaderInModalScreenStyle(): Record<string, unknown>;
9
- export declare function resolveScreenProps(view: IScreenViewProps): Record<string, unknown>;
10
- export declare function resolveStackProps(view: IStackViewProps): Record<string, unknown>;
11
- export declare function renderHeaderConfig(view: IHeaderConfigViewProps, searchBarProps?: Record<string, unknown>): IDescriptor;
12
- export declare function resolveSearchBarProps(view: ISearchBarViewProps): Record<string, unknown>;
13
- export declare function buildSearchBarPassthrough(options: ISearchBarOptions, log?: (message: string) => void): Record<string, (event: ISymbioteEvent) => void>;
14
- export type IScreenRenderPlanInput = {
15
- screenId: string;
16
- index: number;
17
- routeCount: number;
18
- options: IScreenOptions | undefined;
19
- platform: INavigatorPlatform;
20
- isAndroid: boolean;
21
- screenPassthrough: Record<string, unknown>;
22
- searchBarPassthrough?: Record<string, unknown>;
23
- };
24
- export type IScreenRenderPlan = {
25
- activityState: number;
26
- screenViewName: string;
27
- screenProps: Record<string, unknown>;
28
- headerConfig: IDescriptor;
29
- searchBarProps: Record<string, unknown> | undefined;
30
- contentWrapperProps: Record<string, unknown>;
31
- inModal: boolean;
32
- innerStackStyle: Record<string, unknown>;
33
- innerScreenStyle: Record<string, unknown>;
34
- };
35
- export declare function resolveScreenRenderPlan(input: IScreenRenderPlanInput): IScreenRenderPlan;
@@ -1,294 +0,0 @@
1
- // Navigator: framework-agnostic render half. A screen's content is an arbitrary framework
2
- // subtree the adapter owns (unlike the slider's one native leaf), so these resolvers hand back
3
- // plain props objects for the RNSScreen/RNSScreenStack leaves the adapter builds itself with real
4
- // children, and one full Descriptor for the header config. The header config Descriptor never
5
- // takes framework children (headerLeftBarButtonItems/headerRightBarButtonItems are native config
6
- // arrays from screen-options.ts, not elements), but can nest one RNSSearchBar leaf wrapped in an
7
- // RNSScreenStackHeaderSubview(type: 'searchBar'), matching how react-native-screens' own
8
- // ScreenStackHeaderSearchBarView mounts it. RNSScreenStackHeaderConfig.mm's mountChildComponentView
9
- // rejects a bare RNSSearchBar child natively ("only accepts children of type
10
- // ScreenStackHeaderSubview") and reads the search bar back out via `subview.subviews[0]`.
11
- import { el } from '@symbiote-native/components';
12
- import { HEADER_ON_PRESS_BAR_BUTTON_ITEM, HEADER_ON_PRESS_BAR_BUTTON_MENU_ITEM, RNS_MODAL_SCREEN_VIEW_NAME, RNS_SCREEN_STACK_HEADER_CONFIG_VIEW_NAME, RNS_SCREEN_STACK_HEADER_SUBVIEW_VIEW_NAME, RNS_SCREEN_VIEW_NAME, RNS_SEARCH_BAR_VIEW_NAME, SEARCH_BAR_ON_BLUR, SEARCH_BAR_ON_CANCEL_BUTTON_PRESS, SEARCH_BAR_ON_CHANGE_TEXT, SEARCH_BAR_ON_CLOSE, SEARCH_BAR_ON_FOCUS, SEARCH_BAR_ON_OPEN, SEARCH_BAR_ON_SEARCH_BUTTON_PRESS, STACK_DEFAULT_ANIMATION, STACK_DEFAULT_PRESENTATION, } from './constants.js';
13
- import { computeActivityState } from './navigator-state/index.js';
14
- import { resolveHeaderConfigView, resolveScreenView, resolveSearchBarView } from './screen-options.js';
15
- // Mirrors react-native-screens' ScreenStackItem.tsx (getPositioningStyle): a formSheet's content
16
- // wrapper does not get `flex: 1`. RNSScreen.mm's updateBounds pushes the sheet's live native size
17
- // into Fabric's shadow state on every frame of a detent drag, and a `bottom: 0`-pinned wrapper
18
- // forces React to set a strict frame on every update, causing the flicker PR #1870 fixed by
19
- // switching to `absoluteWithNoBottom` (RNSScreen.mm: "we do not set bottom: 0 in JS for wrapper of
20
- // the screen content, causing React to not set strict frame every time the sheet size is updated").
21
- // Tradeoff: content shorter than the sheet's current detent leaves a gap below it.
22
- // RNSScreenContentWrapper.mm's coerceChildScrollViewComponentSizeToSize fixes that natively, but
23
- // only for a ScrollView child, which it resizes directly to the sheet's real frame bypassing
24
- // Yoga/flex, not something reachable from a style choice here.
25
- const SCREEN_CONTENT_WRAPPER_FLEX_STYLE = { flex: 1 };
26
- const SHEET_CONTENT_WRAPPER_STYLE = {
27
- position: 'absolute',
28
- top: 0,
29
- start: 0,
30
- end: 0,
31
- bottom: undefined,
32
- };
33
- // Android's native header (react-native-screens' CustomToolbar in a CoordinatorLayout alongside
34
- // an AppBarLayout) auto-offsets screen content below the header via a Material
35
- // `AppBarLayout.ScrollingViewBehavior` (ScreenStackFragment.kt's setToolbarTranslucent: `behavior
36
- // = if (translucent) null else ScrollingViewBehavior()`). A plain (non-translucent) header gets
37
- // this offset for free and needs no JS compensation. `headerTranslucent: true` removes that
38
- // behavior: react-native-screens' contract for translucent is "background goes away AND content
39
- // is no longer auto-pushed below it", meant for a screen painting under a see-through header. An
40
- // app that sets headerTranslucent just for a custom (still opaque) header look, while relying on
41
- // its own SafeAreaView for safe-area content, loses the free offset and gets clipped:
42
- // SafeAreaView's Android inset only reflects WindowInsetsCompat's systemBars() (status
43
- // bar/cutout), with no awareness of the sibling Toolbar, so content paints its first 56dp under
44
- // the header. Verified empirically (2026-07-10) via `adb shell uiautomator dump`: a
45
- // translucent-header screen's content measured starting at y=0 (see the
46
- // navigation-header-content-offset skill for the full incident). iOS's UINavigationController
47
- // already lays out below its real nav bar regardless of translucency, so this compensation is
48
- // Android-only and applies only when translucent (a non-translucent header already gets the
49
- // native offset; padding there would double it).
50
- const ANDROID_HEADER_TOOLBAR_HEIGHT = 56;
51
- // react-native-screens' own Screen.tsx picks a different Fabric component for a modally-presented
52
- // screen ('RNSModalScreen' instead of plain 'RNSScreen') because "there is a need for different
53
- // shadow nodes" (its own comment). RNSScreen.mm's updateLayoutMetrics only skips applying Yoga's
54
- // computed frame for `[self isKindOfClass:RNSModalScreen.class]` ("the available space is most
55
- // likely restricted & differs from what Yoga resolves during first layout - we want to rely on
56
- // native layout here"). Emitting plain 'RNSScreen' for a formSheet/modal means that check never
57
- // matches, so Yoga's frame (computed as an ordinary push-stack sibling) applies uncorrected,
58
- // shifting the modal down by one full screen height (see register.ts's incident note). Mirrors
59
- // Screen.tsx's `shouldUseModalScreenComponent` exactly, including the iOS-only scope (Android
60
- // sizes modals natively regardless of Fabric view name).
61
- export function resolveScreenViewName(stackPresentation, isAndroid) {
62
- if (isAndroid)
63
- return RNS_SCREEN_VIEW_NAME;
64
- const isPlainPresentation = stackPresentation === undefined ||
65
- stackPresentation === 'push' ||
66
- stackPresentation === 'containedModal' ||
67
- stackPresentation === 'containedTransparentModal';
68
- return isPlainPresentation ? RNS_SCREEN_VIEW_NAME : RNS_MODAL_SCREEN_VIEW_NAME;
69
- }
70
- export function resolveScreenContentWrapperStyle(stackPresentation, headerHidden, headerTranslucent, isAndroid) {
71
- let baseStyles;
72
- if (stackPresentation !== 'formSheet') {
73
- baseStyles = SCREEN_CONTENT_WRAPPER_FLEX_STYLE;
74
- }
75
- else if (isAndroid) {
76
- baseStyles = {
77
- ...SHEET_CONTENT_WRAPPER_STYLE,
78
- bottom: 0,
79
- };
80
- }
81
- else {
82
- baseStyles = SHEET_CONTENT_WRAPPER_STYLE;
83
- }
84
- // paddingTop compensates the Android-only ScrollingViewBehavior removal above; iOS never needs
85
- // it, so the `!isAndroid` check must stay.
86
- if (!isAndroid || headerHidden || !headerTranslucent)
87
- return baseStyles;
88
- return { ...baseStyles, paddingTop: ANDROID_HEADER_TOOLBAR_HEIGHT };
89
- }
90
- // A modally-presented screen (anything but 'push') has no UINavigationController on iOS, so
91
- // RNSScreenStackHeaderConfig has nothing to attach a header bar to. react-native-screens' own
92
- // ScreenStackItem.tsx (isHeaderInModal) works around this by nesting header + content inside a
93
- // second, inner RNSScreenStack/RNSScreen pair purely to host the native nav bar. This is a UIKit
94
- // constraint, not a React-renderer one, so it applies identically here. Android has no such
95
- // requirement (its header draws without a nav-controller host).
96
- export function isHeaderInModal(stackPresentation, headerHidden, isAndroid) {
97
- if (isAndroid)
98
- return false;
99
- return (stackPresentation ?? STACK_DEFAULT_PRESENTATION) !== 'push' && !headerHidden;
100
- }
101
- // Inner-modal wrapper styles for isHeaderInModal: the inner RNSScreenStack fills its outer
102
- // RNSScreen, and the inner RNSScreen fills the stack absolutely on all four edges. This mirrors
103
- // react-native-screens' own `styles.container` / `StyleSheet.absoluteFill` for this exact pair.
104
- const HEADER_IN_MODAL_STACK_STYLE = { flex: 1 };
105
- const HEADER_IN_MODAL_SCREEN_STYLE = { position: 'absolute', top: 0, start: 0, end: 0, bottom: 0 };
106
- export function resolveHeaderInModalStackStyle() {
107
- return HEADER_IN_MODAL_STACK_STYLE;
108
- }
109
- export function resolveHeaderInModalScreenStyle() {
110
- return HEADER_IN_MODAL_SCREEN_STYLE;
111
- }
112
- // The RNSScreen leaf's own props; the adapter spreads this record directly onto the element it
113
- // builds and appends the header config child and real screen content itself, not here.
114
- export function resolveScreenProps(view) {
115
- return {
116
- ...view.passthrough,
117
- screenId: view.screenId,
118
- activityState: view.activityState,
119
- gestureEnabled: view.gestureEnabled,
120
- stackAnimation: view.stackAnimation ?? STACK_DEFAULT_ANIMATION,
121
- stackPresentation: view.stackPresentation ?? STACK_DEFAULT_PRESENTATION,
122
- // Off by default upstream; a formSheet's updateBounds pushes the sheet's live native size
123
- // into Fabric's shadow state on every drag frame, and this switches that push from
124
- // Asynchronous to EventQueue::UpdateMode::unstable_Immediate. The ScrollView-frame correction
125
- // that fills a taller detent (RNSScreenContentWrapper.mm's
126
- // coerceChildScrollViewComponentSizeToSize) is driven by that same state, so an async push
127
- // likely lags one step behind a fast drag. Scoped to formSheet: push/plain-modal screens have
128
- // no comparable live-resize need.
129
- synchronousShadowStateUpdatesEnabled: view.stackPresentation === 'formSheet',
130
- transitionDuration: view.transitionDuration,
131
- sheetAllowedDetents: view.sheetAllowedDetents,
132
- // Native RNSScreen's real prop names carry an "Index" suffix (react-native-screens' own
133
- // types.tsx); without it the value never reaches the native view and silently falls back to
134
- // RNS's default.
135
- sheetLargestUndimmedDetentIndex: view.sheetLargestUndimmedDetent,
136
- sheetInitialDetentIndex: view.sheetInitialDetent,
137
- sheetGrabberVisible: view.sheetGrabberVisible,
138
- sheetCornerRadius: view.sheetCornerRadius,
139
- sheetExpandsWhenScrolledToEdge: view.sheetExpandsWhenScrolledToEdge,
140
- sheetElevation: view.sheetElevation,
141
- sheetShouldOverflowTopInset: view.sheetShouldOverflowTopInset,
142
- sheetDefaultResizeAnimationEnabled: view.sheetDefaultResizeAnimationEnabled,
143
- statusBarStyle: view.statusBarStyle,
144
- statusBarHidden: view.statusBarHidden,
145
- statusBarAnimation: view.statusBarAnimation,
146
- screenOrientation: view.screenOrientation,
147
- };
148
- }
149
- // `style: {flex: 1}` is ours, not react-native-screens': RNSScreenStackView sizes its nav
150
- // controller to `self.bounds`, which Yoga would otherwise collapse to zero, and (unlike RN's
151
- // own ScreenStack.tsx) we drive the native view directly (<third_party_rn_packages_are_react_only>).
152
- export function resolveStackProps(view) {
153
- return { style: { flex: 1 }, ...view.passthrough };
154
- }
155
- // The RNSScreenStackHeaderConfig leaf is a full Descriptor since it never hosts framework children,
156
- // so the adapter bridges it verbatim (descriptorToReact/descriptorToVue) as the screen's first
157
- // child, ahead of the real content. `searchBarProps` nests as an RNSSearchBar wrapped in an
158
- // RNSScreenStackHeaderSubview(type: 'searchBar'), matching how react-native-screens' own JS
159
- // component mounts it (see resolveSearchBarView's comment in screen-options.ts); omitted when the
160
- // screen has no search bar.
161
- export function renderHeaderConfig(view, searchBarProps) {
162
- const props = {
163
- ...view.passthrough,
164
- title: view.title,
165
- hidden: view.hidden,
166
- // Android-only (CustomToolbar.kt gates its WindowInsets padding on these; iOS's
167
- // UINavigationController lays out under the safe area regardless). Upstream's JS
168
- // ScreenStackHeaderConfig computes these via an EdgeInsetApplicationContext that coordinates a
169
- // "consume once" top inset across nested headers; we drive RNSScreenStackHeaderConfig directly
170
- // and skip that layer, so without this the native default (false) leaves the header glued
171
- // under the status bar/cutout on Android. Every adapter renders one active header at a time,
172
- // so consuming all four edges matches upstream's default for the common case; a genuine
173
- // nested-header edge case would need upstream's same coordination.
174
- consumeTopInset: !view.hidden,
175
- consumeLeftInset: !view.hidden,
176
- consumeRightInset: !view.hidden,
177
- consumeBottomInset: !view.hidden,
178
- backTitle: view.backTitle,
179
- backTitleVisible: view.backTitleVisible,
180
- backButtonDisplayMode: view.backButtonDisplayMode,
181
- largeTitle: view.largeTitle,
182
- translucent: view.translucent,
183
- color: view.color,
184
- titleColor: view.titleColor,
185
- backgroundColor: view.backgroundColor,
186
- largeTitleBackgroundColor: view.largeTitleBackgroundColor,
187
- userInterfaceStyle: view.userInterfaceStyle,
188
- headerLeftBarButtonItems: view.headerLeftBarButtonItems,
189
- headerRightBarButtonItems: view.headerRightBarButtonItems,
190
- [HEADER_ON_PRESS_BAR_BUTTON_ITEM]: view.onPressHeaderBarButtonItem,
191
- [HEADER_ON_PRESS_BAR_BUTTON_MENU_ITEM]: view.onPressHeaderBarButtonMenuItem,
192
- };
193
- const children = searchBarProps
194
- ? [
195
- el(RNS_SCREEN_STACK_HEADER_SUBVIEW_VIEW_NAME, { type: 'searchBar' }, [
196
- el(RNS_SEARCH_BAR_VIEW_NAME, searchBarProps),
197
- ]),
198
- ]
199
- : [];
200
- return el(RNS_SCREEN_STACK_HEADER_CONFIG_VIEW_NAME, props, children);
201
- }
202
- // The RNSSearchBar leaf's static config props; event handlers ride in `passthrough`.
203
- // renderHeaderConfig wraps this into the RNSSearchBar Descriptor and nests it as the header
204
- // config's child.
205
- export function resolveSearchBarProps(view) {
206
- return {
207
- ...view.passthrough,
208
- placeholder: view.placeholder,
209
- autoCapitalize: view.autoCapitalize,
210
- placement: view.placement,
211
- hideWhenScrolling: view.hideWhenScrolling,
212
- allowToolbarIntegration: view.allowToolbarIntegration,
213
- obscureBackground: view.obscureBackground,
214
- hideNavigationBar: view.hideNavigationBar,
215
- cancelButtonText: view.cancelButtonText,
216
- barTintColor: view.barTintColor,
217
- tintColor: view.tintColor,
218
- textColor: view.textColor,
219
- autoFocus: view.autoFocus,
220
- disableBackButtonOverride: view.disableBackButtonOverride,
221
- inputType: view.inputType,
222
- hintTextColor: view.hintTextColor,
223
- headerIconColor: view.headerIconColor,
224
- shouldShowHintSearchIcon: view.shouldShowHintSearchIcon,
225
- };
226
- }
227
- // The 7 search-bar event keys -> option-callback passthrough: zero lifecycle dependency, so every
228
- // adapter built the identical map by hand. `log` is an optional per-event hook so an adapter can
229
- // still attach its own route-scoped dlog line (React/Vue do; Angular doesn't) without forking the
230
- // map itself.
231
- export function buildSearchBarPassthrough(options, log) {
232
- return {
233
- [SEARCH_BAR_ON_FOCUS]: () => {
234
- log?.('search bar focused');
235
- options.onFocus?.();
236
- },
237
- [SEARCH_BAR_ON_BLUR]: () => {
238
- log?.('search bar blurred');
239
- options.onBlur?.();
240
- },
241
- [SEARCH_BAR_ON_CHANGE_TEXT]: (event) => {
242
- const { text } = event.nativeEvent;
243
- const changedText = typeof text === 'string' ? text : '';
244
- log?.(`search text changed: ${changedText}`);
245
- options.onChangeText?.(changedText);
246
- },
247
- [SEARCH_BAR_ON_SEARCH_BUTTON_PRESS]: (event) => {
248
- const { text } = event.nativeEvent;
249
- const pressedText = typeof text === 'string' ? text : '';
250
- log?.(`search button pressed: ${pressedText}`);
251
- options.onSearchButtonPress?.(pressedText);
252
- },
253
- [SEARCH_BAR_ON_CANCEL_BUTTON_PRESS]: () => {
254
- log?.('search bar cancel pressed');
255
- options.onCancelButtonPress?.();
256
- },
257
- [SEARCH_BAR_ON_CLOSE]: () => {
258
- log?.('search bar closed');
259
- options.onClose?.();
260
- },
261
- [SEARCH_BAR_ON_OPEN]: () => {
262
- log?.('search bar opened');
263
- options.onOpen?.();
264
- },
265
- };
266
- }
267
- export function resolveScreenRenderPlan(input) {
268
- const { options, isAndroid } = input;
269
- const headerHidden = options?.headerShown === false;
270
- const activityState = computeActivityState(input.index, input.routeCount);
271
- const screenProps = resolveScreenProps(resolveScreenView(input.screenId, activityState, options, input.screenPassthrough));
272
- const searchBarOptions = options?.headerSearchBarOptions;
273
- const searchBarProps = searchBarOptions
274
- ? resolveSearchBarProps(resolveSearchBarView(searchBarOptions, input.searchBarPassthrough ?? {}))
275
- : undefined;
276
- // See renderHeaderConfig's own header comment for why `props` never reflects `searchBarProps` --
277
- // only `children` does -- so passing it here unconditionally is safe even for Angular, which
278
- // never reads `children` off the result.
279
- const headerConfig = renderHeaderConfig(resolveHeaderConfigView(options, input.platform), searchBarProps);
280
- return {
281
- activityState,
282
- screenViewName: resolveScreenViewName(options?.stackPresentation, isAndroid),
283
- screenProps,
284
- headerConfig,
285
- searchBarProps,
286
- contentWrapperProps: {
287
- style: resolveScreenContentWrapperStyle(options?.stackPresentation, headerHidden, options?.headerTranslucent, isAndroid),
288
- collapsable: false,
289
- },
290
- inModal: isHeaderInModal(options?.stackPresentation, headerHidden, isAndroid),
291
- innerStackStyle: resolveHeaderInModalStackStyle(),
292
- innerScreenStyle: resolveHeaderInModalScreenStyle(),
293
- };
294
- }
@@ -1,20 +0,0 @@
1
- import type { IDescriptor } from '@symbiote-native/components';
2
- import type { IColorValue, IViewStyle, IStyleProp } from '@symbiote-native/engine';
3
- import type { ITabBarIcon } from './tab-options';
4
- export type ITabBarItemView = {
5
- key: string;
6
- focused: boolean;
7
- label: string;
8
- icon?: ITabBarIcon;
9
- badge?: string | number;
10
- activeTintColor?: IColorValue;
11
- inactiveTintColor?: IColorValue;
12
- passthrough: Record<string, unknown>;
13
- };
14
- export type ITabBarViewProps = {
15
- items: readonly ITabBarItemView[];
16
- style?: IStyleProp<IViewStyle>;
17
- passthrough: Record<string, unknown>;
18
- };
19
- export declare function renderTabBarItem(item: ITabBarItemView): IDescriptor;
20
- export declare function renderTabBar(view: ITabBarViewProps): IDescriptor;
@@ -1,97 +0,0 @@
1
- // TabBar: the render half (framework-agnostic). A bottom-tabs bar is a PURE-JS UI - unlike
2
- // Stack, which drives real native RNSScreen/RNSScreenStack views, this paints ordinary
3
- // `symbiote-view`/`symbiote-text` primitives, so it needs no react-native-screens ViewConfig
4
- // work at all (see the task brief in packages/navigation's skill notes). Mirrors renderSwitch's
5
- // shape: pure prop-driven functions building a `Descriptor` tree from pre-resolved view props;
6
- // the adapter supplies focus state, tint-color resolution inputs, and the press wiring via
7
- // `passthrough` (exactly like Stack wires onDismissed/onHeaderBackButtonClicked into
8
- // resolveScreenView's passthrough) - this layer never invents an onPress/responder pair itself.
9
- import { el, txt } from '@symbiote-native/components';
10
- // react-navigation's own bottom-tabs default to the surrounding theme's primary/text colors;
11
- // symbiote's navigation package has no app-level theme yet, so these are placeholder defaults -
12
- // the same iOS-system-blue/neutral-grey pair render-button.ts already uses for its default
13
- // tint/disabled colors, reused here rather than inventing a third pair (no shared semantic color
14
- // tokens exist yet in @symbiote-native/engine as of this writing).
15
- const DEFAULT_ACTIVE_TINT_COLOR = '#007AFF';
16
- const DEFAULT_INACTIVE_TINT_COLOR = '#8e8e93';
17
- const BADGE_BACKGROUND_COLOR = '#FF3B30';
18
- const BADGE_TEXT_COLOR = '#ffffff';
19
- const TAB_BAR_HEIGHT = 49;
20
- const TAB_ICON_FONT_SIZE = 20;
21
- const TAB_LABEL_FONT_SIZE = 10;
22
- const BADGE_SIZE = 16;
23
- const BADGE_BORDER_RADIUS = BADGE_SIZE / 2;
24
- const BADGE_OFFSET_TOP = -4;
25
- const BADGE_OFFSET_RIGHT = -8;
26
- const TAB_BAR_STYLE = {
27
- flexDirection: 'row',
28
- height: TAB_BAR_HEIGHT,
29
- };
30
- const TAB_ITEM_STYLE = {
31
- flex: 1,
32
- alignItems: 'center',
33
- justifyContent: 'center',
34
- };
35
- const ICON_WRAPPER_STYLE = {
36
- alignItems: 'center',
37
- justifyContent: 'center',
38
- };
39
- const BADGE_STYLE = {
40
- position: 'absolute',
41
- top: BADGE_OFFSET_TOP,
42
- right: BADGE_OFFSET_RIGHT,
43
- minWidth: BADGE_SIZE,
44
- height: BADGE_SIZE,
45
- borderRadius: BADGE_BORDER_RADIUS,
46
- backgroundColor: BADGE_BACKGROUND_COLOR,
47
- alignItems: 'center',
48
- justifyContent: 'center',
49
- paddingHorizontal: 3,
50
- };
51
- const BADGE_TEXT_STYLE = {
52
- color: BADGE_TEXT_COLOR,
53
- fontSize: 8,
54
- fontWeight: '600',
55
- };
56
- function resolveTintColor(item) {
57
- if (item.focused)
58
- return item.activeTintColor ?? DEFAULT_ACTIVE_TINT_COLOR;
59
- return item.inactiveTintColor ?? DEFAULT_INACTIVE_TINT_COLOR;
60
- }
61
- // A string icon paints as a glyph (an emoji/ligature font name - same convention a caller uses
62
- // for a vector-icon-font label); an IDescriptor icon (an <Image>/vector-icon element the adapter
63
- // already resolved) is spliced in verbatim, exactly like renderHeaderConfig treats a pre-built
64
- // Descriptor child.
65
- function renderIcon(icon, color) {
66
- if (icon === undefined)
67
- return undefined;
68
- if (typeof icon === 'string')
69
- return txt({ style: { color, fontSize: TAB_ICON_FONT_SIZE } }, [icon]);
70
- return icon;
71
- }
72
- function renderBadge(badge) {
73
- if (badge === undefined)
74
- return undefined;
75
- return el('symbiote-view', { style: BADGE_STYLE }, [
76
- txt({ style: BADGE_TEXT_STYLE }, [String(badge)]),
77
- ]);
78
- }
79
- export function renderTabBarItem(item) {
80
- const color = resolveTintColor(item);
81
- const icon = renderIcon(item.icon, color);
82
- const badge = renderBadge(item.badge);
83
- const iconChildren = [];
84
- if (icon)
85
- iconChildren.push(icon);
86
- if (badge)
87
- iconChildren.push(badge);
88
- const children = [];
89
- if (iconChildren.length > 0)
90
- children.push(el('symbiote-view', { style: ICON_WRAPPER_STYLE }, iconChildren));
91
- children.push(txt({ style: { color, fontSize: TAB_LABEL_FONT_SIZE } }, [item.label]));
92
- return el('symbiote-view', { ...item.passthrough, style: TAB_ITEM_STYLE, accessibilityRole: 'tab' }, children, item.key);
93
- }
94
- export function renderTabBar(view) {
95
- const items = view.items.map(renderTabBarItem);
96
- return el('symbiote-view', { ...view.passthrough, style: [TAB_BAR_STYLE, view.style] }, items);
97
- }
@@ -1,4 +0,0 @@
1
- import type { IHeaderConfigViewProps, INavigatorPlatform, IScreenOptions, IScreenViewProps, ISearchBarOptions, ISearchBarViewProps } from './navigator-props';
2
- export declare function resolveHeaderConfigView(options: IScreenOptions | undefined, platform: INavigatorPlatform, passthrough?: Record<string, unknown>): IHeaderConfigViewProps;
3
- export declare function resolveScreenView(screenId: string, activityState: number, options: IScreenOptions | undefined, passthrough?: Record<string, unknown>): IScreenViewProps;
4
- export declare function resolveSearchBarView(options: ISearchBarOptions | undefined, passthrough?: Record<string, unknown>): ISearchBarViewProps;