@symbiote-native/navigation 2.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 (155) hide show
  1. package/package.json +11 -10
  2. package/build/angular/drawer/index.d.ts +0 -76
  3. package/build/angular/drawer/index.js +0 -508
  4. package/build/angular/drawer-screen.directive.d.ts +0 -14
  5. package/build/angular/drawer-screen.directive.js +0 -87
  6. package/build/angular/index.d.ts +0 -23
  7. package/build/angular/index.js +0 -22
  8. package/build/angular/injectors/index.d.ts +0 -13
  9. package/build/angular/injectors/index.js +0 -8
  10. package/build/angular/injectors/inject-drawer-navigation.d.ts +0 -4
  11. package/build/angular/injectors/inject-drawer-navigation.js +0 -12
  12. package/build/angular/injectors/inject-focus-effect.d.ts +0 -2
  13. package/build/angular/injectors/inject-focus-effect.js +0 -29
  14. package/build/angular/injectors/inject-is-focused.d.ts +0 -2
  15. package/build/angular/injectors/inject-is-focused.js +0 -20
  16. package/build/angular/injectors/inject-navigation-state.d.ts +0 -3
  17. package/build/angular/injectors/inject-navigation-state.js +0 -35
  18. package/build/angular/injectors/inject-navigation.d.ts +0 -7
  19. package/build/angular/injectors/inject-navigation.js +0 -16
  20. package/build/angular/injectors/inject-route.d.ts +0 -3
  21. package/build/angular/injectors/inject-route.js +0 -20
  22. package/build/angular/injectors/inject-stack-navigation.d.ts +0 -4
  23. package/build/angular/injectors/inject-stack-navigation.js +0 -15
  24. package/build/angular/injectors/inject-tab-navigation.d.ts +0 -4
  25. package/build/angular/injectors/inject-tab-navigation.js +0 -11
  26. package/build/angular/linking.d.ts +0 -3
  27. package/build/angular/linking.js +0 -43
  28. package/build/angular/navigation-context.service.d.ts +0 -13
  29. package/build/angular/navigation-context.service.js +0 -105
  30. package/build/angular/navigation-scope.directive.d.ts +0 -10
  31. package/build/angular/navigation-scope.directive.js +0 -90
  32. package/build/angular/screen.directive.d.ts +0 -22
  33. package/build/angular/screen.directive.js +0 -90
  34. package/build/angular/search-bar-ref.directive.d.ts +0 -10
  35. package/build/angular/search-bar-ref.directive.js +0 -88
  36. package/build/angular/stack/index.d.ts +0 -55
  37. package/build/angular/stack/index.js +0 -386
  38. package/build/angular/tab-screen.directive.d.ts +0 -14
  39. package/build/angular/tab-screen.directive.js +0 -87
  40. package/build/angular/tabs/index.d.ts +0 -42
  41. package/build/angular/tabs/index.js +0 -263
  42. package/build/core/constants.d.ts +0 -27
  43. package/build/core/constants.js +0 -61
  44. package/build/core/drawer-options/index.d.ts +0 -64
  45. package/build/core/drawer-options/index.js +0 -200
  46. package/build/core/drawer-router-state/index.d.ts +0 -21
  47. package/build/core/drawer-router-state/index.js +0 -40
  48. package/build/core/guards.d.ts +0 -2
  49. package/build/core/guards.js +0 -22
  50. package/build/core/header-bar-buttons.d.ts +0 -9
  51. package/build/core/header-bar-buttons.js +0 -163
  52. package/build/core/index.d.ts +0 -29
  53. package/build/core/index.js +0 -32
  54. package/build/core/linking-config/index.d.ts +0 -13
  55. package/build/core/linking-config/index.js +0 -171
  56. package/build/core/navigation-events/index.d.ts +0 -16
  57. package/build/core/navigation-events/index.js +0 -57
  58. package/build/core/navigator-handles/index.d.ts +0 -30
  59. package/build/core/navigator-handles/index.js +0 -15
  60. package/build/core/navigator-props.d.ts +0 -216
  61. package/build/core/navigator-props.js +0 -7
  62. package/build/core/navigator-state/index.d.ts +0 -36
  63. package/build/core/navigator-state/index.js +0 -81
  64. package/build/core/render-drawer.d.ts +0 -13
  65. package/build/core/render-drawer.js +0 -88
  66. package/build/core/render-stack.d.ts +0 -35
  67. package/build/core/render-stack.js +0 -294
  68. package/build/core/render-tabs.d.ts +0 -20
  69. package/build/core/render-tabs.js +0 -97
  70. package/build/core/screen-options.d.ts +0 -4
  71. package/build/core/screen-options.js +0 -133
  72. package/build/core/search-bar-commands.d.ts +0 -10
  73. package/build/core/search-bar-commands.js +0 -42
  74. package/build/core/state-persistence/index.d.ts +0 -3
  75. package/build/core/state-persistence/index.js +0 -23
  76. package/build/core/tab-options.d.ts +0 -13
  77. package/build/core/tab-options.js +0 -10
  78. package/build/core/tab-router-state/index.d.ts +0 -19
  79. package/build/core/tab-router-state/index.js +0 -51
  80. package/build/react/collect-registry.d.ts +0 -4
  81. package/build/react/collect-registry.js +0 -15
  82. package/build/react/drawer/index.d.ts +0 -29
  83. package/build/react/drawer/index.js +0 -277
  84. package/build/react/drawer-screen.d.ts +0 -14
  85. package/build/react/drawer-screen.js +0 -8
  86. package/build/react/hooks/index.d.ts +0 -12
  87. package/build/react/hooks/index.js +0 -8
  88. package/build/react/hooks/use-drawer-navigation.d.ts +0 -4
  89. package/build/react/hooks/use-drawer-navigation.js +0 -12
  90. package/build/react/hooks/use-focus-effect.d.ts +0 -2
  91. package/build/react/hooks/use-focus-effect.js +0 -29
  92. package/build/react/hooks/use-is-focused.d.ts +0 -1
  93. package/build/react/hooks/use-is-focused.js +0 -22
  94. package/build/react/hooks/use-navigation-state/index.d.ts +0 -2
  95. package/build/react/hooks/use-navigation-state/index.js +0 -39
  96. package/build/react/hooks/use-navigation.d.ts +0 -7
  97. package/build/react/hooks/use-navigation.js +0 -16
  98. package/build/react/hooks/use-route.d.ts +0 -2
  99. package/build/react/hooks/use-route.js +0 -8
  100. package/build/react/hooks/use-stack-navigation.d.ts +0 -4
  101. package/build/react/hooks/use-stack-navigation.js +0 -14
  102. package/build/react/hooks/use-tab-navigation.d.ts +0 -4
  103. package/build/react/hooks/use-tab-navigation.js +0 -12
  104. package/build/react/index.d.ts +0 -24
  105. package/build/react/index.js +0 -21
  106. package/build/react/linking.d.ts +0 -3
  107. package/build/react/linking.js +0 -47
  108. package/build/react/navigation-context.d.ts +0 -10
  109. package/build/react/navigation-context.js +0 -22
  110. package/build/react/screen.d.ts +0 -22
  111. package/build/react/screen.js +0 -7
  112. package/build/react/stack/index.d.ts +0 -13
  113. package/build/react/stack/index.js +0 -202
  114. package/build/react/tab-screen.d.ts +0 -15
  115. package/build/react/tab-screen.js +0 -8
  116. package/build/react/tabs/index.d.ts +0 -12
  117. package/build/react/tabs/index.js +0 -116
  118. package/build/register.d.ts +0 -7
  119. package/build/register.js +0 -58
  120. package/build/vue/composables/index.d.ts +0 -12
  121. package/build/vue/composables/index.js +0 -8
  122. package/build/vue/composables/use-drawer-navigation.d.ts +0 -5
  123. package/build/vue/composables/use-drawer-navigation.js +0 -15
  124. package/build/vue/composables/use-focus-effect.d.ts +0 -1
  125. package/build/vue/composables/use-focus-effect.js +0 -31
  126. package/build/vue/composables/use-is-focused.d.ts +0 -2
  127. package/build/vue/composables/use-is-focused.js +0 -28
  128. package/build/vue/composables/use-navigation-state.d.ts +0 -3
  129. package/build/vue/composables/use-navigation-state.js +0 -29
  130. package/build/vue/composables/use-navigation.d.ts +0 -8
  131. package/build/vue/composables/use-navigation.js +0 -13
  132. package/build/vue/composables/use-route.d.ts +0 -3
  133. package/build/vue/composables/use-route.js +0 -9
  134. package/build/vue/composables/use-stack-navigation.d.ts +0 -5
  135. package/build/vue/composables/use-stack-navigation.js +0 -18
  136. package/build/vue/composables/use-tab-navigation.d.ts +0 -5
  137. package/build/vue/composables/use-tab-navigation.js +0 -15
  138. package/build/vue/drawer/index.d.ts +0 -19
  139. package/build/vue/drawer/index.js +0 -390
  140. package/build/vue/drawer-screen.d.ts +0 -14
  141. package/build/vue/drawer-screen.js +0 -12
  142. package/build/vue/index.d.ts +0 -21
  143. package/build/vue/index.js +0 -19
  144. package/build/vue/linking.d.ts +0 -3
  145. package/build/vue/linking.js +0 -57
  146. package/build/vue/navigation-context.d.ts +0 -16
  147. package/build/vue/navigation-context.js +0 -65
  148. package/build/vue/screen.d.ts +0 -21
  149. package/build/vue/screen.js +0 -12
  150. package/build/vue/stack/index.d.ts +0 -9
  151. package/build/vue/stack/index.js +0 -279
  152. package/build/vue/tab-screen.d.ts +0 -14
  153. package/build/vue/tab-screen.js +0 -11
  154. package/build/vue/tabs/index.d.ts +0 -10
  155. package/build/vue/tabs/index.js +0 -209
