expo-router 2.0.5 → 2.0.7

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.
@@ -1,6 +1,4 @@
1
- import { getActionFromState } from "@react-navigation/core";
2
1
  import { Href } from "../link/href";
3
- import { NavigateAction } from "../link/stateOperations";
4
2
  import type { RouterStore } from "./router-store";
5
3
  export declare function push(this: RouterStore, url: Href): void;
6
4
  export declare function replace(this: RouterStore, url: Href): void;
@@ -8,6 +6,4 @@ export declare function goBack(this: RouterStore): void;
8
6
  export declare function canGoBack(this: RouterStore): boolean;
9
7
  export declare function setParams(this: RouterStore, params?: Record<string, string | number>): any;
10
8
  export declare function linkTo(this: RouterStore, href: string, event?: string): void;
11
- /** @returns `true` if the action is moving to the first screen of all the navigators in the action. */
12
- export declare function isAbsoluteInitialRoute(action: ReturnType<typeof getActionFromState>): action is NavigateAction;
13
9
  //# sourceMappingURL=routing.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/global-state/routing.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,kBAAkB,EAEnB,MAAM,wBAAwB,CAAC;AAIhC,OAAO,EAAE,IAAI,EAAe,MAAM,cAAc,CAAC;AAEjD,OAAO,EACL,cAAc,EAKf,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAUlD,wBAAgB,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,IAAI,QAEhD;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,IAAI,QAEnD;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,WAAW,QAGvC;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAKpD;AAED,wBAAgB,SAAS,CACvB,IAAI,EAAE,WAAW,EACjB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAM,OAI7C;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,QA6HrE;AAED,uGAAuG;AACvG,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,GAC5C,MAAM,IAAI,cAAc,CAqB1B"}
