@react-navigation/core 8.0.0-alpha.23 → 8.0.0-alpha.25

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 (153) hide show
  1. package/lib/module/BaseNavigationContainer.js +5 -2
  2. package/lib/module/BaseNavigationContainer.js.map +1 -1
  3. package/lib/module/DataLoading.js +102 -0
  4. package/lib/module/DataLoading.js.map +1 -0
  5. package/lib/module/NavigationBuilderContext.js.map +1 -1
  6. package/lib/module/NavigationIndependentTree.js +7 -6
  7. package/lib/module/NavigationIndependentTree.js.map +1 -1
  8. package/lib/module/NavigationProvider.js +12 -12
  9. package/lib/module/NavigationProvider.js.map +1 -1
  10. package/lib/module/PreventRemoveContext.js.map +1 -1
  11. package/lib/module/PreventRemoveProvider.js +34 -24
  12. package/lib/module/PreventRemoveProvider.js.map +1 -1
  13. package/lib/module/StaticNavigation.js +15 -7
  14. package/lib/module/StaticNavigation.js.map +1 -1
  15. package/lib/module/StaticTreeContext.js +5 -0
  16. package/lib/module/StaticTreeContext.js.map +1 -0
  17. package/lib/module/checkSerializable.js +7 -9
  18. package/lib/module/checkSerializable.js.map +1 -1
  19. package/lib/module/createNavigationContainerRef.js +6 -8
  20. package/lib/module/createNavigationContainerRef.js.map +1 -1
  21. package/lib/module/createNavigatorFactory.js.map +1 -1
  22. package/lib/module/getActionFromState.js +11 -11
  23. package/lib/module/getActionFromState.js.map +1 -1
  24. package/lib/module/getPathFromState.js +125 -96
  25. package/lib/module/getPathFromState.js.map +1 -1
  26. package/lib/module/getPatternParts.js +2 -2
  27. package/lib/module/getPatternParts.js.map +1 -1
  28. package/lib/module/getStateFromPath.js +136 -78
  29. package/lib/module/getStateFromPath.js.map +1 -1
  30. package/lib/module/index.js +1 -0
  31. package/lib/module/index.js.map +1 -1
  32. package/lib/module/types.js +13 -0
  33. package/lib/module/types.js.map +1 -1
  34. package/lib/module/useEventEmitter.js +5 -2
  35. package/lib/module/useEventEmitter.js.map +1 -1
  36. package/lib/module/useKeyedChildListeners.js +4 -2
  37. package/lib/module/useKeyedChildListeners.js.map +1 -1
  38. package/lib/module/useNavigation.js +2 -15
  39. package/lib/module/useNavigation.js.map +1 -1
  40. package/lib/module/useNavigationBuilder.js +5 -21
  41. package/lib/module/useNavigationBuilder.js.map +1 -1
  42. package/lib/module/useNavigationCache.js +19 -14
  43. package/lib/module/useNavigationCache.js.map +1 -1
  44. package/lib/module/useNavigationHelpers.js +27 -9
  45. package/lib/module/useNavigationHelpers.js.map +1 -1
  46. package/lib/module/useNavigationState.js +69 -36
  47. package/lib/module/useNavigationState.js.map +1 -1
  48. package/lib/module/useOnAction.js +25 -3
  49. package/lib/module/useOnAction.js.map +1 -1
  50. package/lib/module/useOnPreventRemove.js +35 -24
  51. package/lib/module/useOnPreventRemove.js.map +1 -1
  52. package/lib/module/useOptionsGetters.js +3 -1
  53. package/lib/module/useOptionsGetters.js.map +1 -1
  54. package/lib/module/usePreventRemove.js +9 -2
  55. package/lib/module/usePreventRemove.js.map +1 -1
  56. package/lib/module/useRoute.js.map +1 -1
  57. package/lib/module/useRouteCache.js +14 -7
  58. package/lib/module/useRouteCache.js.map +1 -1
  59. package/lib/module/useScheduleUpdate.js +8 -8
  60. package/lib/module/useScheduleUpdate.js.map +1 -1
  61. package/lib/module/useSyncState.js +4 -1
  62. package/lib/module/useSyncState.js.map +1 -1
  63. package/lib/module/validatePathConfig.js +50 -37
  64. package/lib/module/validatePathConfig.js.map +1 -1
  65. package/lib/typescript/src/BaseNavigationContainer.d.ts.map +1 -1
  66. package/lib/typescript/src/DataLoading.d.ts +20 -0
  67. package/lib/typescript/src/DataLoading.d.ts.map +1 -0
  68. package/lib/typescript/src/NavigationBuilderContext.d.ts +2 -2
  69. package/lib/typescript/src/NavigationBuilderContext.d.ts.map +1 -1
  70. package/lib/typescript/src/NavigationProvider.d.ts +0 -1
  71. package/lib/typescript/src/NavigationProvider.d.ts.map +1 -1
  72. package/lib/typescript/src/PreventRemoveContext.d.ts +1 -0
  73. package/lib/typescript/src/PreventRemoveContext.d.ts.map +1 -1
  74. package/lib/typescript/src/PreventRemoveProvider.d.ts.map +1 -1
  75. package/lib/typescript/src/StaticNavigation.d.ts +68 -42
  76. package/lib/typescript/src/StaticNavigation.d.ts.map +1 -1
  77. package/lib/typescript/src/StaticTreeContext.d.ts +4 -0
  78. package/lib/typescript/src/StaticTreeContext.d.ts.map +1 -0
  79. package/lib/typescript/src/checkSerializable.d.ts +4 -2
  80. package/lib/typescript/src/checkSerializable.d.ts.map +1 -1
  81. package/lib/typescript/src/createNavigationContainerRef.d.ts.map +1 -1
  82. package/lib/typescript/src/createNavigatorFactory.d.ts +11 -5
  83. package/lib/typescript/src/createNavigatorFactory.d.ts.map +1 -1
  84. package/lib/typescript/src/getActionFromState.d.ts.map +1 -1
  85. package/lib/typescript/src/getPathFromState.d.ts.map +1 -1
  86. package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
  87. package/lib/typescript/src/index.d.ts +2 -1
  88. package/lib/typescript/src/index.d.ts.map +1 -1
  89. package/lib/typescript/src/types.d.ts +84 -29
  90. package/lib/typescript/src/types.d.ts.map +1 -1
  91. package/lib/typescript/src/useEventEmitter.d.ts +1 -1
  92. package/lib/typescript/src/useEventEmitter.d.ts.map +1 -1
  93. package/lib/typescript/src/useKeyedChildListeners.d.ts.map +1 -1
  94. package/lib/typescript/src/useNavigation.d.ts.map +1 -1
  95. package/lib/typescript/src/useNavigationBuilder.d.ts +1 -1
  96. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  97. package/lib/typescript/src/useNavigationCache.d.ts.map +1 -1
  98. package/lib/typescript/src/useNavigationHelpers.d.ts +3 -4
  99. package/lib/typescript/src/useNavigationHelpers.d.ts.map +1 -1
  100. package/lib/typescript/src/useNavigationState.d.ts +11 -1
  101. package/lib/typescript/src/useNavigationState.d.ts.map +1 -1
  102. package/lib/typescript/src/useOnAction.d.ts.map +1 -1
  103. package/lib/typescript/src/useOnPreventRemove.d.ts +2 -6
  104. package/lib/typescript/src/useOnPreventRemove.d.ts.map +1 -1
  105. package/lib/typescript/src/useOptionsGetters.d.ts.map +1 -1
  106. package/lib/typescript/src/usePreventRemove.d.ts.map +1 -1
  107. package/lib/typescript/src/usePreventRemoveContext.d.ts +1 -0
  108. package/lib/typescript/src/usePreventRemoveContext.d.ts.map +1 -1
  109. package/lib/typescript/src/useRoute.d.ts +1 -1
  110. package/lib/typescript/src/useRoute.d.ts.map +1 -1
  111. package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
  112. package/lib/typescript/src/useScheduleUpdate.d.ts +5 -4
  113. package/lib/typescript/src/useScheduleUpdate.d.ts.map +1 -1
  114. package/lib/typescript/src/useSyncState.d.ts.map +1 -1
  115. package/lib/typescript/src/utilities.d.ts +21 -38
  116. package/lib/typescript/src/utilities.d.ts.map +1 -1
  117. package/lib/typescript/src/validatePathConfig.d.ts.map +1 -1
  118. package/package.json +6 -8
  119. package/src/BaseNavigationContainer.tsx +9 -2
  120. package/src/DataLoading.tsx +139 -0
  121. package/src/NavigationBuilderContext.tsx +5 -1
  122. package/src/NavigationIndependentTree.tsx +11 -11
  123. package/src/NavigationProvider.tsx +13 -16
  124. package/src/PreventRemoveContext.tsx +1 -0
  125. package/src/PreventRemoveProvider.tsx +48 -43
  126. package/src/StaticNavigation.tsx +164 -99
  127. package/src/StaticTreeContext.tsx +7 -0
  128. package/src/checkSerializable.tsx +20 -14
  129. package/src/createNavigationContainerRef.tsx +12 -9
  130. package/src/createNavigatorFactory.tsx +27 -18
  131. package/src/getActionFromState.tsx +18 -16
  132. package/src/getPathFromState.tsx +167 -132
  133. package/src/getPatternParts.tsx +2 -2
  134. package/src/getStateFromPath.tsx +260 -193
  135. package/src/index.tsx +3 -0
  136. package/src/types.tsx +298 -62
  137. package/src/useEventEmitter.tsx +7 -2
  138. package/src/useKeyedChildListeners.tsx +4 -2
  139. package/src/useNavigation.tsx +2 -21
  140. package/src/useNavigationBuilder.tsx +7 -24
  141. package/src/useNavigationCache.tsx +26 -19
  142. package/src/useNavigationHelpers.tsx +26 -9
  143. package/src/useNavigationState.tsx +90 -60
  144. package/src/useOnAction.tsx +55 -3
  145. package/src/useOnPreventRemove.tsx +68 -36
  146. package/src/useOptionsGetters.tsx +4 -1
  147. package/src/usePreventRemove.tsx +11 -2
  148. package/src/useRoute.tsx +3 -5
  149. package/src/useRouteCache.tsx +19 -11
  150. package/src/useScheduleUpdate.tsx +8 -7
  151. package/src/useSyncState.tsx +5 -1
  152. package/src/utilities.tsx +36 -41
  153. package/src/validatePathConfig.tsx +86 -58
