@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,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
- };
@@ -1,10 +0,0 @@
1
- // Per-tab agnostic options. Mirrors @react-navigation/bottom-tabs' `options` surface (title,
2
- // tabBarLabel, tabBarIcon, tabBarBadge, tint colors, tabBarStyle) minus anything that requires a
3
- // framework element: `tabBarIcon` there is a render prop returning a platform icon element, but
4
- // per <prop_types_split_agnostic_vs_per_adapter> a render-callback-returning-a-framework-element
5
- // field can't live in the shared agnostic type, so here it is EITHER a pre-built `IDescriptor`
6
- // (an adapter can bridge any host node it already resolved, image or vector icon) or a bare icon
7
- // NAME string the renderer paints as a label-style glyph - both framework-agnostic. An adapter
8
- // wanting a `(props) => ReactNode`-style callback resolves it to one of these two forms itself,
9
- // same split as IPressableProps's per-adapter children.
10
- export {};
@@ -1,19 +0,0 @@
1
- import type { IRoute } from '../navigator-state';
2
- export type { IRoute } from '../navigator-state';
3
- type IRouteEntry = IRoute<unknown>;
4
- export type ITabRouterState = Readonly<{
5
- routes: readonly IRouteEntry[];
6
- index: number;
7
- }>;
8
- export type ITabRouterAction = {
9
- type: 'jumpTo';
10
- name: string;
11
- params?: unknown;
12
- } | {
13
- type: 'setParams';
14
- key: string;
15
- params: unknown;
16
- };
17
- export declare function createInitialTabState(routes: readonly IRouteEntry[], initialRouteName?: string): ITabRouterState;
18
- export declare function tabRouterReducer(state: ITabRouterState, action: ITabRouterAction): ITabRouterState;
19
- export declare function isFocusedRoute(index: number, focusedIndex: number): boolean;
@@ -1,51 +0,0 @@
1
- // TabRouter: the logic half (framework-agnostic, zero render). A pure reducer over a FOCUSED
2
- // INDEX into a fixed route list - jumpTo/setParams - mirroring what @react-navigation/routers'
3
- // TabRouter keeps, minus the React-specific action-creator/linking machinery. The key structural
4
- // difference from navigator-state's Stack: tabs never push/pop a route, they move a focused
5
- // index between a fixed set (mirrors TabRouter.getInitialState / getStateForAction for
6
- // JUMP_TO - see the tab-actions docs: jumpTo takes a route NAME, not a stack push). Route
7
- // identity (`key`) is generated by the CALLER, same convention as navigator-state.ts - reused
8
- // verbatim from there so Stack and Tabs share one route shape.
9
- import { mergeParams } from '../guards.js';
10
- const INITIAL_FOCUSED_INDEX = 0;
11
- export function createInitialTabState(routes, initialRouteName) {
12
- if (initialRouteName === undefined)
13
- return { routes, index: INITIAL_FOCUSED_INDEX };
14
- const index = routes.findIndex(route => route.name === initialRouteName);
15
- return { routes, index: index === -1 ? INITIAL_FOCUSED_INDEX : index };
16
- }
17
- // jumpTo moves the focused index to an existing route by name and optionally merges params
18
- // (navigation.jumpTo(name, params) - the tab-actions doc's own signature); it never adds,
19
- // removes, or reorders routes (mirrors TabRouter: JUMP_TO is a no-op when the target name isn't
20
- // registered, same as navigatorReducer's popTo bailing on an unknown key). setParams merges into
21
- // the target route's params by key (CommonActions.setParams semantics - a merge, not a replace).
22
- export function tabRouterReducer(state, action) {
23
- switch (action.type) {
24
- case 'jumpTo': {
25
- const index = state.routes.findIndex(route => route.name === action.name);
26
- if (index === -1)
27
- return state;
28
- if (action.params === undefined) {
29
- return index === state.index ? state : { ...state, index };
30
- }
31
- const target = state.routes[index];
32
- const routes = state.routes.slice();
33
- routes[index] = { ...target, params: mergeParams(target.params, action.params) };
34
- return { ...state, index, routes };
35
- }
36
- case 'setParams': {
37
- const index = state.routes.findIndex(route => route.key === action.key);
38
- if (index === -1)
39
- return state;
40
- const target = state.routes[index];
41
- const routes = state.routes.slice();
42
- routes[index] = { ...target, params: mergeParams(target.params, action.params) };
43
- return { ...state, routes };
44
- }
45
- default:
46
- return state;
47
- }
48
- }
49
- export function isFocusedRoute(index, focusedIndex) {
50
- return index === focusedIndex;
51
- }
@@ -1,4 +0,0 @@
1
- import { type ReactElement, type ReactNode } from 'react';
2
- export declare function collectRegistry<TProps extends {
3
- name: string;
4
- }>(children: ReactNode, isMarkerElement: (child: ReactNode) => child is ReactElement<TProps>): Map<string, Omit<TProps, 'name'>>;
@@ -1,15 +0,0 @@
1
- // Shared by stack.ts/tabs.ts/drawer.ts: each navigator registers its own Screen marker
2
- // component's props into a name -> entry map via React.Children, differing only in the marker
3
- // component (isMarkerElement) and the props shape (TProps). Stays in react/ rather than core/:
4
- // Children/isValidElement are React-only APIs (see CLAUDE.md <adapter_src_follows_framework_idioms>).
5
- import { Children } from 'react';
6
- export function collectRegistry(children, isMarkerElement) {
7
- const registry = new Map();
8
- Children.forEach(children, child => {
9
- if (!isMarkerElement(child))
10
- return;
11
- const { name, ...entry } = child.props;
12
- registry.set(name, entry);
13
- });
14
- return registry;
15
- }
@@ -1,29 +0,0 @@
1
- import type { ReactNode } from 'react';
2
- import { type IStyleProp, type IViewStyle } from '@symbiote-native/engine';
3
- import type { IDrawerDescriptorMap, IDrawerNavigatorHandle, IDrawerOptions, IDrawerRouterState, IDrawerScreenOptions } from '../../core';
4
- import { DrawerScreen } from '../drawer-screen';
5
- export type { IDrawerNavigatorHandle, IDrawerDescriptorMap } from '../../core';
6
- export type IDrawerProps = IDrawerOptions & {
7
- initialRouteName?: string;
8
- screenOptions?: IDrawerScreenOptions;
9
- drawerStyle?: IStyleProp<IViewStyle>;
10
- renderDrawerContent?: (props: {
11
- state: IDrawerRouterState;
12
- descriptors: IDrawerDescriptorMap;
13
- navigation: IDrawerNavigatorHandle;
14
- }) => ReactNode;
15
- children?: ReactNode;
16
- };
17
- export declare const Drawer: import("react").ForwardRefExoticComponent<IDrawerOptions & {
18
- initialRouteName?: string;
19
- screenOptions?: IDrawerScreenOptions;
20
- drawerStyle?: IStyleProp<IViewStyle>;
21
- renderDrawerContent?: (props: {
22
- state: IDrawerRouterState;
23
- descriptors: IDrawerDescriptorMap;
24
- navigation: IDrawerNavigatorHandle;
25
- }) => ReactNode;
26
- children?: ReactNode;
27
- } & import("react").RefAttributes<IDrawerNavigatorHandle>> & {
28
- Screen: typeof DrawerScreen;
29
- };
@@ -1,277 +0,0 @@
1
- // Drawer, the React lifecycle half. The open/closed + focused-route router (drawer-router-state)
2
- // and the pure swipe/geometry math (drawer-options) live in @symbiote-native/navigation core,
3
- // shared verbatim with the Vue/Angular adapters; here React supplies the lifecycle - useReducer
4
- // for the router, a PanResponder for the swipe gesture (RN's own idiom: build it ONCE via
5
- // useRef, let its callbacks read current values off refs rather than rebuilding it every render),
6
- // an Animated.Value driving the slide/opacity transforms, and useImperativeHandle for the
7
- // open/close/toggle/jumpTo handle - mirroring react/tabs.ts's shape (Tab is the closer sibling:
8
- // both are fixed-route-list, no-react-native-screens navigators; Stack's push/pop + native-screen
9
- // bridging don't apply here).
10
- //
11
- // FEASIBILITY NOTE (see the drawer skill / PR description for the full writeup): the REAL
12
- // @react-navigation/drawer is built on react-native-gesture-handler + react-native-reanimated,
13
- // neither of which this codebase depends on. What's built here reaches the same swipe-to-open/
14
- // close + front/back/slide/permanent behavior using only PanResponder + Animated (both already in
15
- // @symbiote-native/engine), which is sufficient for a solid drawer but NOT byte-for-byte parity -
16
- // see the explicit gap list at the bottom of this file.
17
- import { createElement, forwardRef, isValidElement, useCallback, useContext, useEffect, useId, useImperativeHandle, useMemo, useReducer, useRef, } from 'react';
18
- import { Animated, PanResponder, useWindowDimensions } from '@symbiote-native/react';
19
- import { dlog } from '@symbiote-native/engine';
20
- import { DRAWER_DEFAULT_OVERLAY_COLOR, NAVIGATION_EVENT_BLUR, NAVIGATION_EVENT_FOCUS, createInitialDrawerRouterState, createNavigationEmitter, drawerChildOrder, drawerRouterReducer, isDrawerAnimated, renderDrawer, resolveDragProgress, resolveDrawerGeometry, resolveDrawerSlotInterpolation, resolveSwipeIntent, shouldClaimDrawerSwipe, } from '../../core/index.js';
21
- import { collectRegistry } from '../collect-registry.js';
22
- import { NavigationContext } from '../navigation-context.js';
23
- import { DrawerScreen } from '../drawer-screen.js';
24
- function isDrawerScreenElement(child) {
25
- return isValidElement(child) && child.type === DrawerScreen;
26
- }
27
- function resolveDrawerScreenOptions(entry, optionsArgs, screenOptions) {
28
- const own = typeof entry.options === 'function' ? entry.options(optionsArgs) : entry.options;
29
- return { ...screenOptions, ...own };
30
- }
31
- const DRAWER_SNAP_DURATION = 250;
32
- const DrawerImpl = forwardRef((props, forwardedRef) => {
33
- // Read BEFORE establishing this Drawer's own Context value below - becomes the `parent` link a
34
- // nested screen's useNavigation().getParent() walks (e.g. this Drawer rendered as a Stack
35
- // screen's content reaches that Stack via this value). undefined when this Drawer is the
36
- // nesting root.
37
- const ambientContext = useContext(NavigationContext);
38
- const { initialRouteName, screenOptions, renderDrawerContent, children, drawerStyle, drawerType, drawerPosition, drawerWidth, overlayColor, swipeEnabled, swipeEdgeWidth, swipeMinDistance, swipeMinVelocity, } = props;
39
- const options = {
40
- drawerType,
41
- drawerPosition,
42
- drawerWidth,
43
- overlayColor,
44
- swipeEnabled,
45
- swipeEdgeWidth,
46
- swipeMinDistance,
47
- swipeMinVelocity,
48
- };
49
- const registry = useMemo(() => collectRegistry(children, isDrawerScreenElement), [children]);
50
- const routeIdPrefix = useId();
51
- const routes = useMemo(() => Array.from(registry.entries()).map(([name, entry]) => ({
52
- key: `${routeIdPrefix}-${name}`,
53
- name,
54
- params: entry.initialParams,
55
- })), [registry, routeIdPrefix]);
56
- const [state, dispatch] = useReducer(drawerRouterReducer, undefined, () => createInitialDrawerRouterState(routes, initialRouteName));
57
- if (routes.length === 0)
58
- dlog('Drawer: no <Drawer.Screen> children registered');
59
- const { width: screenWidth } = useWindowDimensions();
60
- // progress: 0 closed -> 1 open, the single Animated.Value every slide/opacity transform below
61
- // interpolates from.
62
- const progress = useRef(new Animated.Value(state.isOpen ? 1 : 0)).current;
63
- // Where a drag STARTS from, in progress units. Always exactly 0 or 1: a gesture only ever
64
- // begins at rest, since terminate/release always snap the value back to a resting state before
65
- // another grant can fire.
66
- const dragStartProgress = useRef(0);
67
- // Refs so the PanResponder's callbacks - built ONCE below, RN's own idiom - always read the
68
- // CURRENT render's values without forcing a new PanResponder identity on every state/prop
69
- // change (recreating panHandlers mid-gesture would drop the in-flight touch).
70
- const optionsRef = useRef(options);
71
- optionsRef.current = options;
72
- const screenWidthRef = useRef(screenWidth);
73
- screenWidthRef.current = screenWidth;
74
- const isOpenRef = useRef(state.isOpen);
75
- isOpenRef.current = state.isOpen;
76
- const animateProgressTo = useCallback((open) => {
77
- // Investigation instrumentation (Drawer openDrawer-no-op / toggleDrawer-no-animation bug):
78
- // every imperative caller (openDrawer/closeDrawer/toggleDrawer/jumpTo) funnels through here,
79
- // so this single seam proves whether Animated.timing is actually being started at all, and
80
- // with what toValue - a live-only failure (e.g. a stale progress ref, or Animated.timing
81
- // silently short-circuiting) would show up as this log firing with no visible motion. Kept
82
- // behind DEBUG per <keep_logs_gate_behind_DEBUG>, never removed.
83
- dlog(`Drawer: animateProgressTo(open=${open}) starting at t=${Date.now()}`);
84
- Animated.timing(progress, {
85
- toValue: open ? 1 : 0,
86
- duration: DRAWER_SNAP_DURATION,
87
- // Native-driver wiring (the AnimatedComponent passthrough opt-in ADR 0017 defines) is
88
- // deferred for v1 - see this file's header feasibility note. The JS timing loop still
89
- // drives every frame, same as any other non-native-driven Animated.timing in this codebase.
90
- useNativeDriver: false,
91
- }).start();
92
- }, [progress]);
93
- const handle = useMemo(() => ({
94
- openDrawer: () => {
95
- dlog(`Drawer: openDrawer() called, isOpen=${isOpenRef.current} at t=${Date.now()}`);
96
- animateProgressTo(true);
97
- dispatch({ type: 'openDrawer' });
98
- },
99
- closeDrawer: () => {
100
- dlog(`Drawer: closeDrawer() called, isOpen=${isOpenRef.current} at t=${Date.now()}`);
101
- animateProgressTo(false);
102
- dispatch({ type: 'closeDrawer' });
103
- },
104
- toggleDrawer: () => {
105
- dlog(`Drawer: toggleDrawer() called, isOpen=${isOpenRef.current} at t=${Date.now()}`);
106
- animateProgressTo(!isOpenRef.current);
107
- dispatch({ type: 'toggleDrawer' });
108
- },
109
- jumpTo: (name) => {
110
- dispatch({ type: 'jumpTo', name });
111
- if (isOpenRef.current)
112
- animateProgressTo(false);
113
- },
114
- }), [animateProgressTo]);
115
- useImperativeHandle(forwardedRef, () => handle, [handle]);
116
- const panResponder = useRef(PanResponder.create({
117
- onStartShouldSetPanResponder: (event, gestureState) => shouldClaimDrawerSwipe(event, gestureState, screenWidthRef.current, isOpenRef.current, optionsRef.current, 'start'),
118
- onMoveShouldSetPanResponder: (event, gestureState) => shouldClaimDrawerSwipe(event, gestureState, screenWidthRef.current, isOpenRef.current, optionsRef.current, 'move'),
119
- onPanResponderGrant: () => {
120
- dlog('Drawer: gesture grant');
121
- dragStartProgress.current = isOpenRef.current ? 1 : 0;
122
- },
123
- onPanResponderMove: (_event, gestureState) => {
124
- const nextProgress = resolveDragProgress(gestureState, dragStartProgress.current, optionsRef.current);
125
- progress.setValue(nextProgress);
126
- },
127
- onPanResponderRelease: (_event, gestureState) => {
128
- const intent = resolveSwipeIntent(gestureState, isOpenRef.current, optionsRef.current);
129
- const open = intent === 'open';
130
- dlog(`Drawer: gesture release -> ${open ? 'open' : 'close'}`);
131
- animateProgressTo(open);
132
- dispatch(open ? { type: 'openDrawer' } : { type: 'closeDrawer' });
133
- },
134
- onPanResponderTerminate: () => {
135
- dlog('Drawer: gesture terminated, snapping back');
136
- animateProgressTo(isOpenRef.current);
137
- },
138
- })).current;
139
- const animated = isDrawerAnimated(options);
140
- const geometry = useMemo(() => resolveDrawerGeometry(options), [options.drawerType, options.drawerPosition, options.drawerWidth]);
141
- const panelSlot = animated ? resolveDrawerSlotInterpolation(geometry, 'panel') : undefined;
142
- const contentSlot = animated ? resolveDrawerSlotInterpolation(geometry, 'content') : undefined;
143
- const overlaySlot = animated ? resolveDrawerSlotInterpolation(geometry, 'overlay') : undefined;
144
- const panelTranslateX = panelSlot ? progress.interpolate(panelSlot.translateX) : undefined;
145
- const contentTranslateX = contentSlot ? progress.interpolate(contentSlot.translateX) : undefined;
146
- const overlayOpacity = overlaySlot ? progress.interpolate(overlaySlot.opacity) : undefined;
147
- // The overlay is a full-screen absolutely-positioned sibling BELOW content in paint order
148
- // (see render-drawer.ts's drawerChildOrder) - for 'front' that's fine since content never moves,
149
- // but for 'slide' content itself translates away by contentTranslateX, and without following it
150
- // the overlay stays pinned full-screen, dimming (and touch-capturing) the now-revealed panel
151
- // underneath instead of just the content sliver it's meant to dim. Tying overlay to the SAME
152
- // translateX as content keeps it registered exactly under content, wherever content actually is
153
- // (resolveDrawerSlotInterpolation's 'overlay' overload returns that same content delta).
154
- const overlayTranslateX = overlaySlot ? progress.interpolate(overlaySlot.translateX) : undefined;
155
- const focusedRoute = state.routes[state.index];
156
- const focusedEntry = focusedRoute ? registry.get(focusedRoute.name) : undefined;
157
- if (focusedRoute && !focusedEntry) {
158
- dlog(`Drawer: no screen registered for route name "${focusedRoute.name}"`);
159
- }
160
- // Only the focused route's screen is ever mounted (like Tab, unlike Stack which keeps every
161
- // pushed route alive), so a fresh emitter per focus change is sufficient - see tabs.ts's
162
- // matching comment for why no per-route emitter map is needed. Keyed on the route KEY, not the
163
- // route object, so a jumpTo-with-no-params re-focus of the ALREADY-focused route (a no-op in
164
- // drawerRouterReducer) and any future params merge don't spuriously re-fire focus/blur.
165
- const focusedRouteKey = focusedRoute?.key;
166
- const routeEmitter = useMemo(() => createNavigationEmitter(), [focusedRouteKey]);
167
- // Drawer paints its own panel in pure JS - there is no native onAppear/onDisappear to hook
168
- // (unlike Stack's RNSScreen), so focus/blur is synthesized here: mount = focus, cleanup = blur,
169
- // exactly what an effect keyed on focusedRouteKey already encodes - no diffFocusedRoute
170
- // indirection needed (unlike Vue/Angular, which diff real prev/next keys inside an imperative
171
- // watch/CD callback that has no mount/cleanup pairing of its own).
172
- useEffect(() => {
173
- if (focusedRouteKey === undefined)
174
- return undefined;
175
- dlog(`Drawer: route "${focusedRoute?.name}" focused`);
176
- routeEmitter.emit(NAVIGATION_EVENT_FOCUS);
177
- return () => {
178
- dlog(`Drawer: route "${focusedRoute?.name}" blurred`);
179
- routeEmitter.emit(NAVIGATION_EVENT_BLUR);
180
- };
181
- // focusedRoute omitted deliberately: only its .key (tracked via focusedRouteKey) should
182
- // re-run this effect - see the comment above on focusedRouteKey.
183
- // eslint-disable-next-line react-hooks/exhaustive-deps
184
- }, [routeEmitter, focusedRouteKey]);
185
- const content = focusedEntry && focusedRoute
186
- ? createElement(NavigationContext.Provider, {
187
- value: {
188
- route: focusedRoute,
189
- navigation: handle,
190
- emitter: routeEmitter,
191
- parent: ambientContext,
192
- },
193
- }, createElement(focusedEntry.component))
194
- : null;
195
- const descriptors = {};
196
- for (const route of state.routes) {
197
- const entry = registry.get(route.name);
198
- if (entry === undefined)
199
- continue;
200
- descriptors[route.key] = {
201
- options: resolveDrawerScreenOptions(entry, { route, navigation: handle }, screenOptions),
202
- navigation: handle,
203
- };
204
- }
205
- const drawerContent = renderDrawerContent?.({ state, descriptors, navigation: handle }) ?? null;
206
- const root = renderDrawer({
207
- overlayColor: overlayColor ?? DRAWER_DEFAULT_OVERLAY_COLOR,
208
- drawerStyle,
209
- contentPassthrough: {},
210
- overlayPassthrough: animated
211
- ? {
212
- pointerEvents: state.isOpen ? 'auto' : 'none',
213
- onStartShouldSetResponder: () => true,
214
- onResponderRelease: () => {
215
- animateProgressTo(false);
216
- dispatch({ type: 'closeDrawer' });
217
- },
218
- }
219
- : {},
220
- panelPassthrough: {},
221
- }, options);
222
- const order = drawerChildOrder(options);
223
- const slots = new Map();
224
- order.forEach((slot, index) => {
225
- const descriptor = root.children[index];
226
- if (typeof descriptor !== 'string')
227
- slots.set(slot, descriptor);
228
- });
229
- const slotChildren = {
230
- content,
231
- overlay: null,
232
- panel: drawerContent,
233
- };
234
- // Each animated style holds an AnimatedInterpolation node, not a plain number/color - it feeds
235
- // only Animated.View's deliberately permissive `style?: unknown` (see create-animated-component.tsx),
236
- // never the plain-IViewStyle branch below, so this stays untyped rather than widening IViewStyle.
237
- const slotAnimatedStyle = {
238
- content: contentTranslateX === undefined
239
- ? undefined
240
- : { transform: [{ translateX: contentTranslateX }] },
241
- overlay: overlayOpacity === undefined
242
- ? undefined
243
- : { opacity: overlayOpacity, transform: [{ translateX: overlayTranslateX }] },
244
- panel: panelTranslateX === undefined ? undefined : { transform: [{ translateX: panelTranslateX }] },
245
- };
246
- function renderSlot(slot) {
247
- const descriptor = slots.get(slot);
248
- if (descriptor === undefined)
249
- return null;
250
- const animatedStyle = slotAnimatedStyle[slot];
251
- if (animatedStyle === undefined) {
252
- return createElement(descriptor.type, { key: descriptor.key, ...descriptor.props }, slotChildren[slot]);
253
- }
254
- const style = [descriptor.props.style, animatedStyle];
255
- return createElement(Animated.View, { key: descriptor.key, ...descriptor.props, style }, slotChildren[slot]);
256
- }
257
- const drawerChildren = order
258
- .map(renderSlot)
259
- .filter((element) => element !== null);
260
- return createElement('symbiote-view', { style: root.props.style, ...panResponder.panHandlers }, ...drawerChildren);
261
- });
262
- export const Drawer = Object.assign(DrawerImpl, { Screen: DrawerScreen });
263
- // --- Explicit gap list vs the real react-native-gesture-handler + react-native-reanimated
264
- // @react-navigation/drawer (confirmed against its current docs) ---
265
- // 1. `configureGestureHandler` - a raw react-native-gesture-handler `Gesture` object escape
266
- // hatch. No PanResponder equivalent exists; not ported.
267
- // 2. Simultaneous/failure gesture RELATIONSHIPS (gesture-handler's declarative composition vs a
268
- // nested ScrollView, another PanResponder, etc.) - PanResponder only offers negotiation via the
269
- // should-set boolean gates used here (edge-start + dominant-axis), which is more prone to an
270
- // accidental hijack of a nested horizontal ScrollView/Swiper than gesture-handler's system.
271
- // 3. `useDrawerProgress` - a Reanimated SharedValue read on the UI thread. `progress` here is a
272
- // JS-thread AnimatedValue; interpolating it for consumer-facing content animation works, but
273
- // without native-driver wiring (gap noted above) it does not carry the same synchronous
274
- // UI-thread guarantee under JS-thread load.
275
- // 4. `hideStatusBarOnOpen` / `keyboardDismissMode` / `statusBarAnimation` / `overlayStyle` - not
276
- // wired in this pass; straightforward additions once StatusBar/Keyboard module wiring is
277
- // needed here (not a PanResponder/Animated limitation, just unscoped for v1).