1
+ {"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/global-state/routing.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,IAAI,EAAe,MAAM,cAAc,CAAC;AAGjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAUlD,wBAAgB,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,IAAI,QAEhD;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,IAAI,QAEnD;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,WAAW,QAGvC;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAUpD;AAED,wBAAgB,SAAS,CACvB,IAAI,EAAE,WAAW,EACjB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAM,OAI7C;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,QAsDrE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-router",
3
- "version": "2.0.5",
3
+ "version": "2.0.7",
4
4
  "main": "src/index.tsx",
5
5
  "types": "build/index.d.ts",
6
6
  "files": [
@@ -22,6 +22,7 @@
22
22
  "tabs.ts",
23
23
  "types",
24
24
  "plugin",
25
+ "testing-library.ts",
25
26
  "app.plugin.js",
26
27
  "!**/__tests__",
27
28
  "_ctx.*",
@@ -104,12 +105,12 @@
104
105
  },
105
106
  "dependencies": {
106
107
  "@bacons/react-views": "^1.1.3",
107
- "@expo/metro-runtime": "2.2.7",
108
+ "@expo/metro-runtime": "2.2.9",
108
109
  "@radix-ui/react-slot": "1.0.1",
109
110
  "@react-navigation/bottom-tabs": "~6.5.7",
110
111
  "@react-navigation/native": "~6.1.6",
111
112
  "@react-navigation/native-stack": "~6.9.12",
112
- "expo-head": "0.0.11",
113
+ "expo-head": "0.0.13",
113
114
  "expo-splash-screen": "~0.20.2",
114
115
  "query-string": "7.1.3",
115
116
  "react-helmet-async": "^1.3.0",
@@ -1,20 +1,12 @@
1
1
  import {
2
- CommonActions,
3
- getActionFromState,
4
- StackActions,
5
- } from "@react-navigation/core";
6
- import { TabActions } from "@react-navigation/native";
2
+ type NavigationAction,
3
+ type NavigationState,
4
+ } from "@react-navigation/native";
7
5
  import * as Linking from "expo-linking";
8
6
 
7
+ import { ResultState } from "../fork/getStateFromPath";
9
8
  import { Href, resolveHref } from "../link/href";
10
9
  import { resolve } from "../link/path";
11
- import {
12
- NavigateAction,
13
- findTopRouteForTarget,
14
- getEarliestMismatchedRoute,
15
- getQualifiedStateForTopOfTargetState,
16
- isMovingToSiblingRoute,
17
- } from "../link/stateOperations";
18
10
  import { hasUrlProtocolPrefix } from "../utils/url";
19
11
  import type { RouterStore } from "./router-store";
20
12
 
@@ -40,6 +32,11 @@ export function goBack(this: RouterStore) {
40
32
  }
41
33
 
42
34
  export function canGoBack(this: RouterStore): boolean {
35
+ // Return a default value here if the navigation hasn't mounted yet.
36
+ // This can happen if the user calls `canGoBack` from the Root Layout route
37
+ // before mounting a navigator. This behavior exists due to React Navigation being dynamically
38
+ // constructed at runtime. We can get rid of this in the future if we use
39
+ // the static configuration internally.
43
40
  if (!this.navigationRef.isReady()) {
44
41
  return false;
45
42
  }
@@ -93,128 +90,94 @@ export function linkTo(this: RouterStore, href: string, event?: string) {
93
90
 
94
91
  const state = this.linking.getStateFromPath!(href, this.linking.config);
95
92
 
96
- if (!state) {
93
+ if (!state || state.routes.length === 0) {
97
94
  console.error(
98
95
  "Could not generate a valid navigation state for the given path: " + href
99
96
  );
100
97
  return;
101
98
  }
102
99
 
103
- const rootState = navigationRef.getRootState();
104
-
105
- // Ensure simple operations are used when moving between siblings
106
- // in the same navigator. This ensures that the state is not reset.
107
- // TODO: We may need to apply this at a larger scale in the future.
108
- if (isMovingToSiblingRoute(rootState, state)) {
109
- // Can perform naive movements
110
- const knownOwnerState = getQualifiedStateForTopOfTargetState(
111
- rootState,
112
- state
113
- )!;
114
- const nextRoute = findTopRouteForTarget(state);
115
- // NOTE(EvanBacon): There's an issue where moving from "a -> b" is considered siblings:
116
- // a. index (initialRouteName="index")
117
- // b. stack/index
118
- // However, the preservation approach doesn't work because it would be moving to a route with the same name.
119
- // The next check will see if the current focused route has the same name as the next route, if so, then fallback on
120
- // the default React Navigation logic.
121
- if (
122
- findTopRouteForTarget(
123
- // @ts-expect-error: stale types don't matter here
124
- rootState
125
- )?.name !== nextRoute.name
126
- ) {
127
- if (event === "REPLACE") {
128
- if (knownOwnerState.type === "tab") {
129
- navigationRef.dispatch(
130
- TabActions.jumpTo(nextRoute.name, nextRoute.params)
131
- );
132
- } else {
133
- navigationRef.dispatch(
134
- StackActions.replace(nextRoute.name, nextRoute.params)
135
- );
136
- }
137
- } else {
138
- // NOTE: Not sure if we should pop or push here...
139
- navigationRef.dispatch(
140
- CommonActions.navigate(nextRoute.name, nextRoute.params)
141
- );
142
- }
143
- return;
144
- }
100
+ switch (event) {
101
+ case "REPLACE":
102
+ return navigationRef.dispatch(
103
+ getNavigateReplaceAction(state, navigationRef.getRootState())
104
+ );
105
+ default:
106
+ return navigationRef.dispatch(getNavigatePushAction(state));
145
107
  }
108
+ }
146
109
 
147
- // TODO: Advanced movements across multiple navigators
148
-
149
- const action = getActionFromState(state, this.linking.config);
150
- if (action) {
151
- // Here we have a navigation action to a nested screen, where we should ideally replace.
152
- // This request can only be fulfilled if the target is an initial route.
153
- // First, check if the action is fully initial routes.
154
- // Then find the nearest mismatched route in the existing state.
155
- // Finally, use the correct navigator-based action to replace the nested screens.
156
- // NOTE(EvanBacon): A future version of this will involve splitting the navigation request so we replace as much as possible, then push the remaining screens to fulfill the request.
157
- if (event === "REPLACE" && isAbsoluteInitialRoute(action)) {
158
- const earliest = getEarliestMismatchedRoute(rootState, action.payload);
159
- if (earliest) {
160
- if (earliest.type === "stack") {
161
- navigationRef.dispatch(
162
- StackActions.replace(earliest.name, earliest.params)
163
- );
164
- } else {
165
- navigationRef.dispatch(
166
- TabActions.jumpTo(earliest.name, earliest.params)
167
- );
168
- }
169
- return;
170
- } else {
171
- // This should never happen because moving to the same route would be handled earlier
172
- // in the sibling operations.
173
- }
174
- }
110
+ type NavigationParams = Partial<{
111
+ screen: string;
112
+ params: NavigationParams;
113
+ }>;
175
114
 
176
- // Ignore the replace event here since replace across
177
- // navigators is not supported.
178
- navigationRef.dispatch(action);
179
- } else {
180
- navigationRef.reset(state);
115
+ function rewriteNavigationStateToParams(
116
+ state?: { routes: ResultState["routes"] },
117
+ params: NavigationParams = {}
118
+ ) {
119
+ if (!state) return params;
120
+ // We Should always have at least one route in the state
121
+ const lastRoute = state.routes.at(-1)!;
122
+ params.screen = lastRoute.name;
123
+ // Weirdly, this always needs to be an object. If it's undefined, it won't work.
124
+ params.params = lastRoute.params ?? {};
125
+
126
+ if (lastRoute.state) {
127
+ rewriteNavigationStateToParams(lastRoute.state, params.params);
181
128
  }
129
+
130
+ return params;
182
131
  }
183
132
 
184
- /** @returns `true` if the action is moving to the first screen of all the navigators in the action. */
185
- export function isAbsoluteInitialRoute(
186
- action: ReturnType<typeof getActionFromState>
187
- ): action is NavigateAction {
188
- if (action?.type !== "NAVIGATE") {
189
- return false;
190
- }
133
+ function getNavigatePushAction(state: ResultState) {
134
+ const { screen, params } = rewriteNavigationStateToParams(state);
135
+ return {
136
+ type: "NAVIGATE",
137
+ payload: {
138
+ name: screen,
139
+ params,
140
+ },
141
+ };
142
+ }
191
143
 
192
- let next = action.payload.params;
193
- // iterate all child screens and bail out if any are not initial.
194
- while (next) {
195
- if (!isNavigationState(next)) {
196
- // Not sure when this would happen
197
- return false;
198
- }
199
- if (next.initial === true) {
200
- next = next.params;
201
- // return true;
202
- } else if (next.initial === false) {
203
- return false;
204
- }
144
+ function getNavigateReplaceAction(
145
+ previousState: ResultState,
146
+ parentState: NavigationState,
147
+ lastNavigatorSupportingReplace: NavigationState = parentState
148
+ ): NavigationAction {
149
+ // We should always have at least one route in the state
150
+ const state = previousState.routes.at(-1)!;
151
+
152
+ // Only these navigators support replace
153
+ if (parentState.type === "stack" || parentState.type === "tab") {
154
+ lastNavigatorSupportingReplace = parentState;
205
155
  }
206
156
 
207
- return true;
208
- }
209
-
210
- type NavStateParams = {
211
- params?: NavStateParams;
212
- path: string;
213
- initial: boolean;
214
- screen: string;
215
- state: unknown;
216
- };
157
+ const currentRoute = parentState.routes.find(
158
+ (route) => route.name === state.name
159
+ );
160
+ const routesAreEqual = parentState.routes[parentState.index] === currentRoute;
161
+
162
+ // If there is nested state and the routes are equal, we should keep going down the tree
163
+ if (state.state && routesAreEqual && currentRoute.state) {
164
+ return getNavigateReplaceAction(
165
+ state.state,
166
+ currentRoute.state as any,
167
+ lastNavigatorSupportingReplace
168
+ );
169
+ }
217
170
 
218
- function isNavigationState(obj: any): obj is NavStateParams {
219
- return "initial" in obj;
171
+ // Either we reached the bottom of the state or the point where the routes diverged
172
+ const { screen, params } = rewriteNavigationStateToParams(previousState);
173
+ return {
174
+ type:
175
+ lastNavigatorSupportingReplace.type === "stack" ? "REPLACE" : "JUMP_TO",
176
+ payload: {
177
+ name: screen,
178
+ params,
179
+ // Ensure that the last navigator supporting replace is the one that handles the action
180
+ source: lastNavigatorSupportingReplace?.key,
181
+ },
182
+ };
220
183
  }
@@ -0,0 +1 @@
1
+ export * from "./src/testing-library";
@@ -1,81 +0,0 @@
1
- import { InitialState, NavigationState, ParamListBase, PartialState, getActionFromState } from "@react-navigation/native";
2
- import { ResultState } from "../fork/getStateFromPath";
3
- export type NavigateAction = Extract<ReturnType<typeof getActionFromState>, {
4
- type: "NAVIGATE";
5
- }> & {
6
- payload: NavigateActionParams;
7
- };
8
- export type NavigateActionParams = {
9
- params?: NavigateActionParams;
10
- path: string;
11
- initial: boolean;
12
- screen: string;
13
- name?: string;
14
- };
15
- /** Return the absolute last route to move to. */
16
- export declare function findTopRouteForTarget(state: ResultState): Omit<import("@react-navigation/native").Route<string, object | undefined>, "key"> & {
17
- state?: Readonly<Partial<Omit<Readonly<{
18
- key: string;
19
- index: number;
20
- routeNames: string[];
21
- history?: unknown[] | undefined;
22
- routes: (Readonly<{
23
- key: string;
24
- name: string;
25
- path?: string | undefined;
26
- }> & Readonly<{
27
- params?: Readonly<object | undefined>;
28
- }> & {
29
- state?: Readonly<any> | PartialState<Readonly<any>> | undefined;
30
- })[];
31
- type: string;
32
- stale: false;
33
- }>, "stale" | "routes">> & {
34
- routes: (Omit<import("@react-navigation/native").Route<string, object | undefined>, "key"> & any)[];
35
- }> | undefined;
36
- };
37
- /** @returns true if moving to a sibling inside the same navigator. */
38
- export declare function isMovingToSiblingRoute(currentState: NavigationState | PartialState<NavigationState> | undefined, targetState: ResultState | undefined): boolean;
39
- export declare function getQualifiedStateForTopOfTargetState(rootState: InitialState, targetState: ResultState): Readonly<Partial<Omit<Readonly<{
40
- key: string;
41
- index: number;
42
- routeNames: string[];
43
- history?: unknown[] | undefined;
44
- routes: (Readonly<{
45
- key: string;
46
- name: string;
47
- path?: string | undefined;
48
- }> & Readonly<{
49
- params?: Readonly<object | undefined>;
50
- }> & {
51
- state?: Readonly<any> | PartialState<Readonly<any>> | undefined;
52
- })[];
53
- type: string;
54
- stale: false;
55
- }>, "stale" | "routes">> & {
56
- routes: (Omit<import("@react-navigation/native").Route<string, object | undefined>, "key"> & {
57
- state?: Readonly<Partial<Omit<Readonly<{
58
- key: string;
59
- index: number;
60
- routeNames: string[];
61
- history?: unknown[] | undefined;
62
- routes: (Readonly<{
63
- key: string;
64
- name: string;
65
- path?: string | undefined;
66
- }> & Readonly<{
67
- params?: Readonly<object | undefined>;
68
- }> & {
69
- state?: Readonly<any> | PartialState<Readonly<any>> | undefined;
70
- })[];
71
- type: string;
72
- stale: false;
73
- }>, "stale" | "routes">> & any> | undefined;
74
- })[];
75
- }>;
76
- export declare function getEarliestMismatchedRoute<T extends ParamListBase>(rootState: NavigationState<T> | undefined, actionParams: NavigateActionParams): {
77
- name: string;
78
- params?: any;
79
- type?: string;
80
- } | null;
81
- //# sourceMappingURL=stateOperations.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"stateOperations.d.ts","sourceRoot":"","sources":["../../src/link/stateOperations.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,eAAe,EACf,aAAa,EACb,YAAY,EACZ,kBAAkB,EACnB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD,MAAM,MAAM,cAAc,GAAG,OAAO,CAClC,UAAU,CAAC,OAAO,kBAAkB,CAAC,EACrC;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CACrB,GAAG;IACF,OAAO,EAAE,oBAAoB,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAwBF,iDAAiD;AACjD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,WAAW;;;;;;;;;;;;;;;;;;;;EAIvD;AAED,sEAAsE;AACtE,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,eAAe,GAAG,YAAY,CAAC,eAAe,CAAC,GAAG,SAAS,EACzE,WAAW,EAAE,WAAW,GAAG,SAAS,GACnC,OAAO,CAgCT;AAKD,wBAAgB,oCAAoC,CAClD,SAAS,EAAE,YAAY,EACvB,WAAW,EAAE,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyBzB;AAKD,wBAAgB,0BAA0B,CAAC,CAAC,SAAS,aAAa,EAChE,SAAS,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,SAAS,EACzC,YAAY,EAAE,oBAAoB,GACjC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,GAAG,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAgCtD"}
@@ -1,163 +0,0 @@
1
- import {
2
- InitialState,
3
- NavigationState,
4
- ParamListBase,
5
- PartialState,
6
- getActionFromState,
7
- } from "@react-navigation/native";
8
-
9
- import { ResultState } from "../fork/getStateFromPath";
10
-
11
- export type NavigateAction = Extract<
12
- ReturnType<typeof getActionFromState>,
13
- { type: "NAVIGATE" }
14
- > & {
15
- payload: NavigateActionParams;
16
- };
17
-
18
- export type NavigateActionParams = {
19
- params?: NavigateActionParams;
20
- path: string;
21
- initial: boolean;
22
- screen: string;
23
- name?: string;
24
- };
25
-
26
- // Get the last state for a given target state (generated from a path).
27
- function findTopStateForTarget(state: ResultState) {
28
- let current: Partial<InitialState> | undefined = state;
29
- let previous: Partial<InitialState> | undefined = state;
30
-
31
- while (current?.routes?.[current?.routes?.length - 1].state != null) {
32
- previous = current;
33
- current = current?.routes[current?.routes.length - 1].state;
34
- }
35
-
36
- // If the last route in the target state is an index route, return the previous state (parent).
37
- // NOTE: This may need to be updated to support initial route name being a non-standard value.
38
- if (
39
- previous &&
40
- current?.routes?.[current.routes.length - 1]!.name === "index"
41
- ) {
42
- return previous;
43
- }
44
-
45
- return current;
46
- }
47
-
48
- /** Return the absolute last route to move to. */
49
- export function findTopRouteForTarget(state: ResultState) {
50
- const nextState = findTopStateForTarget(state)!;
51
- // Ensure we get the last route to prevent returning the initial route.
52
- return nextState.routes?.[nextState.routes.length - 1]!;
53
- }
54
-
55
- /** @returns true if moving to a sibling inside the same navigator. */
56
- export function isMovingToSiblingRoute(
57
- currentState: NavigationState | PartialState<NavigationState> | undefined,
58
- targetState: ResultState | undefined
59
- ): boolean {
60
- if (!currentState || !targetState) {
61
- return false;
62
- }
63
-
64
- // Need to type this, as the current types are not compaitble with the `find`
65
- const targetRoute = targetState.routes[0];
66
-
67
- // Make sure we're in the same navigator
68
- if (!currentState.routeNames?.includes(targetRoute.name)) {
69
- return false;
70
- }
71
-
72
- // If there's no state, we're at the end of the path
73
- if (!targetRoute.state) {
74
- return true;
75
- }
76
-
77
- // Coerce the types into a more common form
78
- const currentRoutes:
79
- | {
80
- name: string;
81
- state?: NavigationState | PartialState<NavigationState>;
82
- }[]
83
- | undefined = currentState?.routes;
84
- const locatedState = currentRoutes?.find((r) => r.name === targetRoute.name);
85
-
86
- if (!locatedState) {
87
- return false;
88
- }
89
-
90
- return isMovingToSiblingRoute(locatedState.state, targetRoute.state);
91
- }
92
-
93
- // Given the root state and a target state from `getStateFromPath`,
94
- // return the root state containing the highest target route matching the root state.
95
- // This can be used to determine what type of navigator action should be used.
96
- export function getQualifiedStateForTopOfTargetState(
97
- rootState: InitialState,
98
- targetState: ResultState
99
- ) {
100
- let current: InitialState | undefined = targetState;
101
- let currentRoot: InitialState | undefined = rootState;
102
-
103
- while (current?.routes?.[current?.routes?.length - 1].state != null) {
104
- const nextRoute: any = current?.routes?.[current?.routes?.length - 1];
105
-
106
- const nextCurrentRoot: InitialState | undefined = currentRoot?.routes?.find(
107
- (route) => route.name === nextRoute.name
108
- )?.state;
109
-
110
- if (nextCurrentRoot == null) {
111
- return currentRoot;
112
- // Not sure what to do -- we're tracking against the assumption that
113
- // all routes in the target state are in the root state
114
- // currentRoot = undefined;
115
- } else {
116
- currentRoot = nextCurrentRoot;
117
- }
118
-
119
- current = nextRoute.state;
120
- }
121
-
122
- return currentRoot;
123
- }
124
-
125
- // Given the root state and a target state from `getStateFromPath`,
126
- // return the root state containing the highest target route matching the root state.
127
- // This can be used to determine what type of navigator action should be used.
128
- export function getEarliestMismatchedRoute<T extends ParamListBase>(
129
- rootState: NavigationState<T> | undefined,
130
- actionParams: NavigateActionParams
131
- ): { name: string; params?: any; type?: string } | null {
132
- const actionName = actionParams.name ?? actionParams.screen;
133
- if (!rootState?.routes || rootState.index == null) {
134
- // This should never happen where there's more action than state.
135
- return {
136
- name: actionName,
137
- type: "stack",
138
- };
139
- }
140
-
141
- const nextCurrentRoot = rootState.routes[rootState.index];
142
- if (actionName === nextCurrentRoot.name) {
143
- if (!actionParams.params) {
144
- // All routes match all the way up, no change required.
145
- return null;
146
- }
147
-
148
- return getEarliestMismatchedRoute(
149
- // @react-navigation/native types this as NavigationState | Partial<NavigationState> | undefined
150
- // In our usage, it's always a NavigationState | undefined
151
- nextCurrentRoot.state as NavigationState<T> | undefined,
152
- actionParams.params
153
- );
154
- }
155
-
156
- // There's a selected state but it doesn't match the action state
157
- // this is now the lowest point of change.
158
- return {
159
- name: actionName,
160
- params: actionParams.params,
161
- type: rootState.type,
162
- };
163
- }