@@ -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;
@@ -1,133 +0,0 @@
1
- // Pure folds from a route's agnostic IScreenOptions into the native view-props shapes
2
- // (RNSScreen + RNSScreenStackHeaderConfig + RNSSearchBar paint from). Framework-agnostic and
3
- // shared verbatim by every adapter - the adapter's own job is only wiring identity (screenId),
4
- // position (activityState via computeActivityState) and the lifecycle event handlers into
5
- // `passthrough`.
6
- //
7
- // The header bar-button payload+dispatch cluster (id-tagging, onPress lookup) lives in
8
- // header-bar-buttons.ts - self-contained, consumed here only as a black box by
9
- // resolveHeaderConfigView.
10
- import { buildHeaderBarButtonDispatch, prepareHeaderBarButtonItems } from './header-bar-buttons.js';
11
- // These sentinels must be kept in sync with react-native-screens' native side - they are RNS's
12
- // own compat values for the 'fitToContents'/'large'/'medium'/'all' legacy presets, replicated
13
- // here because we drive RNSScreen directly (no react-native-screens JS component in the path).
14
- const SHEET_FIT_TO_CONTENTS = [-1];
15
- const SHEET_COMPAT_LARGE = [1.0];
16
- const SHEET_COMPAT_MEDIUM = [0.5];
17
- const SHEET_COMPAT_ALL = [0.5, 1.0];
18
- const SHEET_DIMMED_ALWAYS = -1;
19
- function resolveSheetAllowedDetents(compat) {
20
- if (compat === undefined)
21
- return SHEET_COMPAT_LARGE;
22
- if (Array.isArray(compat))
23
- return compat;
24
- switch (compat) {
25
- case 'fitToContents':
26
- return SHEET_FIT_TO_CONTENTS;
27
- case 'large':
28
- return SHEET_COMPAT_LARGE;
29
- case 'medium':
30
- return SHEET_COMPAT_MEDIUM;
31
- case 'all':
32
- return SHEET_COMPAT_ALL;
33
- }
34
- }
35
- function resolveSheetLargestUndimmedDetent(compat, lastDetentIndex) {
36
- if (typeof compat === 'number')
37
- return compat;
38
- switch (compat) {
39
- case 'last':
40
- return lastDetentIndex;
41
- case 'large':
42
- return 1;
43
- case 'medium':
44
- return 0;
45
- case 'none':
46
- case 'all':
47
- case undefined:
48
- return SHEET_DIMMED_ALWAYS;
49
- }
50
- }
51
- function resolveSheetInitialDetent(compat, lastDetentIndex) {
52
- if (compat === 'last')
53
- return lastDetentIndex;
54
- if (compat === undefined)
55
- return 0;
56
- return compat;
57
- }
58
- function toOptionalBooleanNativeProp(value) {
59
- if (value === undefined)
60
- return 'undefined';
61
- return value ? 'true' : 'false';
62
- }
63
- export function resolveHeaderConfigView(options, platform, passthrough = {}) {
64
- return {
65
- title: options?.title,
66
- hidden: options?.headerShown === false,
67
- backTitle: options?.headerBackTitle,
68
- backTitleVisible: platform.defaultHeaderBackTitleVisible,
69
- backButtonDisplayMode: options?.headerBackButtonDisplayMode,
70
- largeTitle: options?.headerLargeTitle,
71
- translucent: options?.headerTranslucent,
72
- color: options?.headerTintColor,
73
- titleColor: options?.headerTitleColor,
74
- backgroundColor: options?.headerStyle?.backgroundColor,
75
- largeTitleBackgroundColor: options?.headerLargeStyle?.backgroundColor,
76
- userInterfaceStyle: options?.headerUserInterfaceStyle,
77
- headerLeftBarButtonItems: prepareHeaderBarButtonItems(options?.headerLeftBarButtonItems, 'left'),
78
- headerRightBarButtonItems: prepareHeaderBarButtonItems(options?.headerRightBarButtonItems, 'right'),
79
- ...buildHeaderBarButtonDispatch(options?.headerLeftBarButtonItems, options?.headerRightBarButtonItems),
80
- passthrough,
81
- };
82
- }
83
- export function resolveScreenView(screenId, activityState, options, passthrough = {}) {
84
- const sheetAllowedDetents = resolveSheetAllowedDetents(options?.sheetAllowedDetents);
85
- const lastDetentIndex = sheetAllowedDetents.length - 1;
86
- return {
87
- screenId,
88
- activityState,
89
- gestureEnabled: options?.gestureEnabled,
90
- stackAnimation: options?.stackAnimation,
91
- stackPresentation: options?.stackPresentation,
92
- transitionDuration: options?.transitionDuration,
93
- sheetAllowedDetents,
94
- sheetLargestUndimmedDetent: resolveSheetLargestUndimmedDetent(options?.sheetLargestUndimmedDetentIndex, lastDetentIndex),
95
- sheetInitialDetent: resolveSheetInitialDetent(options?.sheetInitialDetentIndex, lastDetentIndex),
96
- sheetGrabberVisible: options?.sheetGrabberVisible,
97
- sheetCornerRadius: options?.sheetCornerRadius,
98
- sheetExpandsWhenScrolledToEdge: options?.sheetExpandsWhenScrolledToEdge,
99
- sheetElevation: options?.sheetElevation,
100
- sheetShouldOverflowTopInset: options?.sheetShouldOverflowTopInset,
101
- sheetDefaultResizeAnimationEnabled: options?.sheetDefaultResizeAnimationEnabled,
102
- statusBarStyle: options?.statusBarStyle,
103
- statusBarHidden: options?.statusBarHidden,
104
- statusBarAnimation: options?.statusBarAnimation,
105
- screenOrientation: options?.screenOrientation,
106
- passthrough,
107
- };
108
- }
109
- // RNSSearchBar is a standalone Fabric leaf (react-native-screens mounts it as a header-subview
110
- // child in its own JS component) - resolveSearchBarView only folds the static config surface;
111
- // where/how the adapter mounts the resulting leaf in the header tree is an adapter concern.
112
- export function resolveSearchBarView(options, passthrough = {}) {
113
- return {
114
- placeholder: options?.placeholder,
115
- autoCapitalize: options?.autoCapitalize,
116
- placement: options?.placement,
117
- hideWhenScrolling: options?.hideWhenScrolling,
118
- allowToolbarIntegration: options?.allowToolbarIntegration,
119
- obscureBackground: toOptionalBooleanNativeProp(options?.obscureBackground),
120
- hideNavigationBar: toOptionalBooleanNativeProp(options?.hideNavigationBar),
121
- cancelButtonText: options?.cancelButtonText,
122
- barTintColor: options?.barTintColor,
123
- tintColor: options?.tintColor,
124
- textColor: options?.textColor,
125
- autoFocus: options?.autoFocus,
126
- disableBackButtonOverride: options?.disableBackButtonOverride,
127
- inputType: options?.inputType,
128
- hintTextColor: options?.hintTextColor,
129
- headerIconColor: options?.headerIconColor,
130
- shouldShowHintSearchIcon: options?.shouldShowHintSearchIcon,
131
- passthrough,
132
- };
133
- }
@@ -1,10 +0,0 @@
1
- import { type ISymbioteNode } from '@symbiote-native/engine';
2
- export interface ISearchBarCommands {
3
- focus(): void;
4
- blur(): void;
5
- clearText(): void;
6
- setText(text: string): void;
7
- cancelSearch(): void;
8
- toggleCancelButton(show: boolean): void;
9
- }
10
- export declare function buildSearchBarHandle(getNode: () => ISymbioteNode | null): ISearchBarCommands;
@@ -1,42 +0,0 @@
1
- // The imperative API react-native-screens' own SearchBarProps exposes via its `ref` field
2
- // (SearchBarCommands): each method dispatches a native view command on the RNSSearchBar node
3
- // (SearchBarNativeComponent's supportedCommands: blur/focus/clearText/toggleCancelButton/
4
- // setText/cancelSearch). Framework-agnostic - mirrors @symbiote-native/components'
5
- // buildScrollViewHandle exactly (same lazy-getter shape); the adapter supplies the node getter
6
- // and its own framework-specific ref field (see react/screen.ts's IReactSearchBarOptions - the
7
- // ref itself is per-adapter, same split as IPressableProps).
8
- import { dispatchViewCommand, dlog } from '@symbiote-native/engine';
9
- // dispatchViewCommand itself dlogs a "node not committed" skip; this null case (the ref never
10
- // attached to a live RNSSearchBar at all - e.g. the header's search bar isn't mounted, or a
11
- // stale handle outlived its screen) was the one unlogged gap, silently indistinguishable from
12
- // "the command fired but did nothing". Investigation instrumentation (HeaderOptionsScreen
13
- // unresponsive-buttons bug); kept behind DEBUG per <keep_logs_gate_behind_DEBUG>, never removed.
14
- function warnIfDetached(command, node) {
15
- if (node !== null)
16
- return true;
17
- dlog(`SearchBarCommands.${command}: skipped, node is null (ref never attached) at t=${Date.now()}`);
18
- return false;
19
- }
20
- // Every command is the same 3-line dance: get the (lazily-read) node, bail with a dlog'd warning
21
- // if it's detached, dispatch the native view command. Factored once so the six methods below
22
- // differ only in which command name they dispatch.
23
- function makeCommand(getNode, command) {
24
- return (...args) => {
25
- const node = getNode();
26
- if (!warnIfDetached(command, node))
27
- return;
28
- dispatchViewCommand(node, command, args);
29
- };
30
- }
31
- // `getNode` is a LAZY getter, read on every call - see buildScrollViewHandle's comment for why
32
- // an eager capture would freeze `null`.
33
- export function buildSearchBarHandle(getNode) {
34
- return {
35
- focus: makeCommand(getNode, 'focus'),
36
- blur: makeCommand(getNode, 'blur'),
37
- clearText: makeCommand(getNode, 'clearText'),
38
- setText: makeCommand(getNode, 'setText'),
39
- cancelSearch: makeCommand(getNode, 'cancelSearch'),
40
- toggleCancelButton: makeCommand(getNode, 'toggleCancelButton'),
41
- };
42
- }
@@ -1,3 +0,0 @@
1
- import type { INavigatorState } from '../navigator-state';
2
- export declare function serializeNavigatorState(state: INavigatorState): unknown;
3
- export declare function deserializeNavigatorState(raw: unknown): INavigatorState;
@@ -1,23 +0,0 @@
1
- // Lightweight state persistence: pure serialize/deserialize for INavigatorState, the primitive
2
- // @react-navigation's `initialState`/`onStateChange` persistence recipe is built on. Routes are
3
- // already `{key, name, params}` - plain data, no functions/refs/class instances - so a JSON-safe
4
- // passthrough is sufficient; no framework wiring lives here yet (that's an adapter-level task).
5
- import { isRecord } from '../guards.js';
6
- // No `'params' in value` check: a route pushed with no params serializes to `{key, name,
7
- // params: undefined}`, and JSON.stringify drops undefined-valued keys - a JSON round trip of a
8
- // perfectly valid route legitimately arrives here without a `params` key at all.
9
- function isRoute(value) {
10
- return isRecord(value) && typeof value.key === 'string' && typeof value.name === 'string';
11
- }
12
- function isNavigatorState(value) {
13
- return isRecord(value) && Array.isArray(value.routes) && value.routes.every(isRoute);
14
- }
15
- export function serializeNavigatorState(state) {
16
- return state;
17
- }
18
- export function deserializeNavigatorState(raw) {
19
- if (!isNavigatorState(raw)) {
20
- throw new Error('deserializeNavigatorState: persisted value is not a valid navigator state');
21
- }
22
- return raw;
23
- }
@@ -1,13 +0,0 @@
1
- import type { IColorValue } from '@symbiote-native/engine';
2
- import type { IViewStyle, IStyleProp } from '@symbiote-native/engine';
3
- import type { IDescriptor } from '@symbiote-native/components';
4
- export type ITabBarIcon = IDescriptor | string;
5
- export type ITabOptions = {
6
- title?: string;
7
- tabBarLabel?: string;
8
- tabBarIcon?: ITabBarIcon;
9
- tabBarBadge?: string | number;
10
- tabBarActiveTintColor?: IColorValue;
11
- tabBarInactiveTintColor?: IColorValue;
12
- tabBarStyle?: IStyleProp<IViewStyle>;
13
- };