@symbiote-native/navigation 4.0.1 → 5.0.0

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.
package/README.md CHANGED
@@ -18,6 +18,10 @@ logic/view/lifecycle split every other SymbioteNative component follows.
18
18
  npm install @symbiote-native/navigation
19
19
  ```
20
20
 
21
+ Scaffolding or extending a SymbioteNative app? `npx @symbiote-native/cli new --navigation` (or
22
+ `add --navigation` in an existing app) installs and wires this for you — see
23
+ [`@symbiote-native/cli`](../cli).
24
+
21
25
  Only this package — never `react-native-screens` directly. `@symbiote-native/navigation` depends
22
26
  on it and ships as the sole autolinked native proxy (`react-native.config.cjs` +
23
27
  `symbiote-navigation.podspec`), the same one-dependency packaging as `@symbiote-native/slider` and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbiote-native/navigation",
3
- "version": "4.0.1",
3
+ "version": "5.0.0",
4
4
  "description": "Native stack navigator for SymbioteNative, built on react-native-screens' native view primitives (RNSScreen/RNSScreenStack) — one framework-agnostic core, reachable from the React, Vue, Svelte, Solid, and Angular adapters.",
5
5
  "keywords": [
6
6
  "react-native",
@@ -160,7 +160,7 @@
160
160
  },
161
161
  "dependencies": {
162
162
  "react-native-screens": "4.26.0",
163
- "@symbiote-native/components": "2.0.0"
163
+ "@symbiote-native/components": "3.0.0"
164
164
  },
165
165
  "peerDependencies": {
166
166
  "@angular/common": ">=20",
@@ -171,12 +171,12 @@
171
171
  "solid-js": ">=1.9.0",
172
172
  "svelte": ">=5.56.0",
173
173
  "vue": ">=3.5.0",
174
- "@symbiote-native/react": "^2.0.0",
175
- "@symbiote-native/angular": "^2.0.0",
176
- "@symbiote-native/solid": "^2.0.0",
177
- "@symbiote-native/svelte": "^2.0.0",
178
- "@symbiote-native/vue": "^2.0.0",
179
- "@symbiote-native/engine": "^0.5.0"
174
+ "@symbiote-native/angular": "^3.0.0",
175
+ "@symbiote-native/engine": "^1.0.0",
176
+ "@symbiote-native/react": "^3.0.0",
177
+ "@symbiote-native/solid": "^3.0.0",
178
+ "@symbiote-native/svelte": "^3.0.0",
179
+ "@symbiote-native/vue": "^3.0.0"
180
180
  },
181
181
  "peerDependenciesMeta": {
182
182
  "@angular/common": {
@@ -228,13 +228,13 @@
228
228
  "solid-js": "^1.9.14",
229
229
  "svelte": "^5.56.0",
230
230
  "typescript": "~6.0.0",
231
- "@symbiote-native/angular": "2.0.0",
232
- "@symbiote-native/engine": "0.5.0",
233
- "@symbiote-native/react": "2.0.0",
234
- "@symbiote-native/solid": "2.0.0",
235
- "@symbiote-native/test-utils": "0.3.0",
236
- "@symbiote-native/vue": "2.0.0",
237
- "@symbiote-native/svelte": "2.0.0"
231
+ "@symbiote-native/angular": "3.0.0",
232
+ "@symbiote-native/engine": "1.0.0",
233
+ "@symbiote-native/react": "3.0.0",
234
+ "@symbiote-native/solid": "3.0.0",
235
+ "@symbiote-native/svelte": "3.0.0",
236
+ "@symbiote-native/test-utils": "0.3.1",
237
+ "@symbiote-native/vue": "3.0.0"
238
238
  },
239
239
  "scripts": {
240
240
  "clean": "rm -rf build-ngc",
@@ -1,95 +1,69 @@
1
- // Live-tree walkers the smoke tests in this folder share.
1
+ // The live-tree walkers the smoke tests in this folder share, over the shared reader.
2
2
  //
3
- // `fabric.find()` (from @symbiote-native/test-utils) walks the CREATION log, not the current
4
- // tree: a node it returns stays "found" forever, and its `props` reflect creation time rather
5
- // than what is currently committed (svelte-adapter-dom-shim skill §15). Every assertion about
6
- // what is on screen NOW - "this route is gone after a pop", "this header currently carries this
7
- // title" - therefore walks `fabric.appRoot()` instead.
3
+ // Everything general — walking the LIVE child links instead of the creation log, flattening
4
+ // anchors the way the commit walk does, the two readings of a node — lives in
5
+ // `@symbiote-native/test-utils`'s `createLiveTree`. See its header for why each of those is not
6
+ // optional. What stays here is the two questions only this folder asks.
8
7
 
9
- export type IFabricNode = {
10
- viewName?: string;
11
- props?: Record<string, unknown>;
12
- children?: IFabricNode[];
13
- instanceHandle?: unknown;
14
- };
15
-
16
- function isFabricNode(value: unknown): value is IFabricNode {
17
- return typeof value === 'object' && value !== null;
18
- }
19
-
20
- export function walkLive(
21
- root: unknown,
22
- visit: (node: IFabricNode) => void,
23
- ): void {
24
- if (!isFabricNode(root)) return;
25
- visit(root);
26
- for (const child of root.children ?? []) walkLive(child, visit);
27
- }
28
-
29
- export function findAllLive(root: unknown, viewName: string): IFabricNode[] {
30
- const found: IFabricNode[] = [];
31
- walkLive(root, node => {
32
- if (node.viewName === viewName) found.push(node);
33
- });
34
- return found;
35
- }
8
+ import {
9
+ createLiveTree,
10
+ type IRecordingHost,
11
+ } from '@symbiote-native/test-utils';
12
+ import type { ILiveNode, ILiveTree } from '@symbiote-native/test-utils';
13
+ import type { ISymbioteNode } from '@symbiote-native/engine';
36
14
 
37
- export function findLive(
38
- root: unknown,
39
- viewName: string,
40
- ): IFabricNode | undefined {
41
- return findAllLive(root, viewName)[0];
42
- }
15
+ export type IFabricNode = ILiveNode;
43
16
 
44
- export function countLive(root: unknown, viewName: string): number {
45
- return findAllLive(root, viewName).length;
46
- }
47
-
48
- export function findLiveByTestId(
49
- root: unknown,
50
- testID: string,
51
- ): IFabricNode | undefined {
52
- let found: IFabricNode | undefined;
53
- walkLive(root, node => {
54
- if (found === undefined && node.props?.testID === testID) found = node;
55
- });
56
- return found;
57
- }
17
+ export type INavigationLiveTree = ILiveTree & {
18
+ findAllLive: (root: ISymbioteNode, viewName: string) => ILiveNode[];
19
+ findLive: (root: ISymbioteNode, viewName: string) => ILiveNode | undefined;
20
+ countLive: (root: ISymbioteNode, viewName: string) => number;
21
+ findLiveByTestId: (
22
+ root: ISymbioteNode,
23
+ testID: string,
24
+ ) => ILiveNode | undefined;
25
+ rawTextsOutsideTextContainer: (root: ISymbioteNode) => string[];
26
+ };
58
27
 
59
28
  const TEXT_CONTAINER_VIEW_NAMES = new Set(['RCTText', 'RCTVirtualText']);
60
29
 
61
- // Every raw text committed under a parent that cannot hold one - an invalid Fabric child.
62
- // The gap Svelte leaves between two sibling tags compiles to a ' ' text node, and the shim
63
- // drops it exactly when the parent takes no raw text (dom-shim/text.ts, svelte-adapter-dom-shim
64
- // §16b); inside an RCTText the same string is a real word separator and must survive. So the
65
- // parent, not the string, is what makes a raw text legal, and this returns the ones that are
66
- // not. Formatted `viewName > "text"` so a failure names the offender.
67
- export function rawTextsOutsideTextContainer(root: unknown): string[] {
68
- const found: string[] = [];
69
- const visit = (node: unknown, parent: IFabricNode | undefined): void => {
70
- if (!isFabricNode(node)) return;
71
- const parentName = parent?.viewName;
72
- if (
73
- node.viewName === 'RCTRawText' &&
74
- (parentName === undefined || !TEXT_CONTAINER_VIEW_NAMES.has(parentName))
75
- ) {
76
- found.push(`${String(parentName)} > ${JSON.stringify(node.props?.text)}`);
77
- }
78
- for (const child of node.children ?? []) visit(child, node);
79
- };
80
- visit(root, undefined);
81
- return found;
82
- }
30
+ export function createNavigationLiveTree(
31
+ fabric: IRecordingHost,
32
+ ): INavigationLiveTree {
33
+ const live = createLiveTree(fabric);
34
+ const byName = (root: ISymbioteNode, viewName: string): ILiveNode[] =>
35
+ live.findAllLive(root, node => node.viewName === viewName);
36
+
37
+ return {
38
+ ...live,
39
+ findAllLive: byName,
40
+ findLive: (root, viewName) => byName(root, viewName)[0],
41
+ countLive: (root, viewName) => byName(root, viewName).length,
42
+ findLiveByTestId: (root, testID) =>
43
+ live.findLive(root, node => node.props.testID === testID),
83
44
 
84
- // A depth-indented `viewName` outline, for asserting the exact committed SHAPE (which is what
85
- // catches a stray whitespace text node, an extra wrapper, or a missing header).
86
- export function outline(root: unknown): string[] {
87
- const lines: string[] = [];
88
- const visit = (node: unknown, depth: number): void => {
89
- if (!isFabricNode(node)) return;
90
- lines.push(`${' '.repeat(depth)}${String(node.viewName)}`);
91
- for (const child of node.children ?? []) visit(child, depth + 1);
45
+ // Every raw text under a parent that cannot hold one - an invalid Fabric child. The gap Svelte
46
+ // leaves between two sibling tags compiles to a ' ' text node, and the shim drops it exactly
47
+ // when the parent takes no raw text (dom-shim/text.ts, svelte-adapter-dom-shim §16b); inside an
48
+ // RCTText the same string is a real word separator and must survive. So the parent, not the
49
+ // string, is what makes a raw text legal, and this returns the ones that are not. Formatted
50
+ // `viewName > "text"` so a failure names the offender.
51
+ rawTextsOutsideTextContainer: (root): string[] => {
52
+ const found: string[] = [];
53
+ const visit = (node: ILiveNode, parentName: string | undefined): void => {
54
+ if (
55
+ node.viewName === 'RCTRawText' &&
56
+ (parentName === undefined ||
57
+ !TEXT_CONTAINER_VIEW_NAMES.has(parentName))
58
+ ) {
59
+ found.push(
60
+ `${String(parentName)} > ${JSON.stringify(node.props.text)}`,
61
+ );
62
+ }
63
+ for (const child of node.children) visit(child, node.viewName);
64
+ };
65
+ visit(live.nodeOf(root), undefined);
66
+ return found;
67
+ },
92
68
  };
93
- visit(root, 0);
94
- return lines;
95
69
  }