@react-navigation/core 7.1.2 → 7.2.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 (33) hide show
  1. package/lib/commonjs/arrayStartsWith.js +16 -0
  2. package/lib/commonjs/arrayStartsWith.js.map +1 -0
  3. package/lib/commonjs/getPathFromState.js +50 -40
  4. package/lib/commonjs/getPathFromState.js.map +1 -1
  5. package/lib/commonjs/getPatternParts.js +105 -0
  6. package/lib/commonjs/getPatternParts.js.map +1 -0
  7. package/lib/commonjs/getStateFromPath.js +124 -101
  8. package/lib/commonjs/getStateFromPath.js.map +1 -1
  9. package/lib/module/arrayStartsWith.js +12 -0
  10. package/lib/module/arrayStartsWith.js.map +1 -0
  11. package/lib/module/getPathFromState.js +50 -40
  12. package/lib/module/getPathFromState.js.map +1 -1
  13. package/lib/module/getPatternParts.js +101 -0
  14. package/lib/module/getPatternParts.js.map +1 -0
  15. package/lib/module/getStateFromPath.js +124 -101
  16. package/lib/module/getStateFromPath.js.map +1 -1
  17. package/lib/typescript/commonjs/src/arrayStartsWith.d.ts +5 -0
  18. package/lib/typescript/commonjs/src/arrayStartsWith.d.ts.map +1 -0
  19. package/lib/typescript/commonjs/src/getPathFromState.d.ts.map +1 -1
  20. package/lib/typescript/commonjs/src/getPatternParts.d.ts +11 -0
  21. package/lib/typescript/commonjs/src/getPatternParts.d.ts.map +1 -0
  22. package/lib/typescript/commonjs/src/getStateFromPath.d.ts.map +1 -1
  23. package/lib/typescript/module/src/arrayStartsWith.d.ts +5 -0
  24. package/lib/typescript/module/src/arrayStartsWith.d.ts.map +1 -0
  25. package/lib/typescript/module/src/getPathFromState.d.ts.map +1 -1
  26. package/lib/typescript/module/src/getPatternParts.d.ts +11 -0
  27. package/lib/typescript/module/src/getPatternParts.d.ts.map +1 -0
  28. package/lib/typescript/module/src/getStateFromPath.d.ts.map +1 -1
  29. package/package.json +2 -2
  30. package/src/arrayStartsWith.tsx +10 -0
  31. package/src/getPathFromState.tsx +61 -58
  32. package/src/getPatternParts.tsx +126 -0
  33. package/src/getStateFromPath.tsx +156 -156
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Compare two arrays to check if the first array starts with the second array.
3
+ */
4
+ export declare function arrayStartsWith<T>(array: T[], start: T[]): boolean;
5
+ //# sourceMappingURL=arrayStartsWith.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arrayStartsWith.d.ts","sourceRoot":"","sources":["../../../../src/arrayStartsWith.tsx"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,WAMxD"}
@@ -1 +1 @@
1
- {"version":3,"file":"getPathFromState.d.ts","sourceRoot":"","sources":["../../../../src/getPathFromState.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EAEb,MAAM,2BAA2B,CAAC;AAGnC,OAAO,KAAK,EAAc,aAAa,EAAE,MAAM,SAAS,CAAC;AAGzD,KAAK,OAAO,CAAC,SAAS,SAAS,EAAE,IAAI;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;CACnC,CAAC;AAEF,KAAK,KAAK,GAAG,eAAe,GAAG,IAAI,CAAC,YAAY,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC,CAAC;AA0C5E;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,SAAS,EAAE,EACnD,KAAK,EAAE,KAAK,EACZ,OAAO,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,GAC3B,MAAM,CA6KR"}
1
+ {"version":3,"file":"getPathFromState.d.ts","sourceRoot":"","sources":["../../../../src/getPathFromState.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EAEb,MAAM,2BAA2B,CAAC;AAInC,OAAO,KAAK,EAAc,aAAa,EAAE,MAAM,SAAS,CAAC;AAGzD,KAAK,OAAO,CAAC,SAAS,SAAS,EAAE,IAAI;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;CACnC,CAAC;AAEF,KAAK,KAAK,GAAG,eAAe,GAAG,IAAI,CAAC,YAAY,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC,CAAC;AA0C5E;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,SAAS,EAAE,EACnD,KAAK,EAAE,KAAK,EACZ,OAAO,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,GAC3B,MAAM,CAgLR"}
@@ -0,0 +1,11 @@
1
+ export type PatternPart = {
2
+ segment: string;
3
+ param?: string;
4
+ regex?: string;
5
+ optional?: boolean;
6
+ };
7
+ /**
8
+ * Parse a path into an array of parts with information about each segment.
9
+ */
10
+ export declare function getPatternParts(path: string): PatternPart[];
11
+ //# sourceMappingURL=getPatternParts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getPatternParts.d.ts","sourceRoot":"","sources":["../../../../src/getPatternParts.tsx"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAmH3D"}
@@ -1 +1 @@
1
- {"version":3,"file":"getStateFromPath.d.ts","sourceRoot":"","sources":["../../../../src/getStateFromPath.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,eAAe,EACf,YAAY,EACb,MAAM,2BAA2B,CAAC;AAKnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG7C,KAAK,OAAO,CAAC,SAAS,SAAS,EAAE,IAAI;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;CACnC,CAAC;AAkBF,KAAK,WAAW,GAAG,YAAY,CAAC,eAAe,CAAC,GAAG;IACjD,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB,CAAC;AAcF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,SAAS,EAAE,EACnD,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,GAC3B,WAAW,GAAG,SAAS,CA4FzB"}
1
+ {"version":3,"file":"getStateFromPath.d.ts","sourceRoot":"","sources":["../../../../src/getStateFromPath.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,eAAe,EACf,YAAY,EACb,MAAM,2BAA2B,CAAC;AAQnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG7C,KAAK,OAAO,CAAC,SAAS,SAAS,EAAE,IAAI;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;CACnC,CAAC;AAkBF,KAAK,WAAW,GAAG,YAAY,CAAC,eAAe,CAAC,GAAG;IACjD,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB,CAAC;AAcF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,SAAS,EAAE,EACnD,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,GAC3B,WAAW,GAAG,SAAS,CAqFzB"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@react-navigation/core",
3
3
  "description": "Core utilities for building navigators",
4
- "version": "7.1.2",
4
+ "version": "7.2.1",
5
5
  "keywords": [
6
6
  "react",
7
7
  "react-native",
@@ -95,5 +95,5 @@
95
95
  ]
96
96
  ]
97
97
  },
98
- "gitHead": "a299403df5bed6c32a343fbbf7ce8e008d9a8b30"
98
+ "gitHead": "ade825bda66345baace3dfdb13aeb51c58875454"
99
99
  }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Compare two arrays to check if the first array starts with the second array.
3
+ */
4
+ export function arrayStartsWith<T>(array: T[], start: T[]) {
5
+ if (start.length > array.length) {
6
+ return false;
7
+ }
8
+
9
+ return start.every((it, index) => it === array[index]);
10
+ }
@@ -5,6 +5,7 @@ import type {
5
5
  } from '@react-navigation/routers';
6
6
  import * as queryString from 'query-string';
7
7
 
8
+ import { getPatternParts, type PatternPart } from './getPatternParts';
8
9
  import type { PathConfig, PathConfigMap } from './types';
9
10
  import { validatePathConfig } from './validatePathConfig';
10
11
 
@@ -16,10 +17,10 @@ type Options<ParamList extends {}> = {
16
17
 
17
18
  type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
18
19
 
19
- type StringifyConfig = Record<string, (value: any) => string>;
20
+ type StringifyConfig = Record<string, (value: unknown) => string>;
20
21
 
21
22
  type ConfigItem = {
22
- pattern?: string;
23
+ parts?: PatternPart[];
23
24
  stringify?: StringifyConfig;
24
25
  screens?: Record<string, ConfigItem>;
25
26
  };
@@ -91,7 +92,7 @@ export function getPathFromState<ParamList extends {}>(
91
92
  ): string {
92
93
  if (state == null) {
93
94
  throw Error(
94
- "Got 'undefined' for the navigation state. You must pass a valid state object."
95
+ `Got '${String(state)}' for the navigation state. You must pass a valid state object.`
95
96
  );
96
97
  }
97
98
 
@@ -104,7 +105,7 @@ export function getPathFromState<ParamList extends {}>(
104
105
  let path = '/';
105
106
  let current: State | undefined = state;
106
107
 
107
- const allParams: Record<string, any> = {};
108
+ const allParams: Record<string, string> = {};
108
109
 
109
110
  while (current) {
110
111
  let index = typeof current.index === 'number' ? current.index : 0;
@@ -112,19 +113,20 @@ export function getPathFromState<ParamList extends {}>(
112
113
  state?: State;
113
114
  };
114
115
 
115
- let pattern: string | undefined;
116
+ let parts: PatternPart[] | undefined;
116
117
 
117
- let focusedParams: Record<string, any> | undefined;
118
- const focusedRoute = getActiveRoute(state);
118
+ let focusedParams: Record<string, string> | undefined;
119
119
  let currentOptions = configs;
120
120
 
121
+ const focusedRoute = getActiveRoute(state);
122
+
121
123
  // Keep all the route names that appeared during going deeper in config in case the pattern is resolved to undefined
122
124
  const nestedRouteNames = [];
123
125
 
124
126
  let hasNext = true;
125
127
 
126
128
  while (route.name in currentOptions && hasNext) {
127
- pattern = currentOptions[route.name].pattern;
129
+ parts = currentOptions[route.name].parts;
128
130
 
129
131
  nestedRouteNames.push(route.name);
130
132
 
@@ -138,7 +140,7 @@ export function getPathFromState<ParamList extends {}>(
138
140
  ])
139
141
  );
140
142
 
141
- if (pattern) {
143
+ if (parts?.length) {
142
144
  Object.assign(allParams, currentParams);
143
145
  }
144
146
 
@@ -147,17 +149,15 @@ export function getPathFromState<ParamList extends {}>(
147
149
  // We save it here since it's been stringified already
148
150
  focusedParams = { ...currentParams };
149
151
 
150
- pattern
151
- ?.split('/')
152
- .filter((p) => p.startsWith(':'))
152
+ parts
153
153
  // eslint-disable-next-line no-loop-func
154
- .forEach((p) => {
155
- const name = getParamName(p);
156
-
157
- // Remove the params present in the pattern since we'll only use the rest for query string
158
- if (focusedParams) {
159
- // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
160
- delete focusedParams[name];
154
+ ?.forEach(({ param }) => {
155
+ if (param) {
156
+ // Remove the params present in the pattern since we'll only use the rest for query string
157
+ if (focusedParams) {
158
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
159
+ delete focusedParams[param];
160
+ }
161
161
  }
162
162
  });
163
163
  }
@@ -186,28 +186,21 @@ export function getPathFromState<ParamList extends {}>(
186
186
  }
187
187
  }
188
188
 
189
- if (pattern === undefined) {
190
- pattern = nestedRouteNames.join('/');
191
- }
192
-
193
189
  if (currentOptions[route.name] !== undefined) {
194
- path += pattern
195
- .split('/')
196
- .map((p) => {
197
- const name = getParamName(p);
198
-
190
+ path += parts
191
+ ?.map(({ segment, param, optional }) => {
199
192
  // We don't know what to show for wildcard patterns
200
193
  // Showing the route name seems ok, though whatever we show here will be incorrect
201
194
  // Since the page doesn't actually exist
202
- if (p === '*') {
195
+ if (segment === '*') {
203
196
  return route.name;
204
197
  }
205
198
 
206
199
  // If the path has a pattern for a param, put the param in the path
207
- if (p.startsWith(':')) {
208
- const value = allParams[name];
200
+ if (param) {
201
+ const value = allParams[param];
209
202
 
210
- if (value === undefined && p.endsWith('?')) {
203
+ if (value === undefined && optional) {
211
204
  // Optional params without value assigned in route.params should be ignored
212
205
  return '';
213
206
  }
@@ -220,15 +213,20 @@ export function getPathFromState<ParamList extends {}>(
220
213
  );
221
214
  }
222
215
 
223
- return encodeURIComponent(p);
216
+ return encodeURIComponent(segment);
224
217
  })
225
218
  .join('/');
226
219
  } else {
227
220
  path += encodeURIComponent(route.name);
228
221
  }
229
222
 
230
- if (!focusedParams) {
231
- focusedParams = focusedRoute.params;
223
+ if (!focusedParams && focusedRoute.params) {
224
+ focusedParams = Object.fromEntries(
225
+ Object.entries(focusedRoute.params).map(([key, value]) => [
226
+ key,
227
+ String(value),
228
+ ])
229
+ );
232
230
  }
233
231
 
234
232
  if (route.state) {
@@ -251,36 +249,37 @@ export function getPathFromState<ParamList extends {}>(
251
249
  current = route.state;
252
250
  }
253
251
 
252
+ // Include the root path if specified
253
+ if (options?.path) {
254
+ path = `${options.path}/${path}`;
255
+ }
256
+
254
257
  // Remove multiple as well as trailing slashes
255
258
  path = path.replace(/\/+/g, '/');
256
259
  path = path.length > 1 ? path.replace(/\/$/, '') : path;
257
260
 
258
- // Include the root path if specified
259
- if (options?.path) {
260
- path = joinPaths(options.path, path);
261
+ // If path doesn't start with a slash, add it
262
+ // This makes sure that history.pushState will update the path correctly instead of appending
263
+ if (!path.startsWith('/')) {
264
+ path = `/${path}`;
261
265
  }
262
266
 
263
267
  return path;
264
268
  }
265
269
 
266
- const getParamName = (pattern: string) =>
267
- pattern.replace(/^:/, '').replace(/\?$/, '');
268
-
269
- const joinPaths = (...paths: string[]): string =>
270
- ([] as string[])
271
- .concat(...paths.map((p) => p.split('/')))
272
- .filter(Boolean)
273
- .join('/');
274
-
275
270
  const createConfigItem = (
276
271
  config: PathConfig<object> | string,
277
- parentPattern?: string
272
+ parentParts?: PatternPart[]
278
273
  ): ConfigItem => {
279
274
  if (typeof config === 'string') {
280
275
  // If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern
281
- const pattern = parentPattern ? joinPaths(parentPattern, config) : config;
276
+ const parts = getPatternParts(config);
277
+
278
+ if (parentParts) {
279
+ return { parts: [...parentParts, ...parts] };
280
+ }
282
281
 
283
- return { pattern };
282
+ return { parts };
284
283
  }
285
284
 
286
285
  if (config.exact && config.path === undefined) {
@@ -291,18 +290,22 @@ const createConfigItem = (
291
290
 
292
291
  // If an object is specified as the value (e.g. Foo: { ... }),
293
292
  // It can have `path` property and `screens` prop which has nested configs
294
- const pattern =
293
+ const parts =
295
294
  config.exact !== true
296
- ? joinPaths(parentPattern || '', config.path || '')
297
- : config.path || '';
295
+ ? [
296
+ ...(parentParts || []),
297
+ ...(config.path ? getPatternParts(config.path) : []),
298
+ ]
299
+ : config.path
300
+ ? getPatternParts(config.path)
301
+ : undefined;
298
302
 
299
303
  const screens = config.screens
300
- ? createNormalizedConfigs(config.screens, pattern)
304
+ ? createNormalizedConfigs(config.screens, parts)
301
305
  : undefined;
302
306
 
303
307
  return {
304
- // Normalize pattern to remove any leading, trailing slashes, duplicate slashes etc.
305
- pattern: pattern?.split('/').filter(Boolean).join('/'),
308
+ parts,
306
309
  stringify: config.stringify,
307
310
  screens,
308
311
  };
@@ -310,11 +313,11 @@ const createConfigItem = (
310
313
 
311
314
  const createNormalizedConfigs = (
312
315
  options: PathConfigMap<object>,
313
- pattern?: string
316
+ parts?: PatternPart[]
314
317
  ): Record<string, ConfigItem> =>
315
318
  Object.fromEntries(
316
319
  Object.entries(options).map(([name, c]) => {
317
- const result = createConfigItem(c, pattern);
320
+ const result = createConfigItem(c, parts);
318
321
 
319
322
  return [name, result];
320
323
  })
@@ -0,0 +1,126 @@
1
+ export type PatternPart = {
2
+ segment: string;
3
+ param?: string;
4
+ regex?: string;
5
+ optional?: boolean;
6
+ };
7
+
8
+ /**
9
+ * Parse a path into an array of parts with information about each segment.
10
+ */
11
+ export function getPatternParts(path: string): PatternPart[] {
12
+ const parts: PatternPart[] = [];
13
+
14
+ let current: PatternPart = { segment: '' };
15
+
16
+ let isRegex = false;
17
+ let isParam = false;
18
+ let regexInnerParens = 0;
19
+
20
+ // One extra iteration to add the last character
21
+ for (let i = 0; i <= path.length; i++) {
22
+ const char = path[i];
23
+
24
+ if (char != null) {
25
+ current.segment += char;
26
+ }
27
+
28
+ if (char === ':') {
29
+ // The segment must start with a colon if it's a param
30
+ if (current.segment === ':') {
31
+ isParam = true;
32
+ } else {
33
+ throw new Error(
34
+ `Encountered ':' in the middle of a segment in path: ${path}`
35
+ );
36
+ }
37
+ } else if (char === '(') {
38
+ if (isParam) {
39
+ if (isRegex) {
40
+ // The '(' is part of the regex if we're already inside one
41
+ regexInnerParens++;
42
+ } else {
43
+ isRegex = true;
44
+ }
45
+ } else {
46
+ throw new Error(
47
+ `Encountered '(' without preceding ':' in path: ${path}`
48
+ );
49
+ }
50
+ } else if (char === ')') {
51
+ if (isParam && isRegex) {
52
+ if (regexInnerParens) {
53
+ // The ')' is part of the regex if we're already inside one
54
+ regexInnerParens--;
55
+ current.regex += char;
56
+ } else {
57
+ isRegex = false;
58
+ isParam = false;
59
+ }
60
+ } else {
61
+ throw new Error(
62
+ `Encountered ')' without preceding '(' in path: ${path}`
63
+ );
64
+ }
65
+ } else if (char === '?') {
66
+ if (current.param) {
67
+ isParam = false;
68
+
69
+ current.optional = true;
70
+ } else {
71
+ throw new Error(
72
+ `Encountered '?' without preceding ':' in path: ${path}`
73
+ );
74
+ }
75
+ } else if (char == null || (char === '/' && !isRegex)) {
76
+ isParam = false;
77
+
78
+ // Remove trailing slash from segment
79
+ current.segment = current.segment.replace(/\/$/, '');
80
+
81
+ if (current.segment === '') {
82
+ continue;
83
+ }
84
+
85
+ if (current.param) {
86
+ current.param = current.param.replace(/^:/, '');
87
+ }
88
+
89
+ if (current.regex) {
90
+ current.regex = current.regex.replace(/^\(/, '').replace(/\)$/, '');
91
+ }
92
+
93
+ parts.push(current);
94
+
95
+ if (char == null) {
96
+ break;
97
+ }
98
+
99
+ current = { segment: '' };
100
+ }
101
+
102
+ if (isRegex) {
103
+ current.regex = current.regex || '';
104
+ current.regex += char;
105
+ }
106
+
107
+ if (isParam && !isRegex) {
108
+ current.param = current.param || '';
109
+ current.param += char;
110
+ }
111
+ }
112
+
113
+ if (isRegex) {
114
+ throw new Error(`Could not find closing ')' in path: ${path}`);
115
+ }
116
+
117
+ const params = parts.map((part) => part.param).filter(Boolean);
118
+
119
+ for (const [index, param] of params.entries()) {
120
+ if (params.indexOf(param) !== index) {
121
+ throw new Error(`Duplicate param name '${param}' found in path: ${path}`);
122
+ }
123
+ }
124
+
125
+ return parts;
126
+ }