@@ -51,25 +51,27 @@ export function getActionFromState(
51
51
  normalizedConfig = {};
52
52
  }
53
53
 
54
- const routes =
55
- state.index != null ? state.routes.slice(0, state.index + 1) : state.routes;
54
+ const routesLength =
55
+ state.index == null || state.index >= state.routes.length
56
+ ? state.routes.length
57
+ : state.index + 1;
56
58
 
57
- if (routes.length === 0) {
59
+ if (routesLength === 0) {
58
60
  return undefined;
59
61
  }
60
62
 
61
- const firstRoute = routes[0];
63
+ const firstRoute = state.routes[0];
62
64
 
63
65
  if (firstRoute == null) {
64
66
  return undefined;
65
67
  }
66
68
 
67
- const secondRoute = routes[1];
69
+ const secondRoute = state.routes[1];
68
70
 
69
71
  if (
70
72
  !(
71
- (routes.length === 1 && firstRoute.key == null) ||
72
- (routes.length === 2 &&
73
+ (routesLength === 1 && firstRoute.key == null) ||
74
+ (routesLength === 2 &&
73
75
  firstRoute.key == null &&
74
76
  firstRoute.name === normalizedConfig?.initialRouteName &&
75
77
  secondRoute?.key == null)
@@ -121,24 +123,24 @@ export function getActionFromState(
121
123
  return undefined;
122
124
  }
123
125
 
124
- const routes =
125
- current.index != null
126
- ? current.routes.slice(0, current.index + 1)
127
- : current.routes;
126
+ const routesLength =
127
+ current.index == null || current.index >= current.routes.length
128
+ ? current.routes.length
129
+ : current.index + 1;
128
130
 
129
- const route = routes[routes.length - 1];
131
+ const route = current.routes[routesLength - 1];
130
132
 
131
133
  if (route == null) {
132
134
  return undefined;
133
135
  }
134
136
 
135
- const firstRoute = routes[0];
137
+ const firstRoute = current.routes[0];
136
138
 
137
139
  if (firstRoute == null) {
138
140
  return undefined;
139
141
  }
140
142
 
141
- const secondRoute = routes[1];
143
+ const secondRoute = current.routes[1];
142
144
 
143
145
  // Explicitly set to override existing value when merging params
144
146
  Object.assign(params, {
@@ -148,11 +150,11 @@ export function getActionFromState(
148
150
  state: undefined,
149
151
  });
150
152
 
151
- if (routes.length === 1 && firstRoute.key == null) {
153
+ if (routesLength === 1 && firstRoute.key == null) {
152
154
  params.initial = true;
153
155
  params.screen = route.name;
154
156
  } else if (
155
- routes.length === 2 &&
157
+ routesLength === 2 &&
156
158
  firstRoute.key == null &&
157
159
  firstRoute.name === config?.initialRouteName &&
158
160
  secondRoute?.key == null
@@ -22,8 +22,30 @@ type ConfigItem = {
22
22
  screens?: Record<string, ConfigItem> | undefined;
23
23
  };
24
24
 
25
+ const encodePathParam = (value: string) => {
26
+ let result = '';
27
+
28
+ for (const char of value) {
29
+ const charCode = char.charCodeAt(0);
30
+ const safe =
31
+ (charCode >= 65 && charCode <= 90) ||
32
+ (charCode >= 97 && charCode <= 122) ||
33
+ (charCode >= 48 && charCode <= 57) ||
34
+ "-._~!$&'()*+,;=:@".includes(char);
35
+
36
+ result += safe ? char : encodeURIComponent(char);
37
+ }
38
+
39
+ return result;
40
+ };
41
+
25
42
  const getActiveRoute = (
26
- state: State
43
+ state: State,
44
+ configs: Record<string, ConfigItem> | undefined,
45
+ getRouteState: (
46
+ route: State['routes'][number],
47
+ config: ConfigItem | undefined
48
+ ) => State | undefined
27
49
  ): { name: string; params?: object | undefined } => {
28
50
  const route =
29
51
  typeof state.index === 'number'
@@ -34,74 +56,41 @@ const getActiveRoute = (
34
56
  throw new Error(`Couldn't find the active route.`);
35
57
  }
36
58
 
37
- if (route.state) {
38
- return getActiveRoute(route.state);
59
+ const config = configs?.[route.name];
60
+ const routeState = getRouteState(route, config);
61
+
62
+ if (routeState) {
63
+ return getActiveRoute(routeState, config?.screens, getRouteState);
39
64
  }
40
65
 
41
66
  return route;
42
67
  };
43
68
 
44
69
  const cachedNormalizedConfigs = new WeakMap<
45
- PathConfigMap<{}>,
70
+ Options<{}>,
46
71
  Record<string, ConfigItem>
47
72
  >();
48
73
 
49
74
  const getNormalizedConfigs = (options?: Options<{}>) => {
50
- if (!options?.screens) return {};
75
+ if (!options) {
76
+ return {};
77
+ }
51
78
 
52
- const cached = cachedNormalizedConfigs.get(options?.screens);
79
+ const cached = cachedNormalizedConfigs.get(options);
80
+
81
+ if (cached) {
82
+ return cached;
83
+ }
53
84
 
54
- if (cached) return cached;
85
+ validatePathConfig(options);
55
86
 
56
87
  const normalizedConfigs = createNormalizedConfigs(options.screens);
57
88
 
58
- cachedNormalizedConfigs.set(options.screens, normalizedConfigs);
89
+ cachedNormalizedConfigs.set(options, normalizedConfigs);
59
90
 
60
91
  return normalizedConfigs;
61
92
  };
62
93
 
63
- const getTransformedState = (
64
- state: State,
65
- configs: Record<string, ConfigItem> | undefined
66
- ): Omit<PartialState<NavigationState>, 'stale'> => {
67
- const routes = state.routes.map(
68
- (route): Omit<PartialState<NavigationState>, 'stale'>['routes'][number] => {
69
- const config = configs?.[route.name];
70
-
71
- if (
72
- route.state ||
73
- (config?.screens &&
74
- route.params &&
75
- (('screen' in route.params &&
76
- typeof route.params.screen === 'string' &&
77
- config.screens[route.params.screen]) ||
78
- 'state' in route.params))
79
- ) {
80
- const nestedState: State | undefined =
81
- route.state ?? getStateFromRouteParams(route.params);
82
-
83
- if (nestedState) {
84
- return {
85
- ...route,
86
- state: getTransformedState(
87
- nestedState,
88
- configs?.[route.name]?.screens
89
- ),
90
- };
91
- }
92
- }
93
-
94
- // @ts-expect-error route.state is handled in previous condition
95
- return route;
96
- }
97
- );
98
-
99
- return {
100
- ...state,
101
- routes,
102
- };
103
- };
104
-
105
94
  /**
106
95
  * Utility to serialize a navigation state object to a path string.
107
96
  *
@@ -141,15 +130,45 @@ export function getPathFromState<ParamList extends {}>(
141
130
  );
142
131
  }
143
132
 
144
- if (options) {
145
- validatePathConfig(options);
146
- }
147
-
148
133
  const configs = getNormalizedConfigs(options);
149
- const transformedState = getTransformedState(state, configs);
134
+ const cachedRouteStates = new Map<object, State | undefined>();
135
+
136
+ const getRouteState = (
137
+ route: State['routes'][number],
138
+ config: ConfigItem | undefined
139
+ ) => {
140
+ if (route.state) {
141
+ return route.state;
142
+ }
143
+
144
+ const hasScreenParams =
145
+ route.params &&
146
+ 'screen' in route.params &&
147
+ typeof route.params.screen === 'string' &&
148
+ config?.screens?.[route.params.screen];
149
+
150
+ const hasStateParams =
151
+ route.params && 'state' in route.params && config?.screens;
152
+
153
+ if (
154
+ !route.params ||
155
+ !config?.screens ||
156
+ (!hasScreenParams && !hasStateParams)
157
+ ) {
158
+ return undefined;
159
+ }
160
+
161
+ if (!cachedRouteStates.has(route)) {
162
+ cachedRouteStates.set(route, getStateFromRouteParams(route.params));
163
+ }
164
+
165
+ return cachedRouteStates.get(route);
166
+ };
167
+
168
+ const focusedRoute = getActiveRoute(state, configs, getRouteState);
150
169
 
151
170
  let path = '/';
152
- let current: State | undefined = transformedState;
171
+ let current: State | undefined = state;
153
172
 
154
173
  const allParams: Record<string, string> = {};
155
174
 
@@ -170,8 +189,6 @@ export function getPathFromState<ParamList extends {}>(
170
189
  let focusedParams: Record<string, string> | undefined;
171
190
  let currentOptions = configs;
172
191
 
173
- const focusedRoute = getActiveRoute(transformedState);
174
-
175
192
  // Keep all the route names that appeared during going deeper in config in case the pattern is resolved to undefined
176
193
  const nestedRouteNames = [];
177
194
 
@@ -190,30 +207,31 @@ export function getPathFromState<ParamList extends {}>(
190
207
 
191
208
  if (route.params) {
192
209
  const options = config;
210
+ const params = route.params as Record<string, unknown>;
211
+ const currentParams: Record<string, string> = {};
212
+
213
+ for (const key in params) {
214
+ const value = params[key];
193
215
 
194
- const currentParams = Object.fromEntries(
195
- Object.entries(route.params)
196
- .map(([key, value]): [string, string] | null => {
197
- if (value === undefined) {
198
- if (options) {
199
- const optional = options.parts?.find(
200
- (part) => part.param === key
201
- )?.optional;
202
-
203
- if (optional) {
204
- return null;
205
- }
206
- } else {
207
- return null;
208
- }
216
+ if (value === undefined) {
217
+ let optional = false;
218
+
219
+ for (const part of options.parts ?? []) {
220
+ if (part.param === key) {
221
+ optional = part.optional === true;
222
+ break;
209
223
  }
224
+ }
210
225
 
211
- const stringify = options?.stringify?.[key] ?? String;
226
+ if (optional) {
227
+ continue;
228
+ }
229
+ }
212
230
 
213
- return [key, stringify(value)];
214
- })
215
- .filter((entry) => entry != null)
216
- );
231
+ const stringify = options.stringify?.[key] ?? String;
232
+
233
+ currentParams[key] = stringify(value);
234
+ }
217
235
 
218
236
  if (parts?.length) {
219
237
  Object.assign(allParams, currentParams);
@@ -222,33 +240,40 @@ export function getPathFromState<ParamList extends {}>(
222
240
  if (focusedRoute === route) {
223
241
  // If this is the focused route, keep the params for later use
224
242
  // We save it here since it's been stringified already
225
- focusedParams = { ...currentParams };
226
-
227
- parts
228
- // eslint-disable-next-line no-loop-func
229
- ?.forEach(({ param }) => {
230
- if (param) {
231
- // Remove the params present in the pattern since we'll only use the rest for query string
232
- if (focusedParams) {
233
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
234
- delete focusedParams[param];
235
- }
243
+ focusedParams = {};
244
+
245
+ for (const key in currentParams) {
246
+ let inPattern = false;
247
+
248
+ for (const part of parts ?? []) {
249
+ if (part.param === key) {
250
+ inPattern = true;
251
+ break;
236
252
  }
237
- });
253
+ }
254
+
255
+ const value = currentParams[key];
256
+
257
+ if (!inPattern && value !== undefined && value !== 'undefined') {
258
+ focusedParams[key] = value;
259
+ }
260
+ }
238
261
  }
239
262
  }
240
263
 
241
264
  // If there is no `screens` property or no nested state, we return pattern
242
- if (!config.screens || route.state == null) {
265
+ const routeState = getRouteState(route, config);
266
+
267
+ if (!config.screens || routeState == null) {
243
268
  hasNext = false;
244
269
  } else {
245
270
  index =
246
- typeof route.state.index === 'number'
247
- ? route.state.index
248
- : route.state.routes.length - 1;
271
+ typeof routeState.index === 'number'
272
+ ? routeState.index
273
+ : routeState.routes.length - 1;
249
274
 
250
275
  const nextRoute: State['routes'][number] | undefined =
251
- route.state.routes[index];
276
+ routeState.routes[index];
252
277
 
253
278
  if (nextRoute == null) {
254
279
  throw new Error(`Couldn't find a route at index ${index}.`);
@@ -267,14 +292,25 @@ export function getPathFromState<ParamList extends {}>(
267
292
  }
268
293
  }
269
294
 
295
+ const routeState = getRouteState(route, currentOptions[route.name]);
296
+
270
297
  if (currentOptions[route.name] != null) {
271
- path += parts
272
- ?.map(({ segment, param, optional }) => {
298
+ if (parts) {
299
+ let index = 0;
300
+
301
+ for (const { segment, param, optional } of parts) {
302
+ if (index > 0) {
303
+ path += '/';
304
+ }
305
+
306
+ index++;
307
+
273
308
  // We don't know what to show for wildcard patterns
274
309
  // Showing the route name seems ok, though whatever we show here will be incorrect
275
310
  // Since the page doesn't actually exist
276
311
  if (segment === '*') {
277
- return route.name;
312
+ path += route.name;
313
+ continue;
278
314
  }
279
315
 
280
316
  // If the path has a pattern for a param, put the param in the path
@@ -283,46 +319,39 @@ export function getPathFromState<ParamList extends {}>(
283
319
 
284
320
  if (value === undefined && optional) {
285
321
  // Optional params without value assigned in route.params should be ignored
286
- return '';
322
+ continue;
287
323
  }
288
324
 
289
325
  // Valid characters according to
290
326
  // https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 (see pchar definition)
291
- return Array.from(String(value))
292
- .map((char) =>
293
- /[^A-Za-z0-9\-._~!$&'()*+,;=:@]/g.test(char)
294
- ? encodeURIComponent(char)
295
- : char
296
- )
297
- .join('');
327
+ path += encodePathParam(String(value));
328
+ continue;
298
329
  }
299
330
 
300
- return encodeURIComponent(segment);
301
- })
302
- .join('/');
331
+ path += encodeURIComponent(segment);
332
+ }
333
+ }
303
334
  } else {
304
335
  path += encodeURIComponent(route.name);
305
336
  }
306
337
 
307
338
  if (!focusedParams && focusedRoute.params) {
308
- focusedParams = Object.fromEntries(
309
- Object.entries(focusedRoute.params).map(([key, value]) => [
310
- key,
311
- String(value),
312
- ])
313
- );
314
- }
339
+ focusedParams = {};
315
340
 
316
- if (route.state) {
317
- path += '/';
318
- } else if (focusedParams) {
319
- for (const param in focusedParams) {
320
- if (focusedParams[param] === 'undefined') {
321
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
322
- delete focusedParams[param];
341
+ const params = focusedRoute.params as Record<string, unknown>;
342
+
343
+ for (const key in params) {
344
+ const value = String(params[key]);
345
+
346
+ if (value !== 'undefined') {
347
+ focusedParams[key] = value;
323
348
  }
324
349
  }
350
+ }
325
351
 
352
+ if (routeState) {
353
+ path += '/';
354
+ } else if (focusedParams) {
326
355
  const query = queryString.stringify(focusedParams, { sort: false });
327
356
 
328
357
  if (query) {
@@ -330,7 +359,7 @@ export function getPathFromState<ParamList extends {}>(
330
359
  }
331
360
  }
332
361
 
333
- current = route.state;
362
+ current = routeState;
334
363
  }
335
364
 
336
365
  // Include the root path if specified
@@ -399,11 +428,17 @@ const createConfigItem = (
399
428
  const createNormalizedConfigs = (
400
429
  options: PathConfigMap<object>,
401
430
  parts?: PatternPart[]
402
- ): Record<string, ConfigItem> =>
403
- Object.fromEntries(
404
- Object.entries(options).map(([name, c]) => {
405
- const result = createConfigItem(c, parts);
406
-
407
- return [name, result];
408
- })
409
- );
431
+ ): Record<string, ConfigItem> => {
432
+ const configs: Record<string, ConfigItem> = {};
433
+ const screens = options as Record<string, PathConfig<{}> | string>;
434
+
435
+ for (const name in screens) {
436
+ const config = screens[name];
437
+
438
+ if (config !== undefined) {
439
+ configs[name] = createConfigItem(config, parts);
440
+ }
441
+ }
442
+
443
+ return configs;
444
+ };
@@ -52,8 +52,8 @@ export function getPatternParts(path: string): PatternPart[] {
52
52
  if (regexInnerParens) {
53
53
  // The ')' is part of the regex if we're already inside one
54
54
  regexInnerParens--;
55
- current.regex += char;
56
55
  } else {
56
+ current.regex += char;
57
57
  isRegex = false;
58
58
  isParam = false;
59
59
  }
@@ -62,7 +62,7 @@ export function getPatternParts(path: string): PatternPart[] {
62
62
  `Encountered ')' without preceding '(' in path: ${path}`
63
63
  );
64
64
  }
65
- } else if (char === '?') {
65
+ } else if (char === '?' && !isRegex) {
66
66
  if (current.param) {
67
67
  isParam = false;
68
68