UNPKG

3.91 kBTypeScriptView Raw
1import type {
2 CommonActions,
3 NavigationState,
4 ParamListBase,
5 PartialRoute,
6 PartialState,
7 Route,
8} from '@react-navigation/routers';
9
10import type { NavigatorScreenParams, PathConfig, PathConfigMap } from './types';
11
12type ConfigItem = {
13 initialRouteName?: string;
14 screens?: Record<string, ConfigItem>;
15};
16
17type Options = {
18 initialRouteName?: string;
19 screens: PathConfigMap<object>;
20};
21
22type NavigateAction<State extends NavigationState> = {
23 type: 'NAVIGATE';
24 payload: {
25 name: string;
26 params?: NavigatorScreenParams<State>;
27 path?: string;
28 };
29};
30
31export default function getActionFromState(
32 state: PartialState<NavigationState>,
33 options?: Options
34): NavigateAction<NavigationState> | CommonActions.Action | undefined {
35 // Create a normalized configs object which will be easier to use
36 const normalizedConfig = options
37 ? createNormalizedConfigItem(options as PathConfig<object> | string)
38 : {};
39
40 const routes =
41 state.index != null ? state.routes.slice(0, state.index + 1) : state.routes;
42
43 if (routes.length === 0) {
44 return undefined;
45 }
46
47 if (
48 !(
49 (routes.length === 1 && routes[0].key === undefined) ||
50 (routes.length === 2 &&
51 routes[0].key === undefined &&
52 routes[0].name === normalizedConfig?.initialRouteName &&
53 routes[1].key === undefined)
54 )
55 ) {
56 return {
57 type: 'RESET',
58 payload: state,
59 };
60 }
61
62 const route = state.routes[state.index ?? state.routes.length - 1];
63
64 let current: PartialState<NavigationState> | undefined = route?.state;
65 let config: ConfigItem | undefined = normalizedConfig?.screens?.[route?.name];
66 let params = { ...route.params } as NavigatorScreenParams<
67 ParamListBase,
68 NavigationState
69 >;
70
71 let payload = route
72 ? { name: route.name, path: route.path, params }
73 : undefined;
74
75 while (current) {
76 if (current.routes.length === 0) {
77 return undefined;
78 }
79
80 const routes =
81 current.index != null
82 ? current.routes.slice(0, current.index + 1)
83 : current.routes;
84
85 const route: Route<string> | PartialRoute<Route<string>> =
86 routes[routes.length - 1];
87
88 // Explicitly set to override existing value when merging params
89 Object.assign(params, {
90 initial: undefined,
91 screen: undefined,
92 params: undefined,
93 state: undefined,
94 });
95
96 if (routes.length === 1 && routes[0].key === undefined) {
97 params.initial = true;
98 params.screen = route.name;
99 } else if (
100 routes.length === 2 &&
101 routes[0].key === undefined &&
102 routes[0].name === config?.initialRouteName &&
103 routes[1].key === undefined
104 ) {
105 params.initial = false;
106 params.screen = route.name;
107 } else {
108 params.state = current;
109 break;
110 }
111
112 if (route.state) {
113 params.params = { ...route.params };
114 params = params.params as NavigatorScreenParams<
115 ParamListBase,
116 NavigationState
117 >;
118 } else {
119 params.path = route.path;
120 params.params = route.params;
121 }
122
123 current = route.state;
124 config = config?.screens?.[route.name];
125 }
126
127 if (!payload) {
128 return;
129 }
130
131 // Try to construct payload for a `NAVIGATE` action from the state
132 // This lets us preserve the navigation state and not lose it
133 return {
134 type: 'NAVIGATE',
135 payload,
136 };
137}
138
139const createNormalizedConfigItem = (config: PathConfig<object> | string) =>
140 typeof config === 'object' && config != null
141 ? {
142 initialRouteName: config.initialRouteName,
143 screens:
144 config.screens != null
145 ? createNormalizedConfigs(config.screens)
146 : undefined,
147 }
148 : {};
149
150const createNormalizedConfigs = (options: PathConfigMap<object>) =>
151 Object.entries(options).reduce<Record<string, ConfigItem>>((acc, [k, v]) => {
152 acc[k] = createNormalizedConfigItem(v);
153 return acc;
154 }, {});
155
\No newline at end of file