@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,171 +0,0 @@
1
- // Linking config: the piece @react-navigation's `NavigationContainer`'s `linking` prop provides
2
- // on top of the framework-agnostic `Linking` module (core/engine/src/linking) - resolving a URL
3
- // ('myapp://user/42', '/user/42') to a route, and a route back to a URL. This is OUR OWN type,
4
- // not imported from react-navigation, kept close to its shape (`prefixes` + `config.screens`)
5
- // only so the DX is familiar.
6
- //
7
- // Why hand-rolled matching instead of react-router's `matchPath`/`generatePath` (evaluated per
8
- // task): react-router 8's package has no subpath export for the pure `lib/router/utils.ts`
9
- // matcher - its only general entry point (`.`) eagerly imports `./lib/dom/*`
10
- // (BrowserRouter/ScrollRestoration/cookies) and `./lib/server-runtime/*`, code that assumes DOM
11
- // and Node globals a Metro/Hermes RN bundle doesn't have; its `peerDependencies` also pin
12
- // `react-dom`, which this DOM-free monorepo never installs (see CLAUDE.md
13
- // react_native_is_an_explicit_top_level_peer - no foreign runtime singleton gets smuggled in).
14
- // On top of the bundling risk, `matchPath`'s/`generatePath`'s param-name extraction is a
15
- // template-literal-type trick that only fires for a compile-time LITERAL path string; our
16
- // patterns come from a runtime config object (`Path` widens to plain `string`), so the generic
17
- // degrades to an empty params type and using the result would force an `as` cast - forbidden by
18
- // this project's TS conventions. A minimal `:param`-segment matcher sidesteps both problems and
19
- // is the right size for a single flat Stack (no optional/splat segments - no nested-navigator use
20
- // for them yet).
21
- import { dlog } from '@symbiote-native/engine';
22
- import { isRecord } from '../guards.js';
23
- // Strips leading/trailing slashes so segments join predictably regardless of how the caller
24
- // wrote a path ('feed/:sort', '/feed/:sort/', etc).
25
- function normalizeSegment(segment) {
26
- return segment.replace(/^\/+|\/+$/g, '');
27
- }
28
- function joinPath(parent, child) {
29
- const parts = [normalizeSegment(parent), normalizeSegment(child)].filter(part => part.length > 0);
30
- return parts.join('/');
31
- }
32
- // Walks `screens` recursively, accumulating the path down to each LEAF screen. Unlike
33
- // react-navigation (which resolves nested config into nested navigation state), our navigator is
34
- // a single flat Stack - a `screens` group with no `path` of its own is transparent (consumes no
35
- // URL segment); a group WITH a `path` but no nested `screens` is itself a leaf using that path.
36
- function flattenScreens(screens, parentPath, out) {
37
- for (const [name, entry] of Object.entries(screens)) {
38
- if (typeof entry === 'string') {
39
- out.push({ name, pattern: joinPath(parentPath, entry) });
40
- continue;
41
- }
42
- const nextParent = entry.path !== undefined ? joinPath(parentPath, entry.path) : parentPath;
43
- if (entry.screens !== undefined) {
44
- flattenScreens(entry.screens, nextParent, out);
45
- }
46
- else if (entry.path !== undefined) {
47
- out.push({ name, pattern: nextParent });
48
- }
49
- else {
50
- dlog(`linking-config: screen "${name}" has neither a path nor nested screens, skipping`);
51
- }
52
- }
53
- return out;
54
- }
55
- function dynamicSegmentCount(pattern) {
56
- if (pattern.length === 0)
57
- return 0;
58
- return pattern.split('/').filter(segment => segment.startsWith(':')).length;
59
- }
60
- function segmentCount(pattern) {
61
- return pattern.length === 0 ? 0 : pattern.split('/').length;
62
- }
63
- // More static segments should win over a param that would also match (e.g. a literal
64
- // '/user/me' pattern over '/user/:id'), mirroring how react-navigation prefers exact matches.
65
- function bySpecificity(a, b) {
66
- const dynamicDiff = dynamicSegmentCount(a.pattern) - dynamicSegmentCount(b.pattern);
67
- if (dynamicDiff !== 0)
68
- return dynamicDiff;
69
- return segmentCount(b.pattern) - segmentCount(a.pattern);
70
- }
71
- function matchPattern(pattern, pathname) {
72
- const patternSegments = pattern.length === 0 ? [] : pattern.split('/');
73
- const pathSegments = pathname.length === 0 ? [] : pathname.split('/');
74
- if (patternSegments.length !== pathSegments.length)
75
- return null;
76
- const params = {};
77
- for (let index = 0; index < patternSegments.length; index += 1) {
78
- const patternSegment = patternSegments[index];
79
- const pathSegment = pathSegments[index];
80
- if (patternSegment.startsWith(':')) {
81
- params[patternSegment.slice(1)] = decodeURIComponent(pathSegment);
82
- }
83
- else if (patternSegment !== pathSegment) {
84
- return null;
85
- }
86
- }
87
- return params;
88
- }
89
- function stripQueryAndHash(url) {
90
- const match = /[?#]/.exec(url);
91
- return match === null ? url : url.slice(0, match.index);
92
- }
93
- // Resolves a full URL down to the bare path segment string ('user/42', no leading slash) by
94
- // stripping the longest matching configured prefix, falling back to treating the URL as an
95
- // already-bare path when it starts with '/' (the task's second example form). Returns null when
96
- // neither applies - an unrecognized scheme/host, not "no route matched".
97
- function extractPathname(config, url) {
98
- const withoutQuery = stripQueryAndHash(url);
99
- const sortedPrefixes = [...config.prefixes].sort((a, b) => b.length - a.length);
100
- for (const prefix of sortedPrefixes) {
101
- if (withoutQuery.startsWith(prefix)) {
102
- return normalizeSegment(withoutQuery.slice(prefix.length));
103
- }
104
- }
105
- if (withoutQuery.startsWith('/'))
106
- return normalizeSegment(withoutQuery);
107
- return null;
108
- }
109
- export function resolveRouteFromUrl(config, url) {
110
- const pathname = extractPathname(config, url);
111
- if (pathname === null) {
112
- dlog(`linking-config: url "${url}" matched no configured prefix`);
113
- return null;
114
- }
115
- const candidates = flattenScreens(config.config.screens, '', []).sort(bySpecificity);
116
- for (const candidate of candidates) {
117
- const params = matchPattern(candidate.pattern, pathname);
118
- if (params !== null) {
119
- dlog(`linking-config: resolved "${url}" -> ${candidate.name}`);
120
- // `key` is not real route identity here - the navigator mints its own on push/replace
121
- // (stack.ts's createRoute); `name` is a stable enough placeholder to satisfy IRoute's shape.
122
- return {
123
- key: candidate.name,
124
- name: candidate.name,
125
- params: Object.keys(params).length > 0 ? params : undefined,
126
- };
127
- }
128
- }
129
- dlog(`linking-config: url "${url}" (path "${pathname}") matched no configured screen`);
130
- return null;
131
- }
132
- // The inverse of matchPattern: fills a pattern's ':param' segments from `params`, or returns
133
- // null when a required param is missing (mirrors react-router's generatePath throwing, minus the
134
- // throw - a resolver returning null reads more naturally at the call site here).
135
- function fillPattern(pattern, params) {
136
- if (pattern.length === 0)
137
- return '';
138
- const source = isRecord(params) ? params : {};
139
- const segments = [];
140
- for (const segment of pattern.split('/')) {
141
- if (!segment.startsWith(':')) {
142
- segments.push(segment);
143
- continue;
144
- }
145
- const value = source[segment.slice(1)];
146
- if (value === undefined || value === null)
147
- return null;
148
- segments.push(encodeURIComponent(String(value)));
149
- }
150
- return segments.join('/');
151
- }
152
- export function resolveUrlFromRoute(config, route) {
153
- const candidate = flattenScreens(config.config.screens, '', []).find(entry => entry.name === route.name);
154
- if (candidate === undefined) {
155
- dlog(`linking-config: no configured screen for route name "${route.name}"`);
156
- return null;
157
- }
158
- const path = fillPattern(candidate.pattern, route.params);
159
- if (path === null) {
160
- dlog(`linking-config: route "${route.name}" is missing a param required by "${candidate.pattern}"`);
161
- return null;
162
- }
163
- const prefix = config.prefixes[0];
164
- if (prefix === undefined)
165
- return path.length > 0 ? `/${path}` : '/';
166
- // A prefix already carrying its own trailing separator ('myapp://') must not be truncated to
167
- // 'myapp:/' by naively slicing one char off - only add a '/' when the prefix doesn't have one.
168
- if (path.length === 0)
169
- return prefix;
170
- return prefix.endsWith('/') ? `${prefix}${path}` : `${prefix}/${path}`;
171
- }
@@ -1,16 +0,0 @@
1
- export declare const NAVIGATION_EVENT_FOCUS = "focus";
2
- export declare const NAVIGATION_EVENT_BLUR = "blur";
3
- export declare const NAVIGATION_EVENT_STATE = "state";
4
- export declare const NAVIGATION_EVENT_BEFORE_REMOVE = "beforeRemove";
5
- export type INavigationEventName = typeof NAVIGATION_EVENT_FOCUS | typeof NAVIGATION_EVENT_BLUR | typeof NAVIGATION_EVENT_STATE | typeof NAVIGATION_EVENT_BEFORE_REMOVE;
6
- export type INavigationEventListener<TData = unknown> = (data: TData) => void;
7
- export type INavigationEmitter = {
8
- emit: (event: INavigationEventName, data?: unknown) => void;
9
- addListener: (event: INavigationEventName, listener: INavigationEventListener) => () => void;
10
- };
11
- export declare function createNavigationEmitter(): INavigationEmitter;
12
- export type IFocusTransition = {
13
- blurKey?: string;
14
- focusKey?: string;
15
- };
16
- export declare function diffFocusedRoute(prevKey: string | undefined, nextKey: string | undefined): IFocusTransition;
@@ -1,57 +0,0 @@
1
- // The framework-agnostic half of what @react-navigation exposes as `navigation.addListener` /
2
- // `useFocusEffect` / `useIsFocused` / `useNavigationState`: a plain pub/sub emitter, zero React.
3
- // addListener is NOT React-specific there either - React's hooks are thin useEffect wrappers
4
- // around exactly this subscribe/unsubscribe shape (confirmed against the 8.x docs: `const
5
- // unsubscribe = navigation.addListener('focus', cb); return unsubscribe;` inside a bare
6
- // `useEffect`). One emitter per route (created by the adapter, e.g. stack.ts's per-route
7
- // `emitters` map) carries that route's own focus/blur/state lifecycle; every adapter's lifecycle
8
- // hooks subscribe to it, matching this file's single responsibility split from `constants.ts`
9
- // (which only names Fabric view/prop-key strings, not this JS-only event system).
10
- //
11
- // diffFocusedRoute below centralizes the "which route lost focus, which gained it" comparison
12
- // that tabs.ts and drawer.ts independently reimplemented in all three adapters (each watching its
13
- // own router state for the focused index/key changing, then manually emitting BLUR to the old key
14
- // and FOCUS to the new one, plus an initial FOCUS seed on first mount) - the diff is pure and
15
- // identical everywhere; only WHEN to run it differs per framework (a `useEffect` dependency array,
16
- // a Vue `watch`, an idempotent-per-read Angular method), so that half stays adapter-owned.
17
- import { dlog } from '@symbiote-native/engine';
18
- export const NAVIGATION_EVENT_FOCUS = 'focus';
19
- export const NAVIGATION_EVENT_BLUR = 'blur';
20
- export const NAVIGATION_EVENT_STATE = 'state';
21
- export const NAVIGATION_EVENT_BEFORE_REMOVE = 'beforeRemove';
22
- export function createNavigationEmitter() {
23
- const listenersByEvent = new Map();
24
- function addListener(event, listener) {
25
- let listeners = listenersByEvent.get(event);
26
- if (!listeners) {
27
- listeners = new Set();
28
- listenersByEvent.set(event, listeners);
29
- }
30
- listeners.add(listener);
31
- return () => {
32
- listeners?.delete(listener);
33
- };
34
- }
35
- function emit(event, data) {
36
- const listeners = listenersByEvent.get(event);
37
- // Investigation instrumentation (flicker-on-focus bug): every emission, whether or not a
38
- // listener is attached, so the log stream shows if focus/blur ever fires with 0 subscribers
39
- // (a silent no-op) vs racing an actual effect. Kept behind DEBUG, never removed.
40
- dlog(`Navigation emitter: emit "${event}" (${listeners?.size ?? 0} listener(s)) at t=${Date.now()}`);
41
- if (!listeners)
42
- return;
43
- for (const listener of listeners)
44
- listener(data);
45
- }
46
- return { emit, addListener };
47
- }
48
- export function diffFocusedRoute(prevKey, nextKey) {
49
- if (prevKey === nextKey)
50
- return {};
51
- const transition = {};
52
- if (prevKey !== undefined)
53
- transition.blurKey = prevKey;
54
- if (nextKey !== undefined)
55
- transition.focusKey = nextKey;
56
- return transition;
57
- }
@@ -1,30 +0,0 @@
1
- import type { INavigatorState } from '../navigator-state';
2
- import type { IDrawerScreenOptions } from '../drawer-options';
3
- export type INavigatorHandle = {
4
- push: (name: string, params?: unknown) => void;
5
- pop: (count?: number) => void;
6
- popToTop: () => void;
7
- popTo: (key: string) => void;
8
- replace: (name: string, params?: unknown) => void;
9
- setParams: (params: unknown, key?: string) => void;
10
- reset: (state: INavigatorState) => void;
11
- canGoBack: () => boolean;
12
- };
13
- export type ITabNavigatorHandle = {
14
- jumpTo: (name: string, params?: unknown) => void;
15
- setParams: (params: unknown, key: string) => void;
16
- };
17
- export type IDrawerNavigatorHandle = {
18
- openDrawer: () => void;
19
- closeDrawer: () => void;
20
- toggleDrawer: () => void;
21
- jumpTo: (name: string) => void;
22
- };
23
- export type IDrawerDescriptorMap = Record<string, {
24
- options: IDrawerScreenOptions;
25
- navigation: IDrawerNavigatorHandle;
26
- }>;
27
- export type IAnyNavigatorHandle = INavigatorHandle | ITabNavigatorHandle | IDrawerNavigatorHandle;
28
- export declare function isStackNavigatorHandle(handle: IAnyNavigatorHandle): handle is INavigatorHandle;
29
- export declare function isDrawerNavigatorHandle(handle: IAnyNavigatorHandle): handle is IDrawerNavigatorHandle;
30
- export declare function isTabNavigatorHandle(handle: IAnyNavigatorHandle): handle is ITabNavigatorHandle;
@@ -1,15 +0,0 @@
1
- // Imperative navigator handles: framework-agnostic (scalars, `unknown`, plain function
2
- // signatures over `INavigatorState`/`IDrawerScreenOptions` - no framework element, ref, or
3
- // children in any field), so each shape is declared once here and every adapter re-exports it
4
- // verbatim (see CLAUDE.md <prop_types_split_agnostic_vs_per_adapter>).
5
- export function isStackNavigatorHandle(handle) {
6
- return 'push' in handle;
7
- }
8
- export function isDrawerNavigatorHandle(handle) {
9
- return 'openDrawer' in handle;
10
- }
11
- // jumpTo exists on both Tab and Drawer handles, so a Tab handle is a jumpTo-having handle that
12
- // isn't a Drawer handle (Drawer's own openDrawer/closeDrawer/toggleDrawer rule it out).
13
- export function isTabNavigatorHandle(handle) {
14
- return 'jumpTo' in handle && !isDrawerNavigatorHandle(handle);
15
- }
@@ -1,216 +0,0 @@
1
- import type { IColorValue, ISymbioteEvent } from '@symbiote-native/engine';
2
- import type { IImageSourceProp } from '@symbiote-native/components';
3
- export type IStackAnimation = 'default' | 'flip' | 'simple_push' | 'none' | 'fade' | 'slide_from_right' | 'slide_from_left' | 'slide_from_bottom' | 'fade_from_bottom' | 'ios_from_right' | 'ios_from_left';
4
- export type IStackPresentation = 'push' | 'modal' | 'transparentModal' | 'fullScreenModal' | 'formSheet' | 'pageSheet' | 'containedModal' | 'containedTransparentModal';
5
- export type IHeaderBackButtonDisplayMode = 'minimal' | 'default' | 'generic';
6
- export type IHeaderUserInterfaceStyle = 'unspecified' | 'light' | 'dark';
7
- export type IOptionalBooleanNativeProp = 'undefined' | 'false' | 'true';
8
- export type ISheetAllowedDetents = number[] | 'fitToContents' | 'large' | 'medium' | 'all';
9
- export type ISheetLargestUndimmedDetent = number | 'last' | 'none' | 'all' | 'large' | 'medium';
10
- export type ISheetInitialDetent = number | 'last';
11
- export type IHeaderBarButtonIcon = {
12
- type: 'sfSymbol';
13
- name: string;
14
- } | {
15
- type: 'xcasset';
16
- name: string;
17
- } | {
18
- type: 'imageSource';
19
- imageSource: IImageSourceProp;
20
- } | {
21
- type: 'templateSource';
22
- templateSource: IImageSourceProp;
23
- };
24
- export type IHeaderBarButtonItemBadge = {
25
- value: string;
26
- style?: {
27
- color?: IColorValue;
28
- backgroundColor?: IColorValue;
29
- fontFamily?: string;
30
- fontSize?: number;
31
- fontWeight?: string;
32
- };
33
- };
34
- type ISharedHeaderBarButtonItem = {
35
- index?: number;
36
- title?: string;
37
- titleStyle?: {
38
- fontFamily?: string;
39
- fontSize?: number;
40
- fontWeight?: string;
41
- color?: IColorValue;
42
- };
43
- icon?: IHeaderBarButtonIcon;
44
- variant?: 'plain' | 'done' | 'prominent';
45
- tintColor?: IColorValue;
46
- disabled?: boolean;
47
- width?: number;
48
- hidesSharedBackground?: boolean;
49
- sharesBackground?: boolean;
50
- identifier?: string;
51
- badge?: IHeaderBarButtonItemBadge;
52
- accessibilityLabel?: string;
53
- accessibilityHint?: string;
54
- };
55
- export type IHeaderBarButtonItemAction = ISharedHeaderBarButtonItem & {
56
- type: 'button';
57
- onPress: () => void;
58
- selected?: boolean;
59
- };
60
- export type IHeaderBarButtonMenuAction = {
61
- type: 'action';
62
- title?: string;
63
- subtitle?: string;
64
- onPress: () => void;
65
- icon?: IHeaderBarButtonIcon;
66
- state?: 'on' | 'off' | 'mixed';
67
- disabled?: boolean;
68
- destructive?: boolean;
69
- hidden?: boolean;
70
- keepsMenuPresented?: boolean;
71
- discoverabilityLabel?: string;
72
- };
73
- export type IHeaderBarButtonSubmenu = {
74
- type: 'submenu';
75
- title?: string;
76
- icon?: IHeaderBarButtonIcon;
77
- items: (IHeaderBarButtonMenuAction | IHeaderBarButtonSubmenu)[];
78
- displayInline?: boolean;
79
- destructive?: boolean;
80
- singleSelection?: boolean;
81
- displayAsPalette?: boolean;
82
- };
83
- export type IHeaderBarButtonItemMenu = ISharedHeaderBarButtonItem & {
84
- type: 'menu';
85
- menu: {
86
- title?: string;
87
- items: (IHeaderBarButtonMenuAction | IHeaderBarButtonSubmenu)[];
88
- singleSelection?: boolean;
89
- displayAsPalette?: boolean;
90
- };
91
- changesSelectionAsPrimaryAction?: boolean;
92
- };
93
- export type IHeaderBarButtonItemSpacing = {
94
- type: 'spacing';
95
- spacing: number;
96
- };
97
- export type IHeaderBarButtonItem = IHeaderBarButtonItemAction | IHeaderBarButtonItemMenu | IHeaderBarButtonItemSpacing;
98
- export type ISearchBarAutoCapitalize = 'systemDefault' | 'none' | 'words' | 'sentences' | 'characters';
99
- export type ISearchBarPlacement = 'automatic' | 'inline' | 'stacked' | 'integrated' | 'integratedButton' | 'integratedCentered';
100
- export type ISearchBarOptions = {
101
- placeholder?: string;
102
- autoCapitalize?: ISearchBarAutoCapitalize;
103
- placement?: ISearchBarPlacement;
104
- hideWhenScrolling?: boolean;
105
- allowToolbarIntegration?: boolean;
106
- obscureBackground?: boolean;
107
- hideNavigationBar?: boolean;
108
- cancelButtonText?: string;
109
- barTintColor?: IColorValue;
110
- tintColor?: IColorValue;
111
- textColor?: IColorValue;
112
- autoFocus?: boolean;
113
- disableBackButtonOverride?: boolean;
114
- inputType?: string;
115
- hintTextColor?: IColorValue;
116
- headerIconColor?: IColorValue;
117
- shouldShowHintSearchIcon?: boolean;
118
- onChangeText?: (text: string) => void;
119
- onFocus?: () => void;
120
- onBlur?: () => void;
121
- onCancelButtonPress?: () => void;
122
- onSearchButtonPress?: (text: string) => void;
123
- onClose?: () => void;
124
- onOpen?: () => void;
125
- };
126
- export type IScreenOrientation = 'default' | 'all' | 'portrait' | 'portrait_up' | 'portrait_down' | 'landscape' | 'landscape_left' | 'landscape_right';
127
- export interface IScreenOptions {
128
- title?: string;
129
- headerShown?: boolean;
130
- headerTitleColor?: IColorValue;
131
- headerStyle?: {
132
- backgroundColor?: IColorValue;
133
- };
134
- headerTintColor?: IColorValue;
135
- headerBackTitle?: string;
136
- headerBackButtonDisplayMode?: IHeaderBackButtonDisplayMode;
137
- headerLargeTitle?: boolean;
138
- headerLargeStyle?: {
139
- backgroundColor?: IColorValue;
140
- };
141
- headerUserInterfaceStyle?: IHeaderUserInterfaceStyle;
142
- headerTranslucent?: boolean;
143
- headerLeftBarButtonItems?: IHeaderBarButtonItem[];
144
- headerRightBarButtonItems?: IHeaderBarButtonItem[];
145
- headerSearchBarOptions?: ISearchBarOptions;
146
- gestureEnabled?: boolean;
147
- stackAnimation?: IStackAnimation;
148
- stackPresentation?: IStackPresentation;
149
- transitionDuration?: number;
150
- sheetAllowedDetents?: ISheetAllowedDetents;
151
- sheetLargestUndimmedDetentIndex?: ISheetLargestUndimmedDetent;
152
- sheetInitialDetentIndex?: ISheetInitialDetent;
153
- sheetGrabberVisible?: boolean;
154
- sheetCornerRadius?: number;
155
- sheetExpandsWhenScrolledToEdge?: boolean;
156
- sheetElevation?: number;
157
- sheetShouldOverflowTopInset?: boolean;
158
- sheetDefaultResizeAnimationEnabled?: boolean;
159
- statusBarStyle?: 'inverted' | 'auto' | 'light' | 'dark';
160
- statusBarHidden?: boolean;
161
- statusBarAnimation?: 'none' | 'fade' | 'slide';
162
- screenOrientation?: IScreenOrientation;
163
- }
164
- export type INavigatorPlatform = {
165
- defaultHeaderBackTitleVisible: boolean;
166
- };
167
- export type IScreenViewProps = {
168
- screenId: string;
169
- activityState: number;
170
- gestureEnabled?: boolean;
171
- stackAnimation?: IStackAnimation;
172
- stackPresentation?: IStackPresentation;
173
- transitionDuration?: number;
174
- sheetAllowedDetents?: number[];
175
- sheetLargestUndimmedDetent?: number;
176
- sheetInitialDetent?: number;
177
- sheetGrabberVisible?: boolean;
178
- sheetCornerRadius?: number;
179
- sheetExpandsWhenScrolledToEdge?: boolean;
180
- sheetElevation?: number;
181
- sheetShouldOverflowTopInset?: boolean;
182
- sheetDefaultResizeAnimationEnabled?: boolean;
183
- statusBarStyle?: 'inverted' | 'auto' | 'light' | 'dark';
184
- statusBarHidden?: boolean;
185
- statusBarAnimation?: 'none' | 'fade' | 'slide';
186
- screenOrientation?: IScreenOrientation;
187
- passthrough: Record<string, unknown>;
188
- };
189
- export type IHeaderConfigViewProps = {
190
- title?: string;
191
- hidden: boolean;
192
- backTitle?: string;
193
- backTitleVisible: boolean;
194
- backButtonDisplayMode?: IHeaderBackButtonDisplayMode;
195
- largeTitle?: boolean;
196
- translucent?: boolean;
197
- color?: IColorValue;
198
- titleColor?: IColorValue;
199
- backgroundColor?: IColorValue;
200
- largeTitleBackgroundColor?: IColorValue;
201
- userInterfaceStyle?: IHeaderUserInterfaceStyle;
202
- headerLeftBarButtonItems?: unknown[];
203
- headerRightBarButtonItems?: unknown[];
204
- onPressHeaderBarButtonItem?: (event: ISymbioteEvent) => void;
205
- onPressHeaderBarButtonMenuItem?: (event: ISymbioteEvent) => void;
206
- passthrough: Record<string, unknown>;
207
- };
208
- export type IStackViewProps = {
209
- passthrough: Record<string, unknown>;
210
- };
211
- export type ISearchBarViewProps = Omit<ISearchBarOptions, 'obscureBackground' | 'hideNavigationBar' | 'onChangeText' | 'onFocus' | 'onBlur' | 'onCancelButtonPress' | 'onSearchButtonPress' | 'onClose' | 'onOpen'> & {
212
- obscureBackground?: IOptionalBooleanNativeProp;
213
- hideNavigationBar?: IOptionalBooleanNativeProp;
214
- passthrough: Record<string, unknown>;
215
- };
216
- export {};
@@ -1,7 +0,0 @@
1
- // Navigator prop types. Per CLAUDE.md <prop_types_split_agnostic_vs_per_adapter>: screen
2
- // options (title, header styling, transition/presentation) are entirely scalar/color-typed, so
3
- // they live here as the shared agnostic base every adapter re-exports. The Stack/Screen PUBLIC
4
- // component props are NOT here - they carry framework children (the screen content) and a
5
- // framework ref (the navigator handle), so each adapter declares its own flavored type over
6
- // this base, same split as IPressableProps.
7
- export {};
@@ -1,36 +0,0 @@
1
- export type IRoute<TParams = undefined> = Readonly<{
2
- key: string;
3
- name: string;
4
- params: TParams;
5
- }>;
6
- type IRouteEntry = IRoute<unknown>;
7
- export type INavigatorState = Readonly<{
8
- routes: readonly IRouteEntry[];
9
- }>;
10
- export type INavigatorAction = {
11
- type: 'push';
12
- route: IRouteEntry;
13
- } | {
14
- type: 'pop';
15
- count?: number;
16
- } | {
17
- type: 'popToTop';
18
- } | {
19
- type: 'popTo';
20
- key: string;
21
- } | {
22
- type: 'replace';
23
- route: IRouteEntry;
24
- } | {
25
- type: 'setParams';
26
- key?: string;
27
- params: unknown;
28
- } | {
29
- type: 'reset';
30
- state: INavigatorState;
31
- };
32
- export declare function createInitialNavigatorState(initialRoute: IRouteEntry): INavigatorState;
33
- export declare function navigatorReducer(state: INavigatorState, action: INavigatorAction): INavigatorState;
34
- export declare function computeActivityState(index: number, routesLength: number): number;
35
- export declare function isTopRoute(index: number, routesLength: number): boolean;
36
- export {};
@@ -1,81 +0,0 @@
1
- // Navigator: the logic half (framework-agnostic, zero render). A pure reducer over a route
2
- // stack - push/pop/popToTop/popTo/replace - mirroring what @react-navigation/native-stack keeps
3
- // in its router, minus the React-specific action-creator/linking machinery. Route identity
4
- // (`key`) is generated by the CALLER (the adapter, via React's useId or an equivalent) - the
5
- // reducer never invents identity itself, so it stays a pure (state, action) => state function.
6
- import { SCREEN_ACTIVITY_STATE_FOCUSED, SCREEN_ACTIVITY_STATE_INACTIVE } from '../constants.js';
7
- import { mergeParams } from '../guards.js';
8
- export function createInitialNavigatorState(initialRoute) {
9
- return { routes: [initialRoute] };
10
- }
11
- // The stack never pops its last route - mirrors the library refusing to pop the initial screen;
12
- // the caller (e.g. a hardware back handler) decides what a pop-at-root means for the host app.
13
- export function navigatorReducer(state, action) {
14
- switch (action.type) {
15
- case 'push':
16
- return { routes: [...state.routes, action.route] };
17
- case 'pop': {
18
- if (state.routes.length <= 1)
19
- return state;
20
- const count = action.count ?? 1;
21
- const nextLength = Math.max(1, state.routes.length - count);
22
- return { routes: state.routes.slice(0, nextLength) };
23
- }
24
- case 'popToTop':
25
- return state.routes.length <= 1 ? state : { routes: state.routes.slice(0, 1) };
26
- case 'popTo': {
27
- const index = state.routes.findIndex(route => route.key === action.key);
28
- if (index === -1)
29
- return state;
30
- return { routes: state.routes.slice(0, index + 1) };
31
- }
32
- case 'replace': {
33
- if (state.routes.length === 0)
34
- return { routes: [action.route] };
35
- return { routes: [...state.routes.slice(0, -1), action.route] };
36
- }
37
- // Mirrors @react-navigation's CommonActions.setParams: merges onto the route matched by
38
- // `key`, defaulting to the focused (top-of-stack) route when omitted - same shape as its
39
- // `source`-defaults-to-focused-route behavior, minus the cross-navigator target routing this
40
- // single-navigator reducer has no need for.
41
- case 'setParams': {
42
- const targetKey = action.key ?? state.routes[state.routes.length - 1]?.key;
43
- if (targetKey === undefined)
44
- return state;
45
- const index = state.routes.findIndex(route => route.key === targetKey);
46
- if (index === -1)
47
- return state;
48
- const target = state.routes[index];
49
- const nextRoute = {
50
- ...target,
51
- params: mergeParams(target.params, action.params),
52
- };
53
- return {
54
- routes: [...state.routes.slice(0, index), nextRoute, ...state.routes.slice(index + 1)],
55
- };
56
- }
57
- // Mirrors @react-navigation's CommonActions.reset / BaseRouter's RESET handler: the payload
58
- // state replaces the current one verbatim, no merge - full rehydration (e.g. restoring a
59
- // persisted stack) is just a reset with the deserialized state as the payload.
60
- case 'reset':
61
- return action.state;
62
- default:
63
- return state;
64
- }
65
- }
66
- // Mirrors @react-navigation/native-stack's actual algorithm (NativeStackView.native.tsx:
67
- // `activityState={isInactive ? 0 : 2}`, where isInactive = index > state.index) - NOT a
68
- // three-way top/one-below/rest split. A route index beyond the focused one only exists for a
69
- // preloaded-but-not-yet-navigated-to screen, a feature this reducer doesn't implement, so the
70
- // focused index is always routesLength - 1 (the last route) and every currently-mounted route is
71
- // FOCUSED. This is required, not just simpler: react-native-screens' native RNSScreen.mm asserts
72
- // an already-mounted NativeStack screen's activityState can never decrease via a plain prop
73
- // update ("activityState can only progress in NativeStack") - since no mounted route's index ever
74
- // exceeds routesLength - 1, this formula never produces a decrease across a push or pop.
75
- export function computeActivityState(index, routesLength) {
76
- const focusedIndex = routesLength - 1;
77
- return index > focusedIndex ? SCREEN_ACTIVITY_STATE_INACTIVE : SCREEN_ACTIVITY_STATE_FOCUSED;
78
- }
79
- export function isTopRoute(index, routesLength) {
80
- return index === routesLength - 1;
81
- }
@@ -1,13 +0,0 @@
1
- import { type IDescriptor } from '@symbiote-native/components';
2
- import type { IStyleProp, IViewStyle } from '@symbiote-native/engine';
3
- import { type IDrawerOptions } from './drawer-options';
4
- export type IDrawerSlot = 'content' | 'overlay' | 'panel';
5
- export declare function drawerChildOrder(options: IDrawerOptions): readonly IDrawerSlot[];
6
- export type IDrawerViewProps = {
7
- overlayColor?: string;
8
- drawerStyle?: IStyleProp<IViewStyle>;
9
- contentPassthrough: Record<string, unknown>;
10
- overlayPassthrough: Record<string, unknown>;
11
- panelPassthrough: Record<string, unknown>;
12
- };
13
- export declare function renderDrawer(view: IDrawerViewProps, options: IDrawerOptions): IDescriptor;