@react-navigation/core 8.0.0-alpha.32 → 8.0.0-alpha.34

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 (78) hide show
  1. package/lib/module/NavigationBuilderContext.js +8 -13
  2. package/lib/module/NavigationBuilderContext.js.map +1 -1
  3. package/lib/module/NavigationIndependentTree.js +42 -15
  4. package/lib/module/NavigationIndependentTree.js.map +1 -1
  5. package/lib/module/getPathFromState.js +41 -16
  6. package/lib/module/getPathFromState.js.map +1 -1
  7. package/lib/module/getPatternParts.js +43 -12
  8. package/lib/module/getPatternParts.js.map +1 -1
  9. package/lib/module/getStateFromPath.js +176 -110
  10. package/lib/module/getStateFromPath.js.map +1 -1
  11. package/lib/module/index.js +1 -0
  12. package/lib/module/index.js.map +1 -1
  13. package/lib/module/useDescriptors.js +2 -2
  14. package/lib/module/useDescriptors.js.map +1 -1
  15. package/lib/module/useFocusedListenersChildrenAdapter.js +2 -2
  16. package/lib/module/useFocusedListenersChildrenAdapter.js.map +1 -1
  17. package/lib/module/useNavigationBuilder.js +2 -2
  18. package/lib/module/useNavigationBuilder.js.map +1 -1
  19. package/lib/module/useNavigationCache.js +3 -3
  20. package/lib/module/useNavigationCache.js.map +1 -1
  21. package/lib/module/useNavigationHelpers.js +2 -2
  22. package/lib/module/useNavigationHelpers.js.map +1 -1
  23. package/lib/module/useOnAction.js +6 -4
  24. package/lib/module/useOnAction.js.map +1 -1
  25. package/lib/module/useOnGetNavigation.js +2 -2
  26. package/lib/module/useOnGetNavigation.js.map +1 -1
  27. package/lib/module/useOnGetState.js +2 -2
  28. package/lib/module/useOnGetState.js.map +1 -1
  29. package/lib/module/useOnPreventRemove.js +2 -2
  30. package/lib/module/useOnPreventRemove.js.map +1 -1
  31. package/lib/module/useOnRouteFocus.js +2 -2
  32. package/lib/module/useOnRouteFocus.js.map +1 -1
  33. package/lib/module/useOptionsGetters.js +2 -4
  34. package/lib/module/useOptionsGetters.js.map +1 -1
  35. package/lib/module/useScheduleUpdate.js +2 -2
  36. package/lib/module/useScheduleUpdate.js.map +1 -1
  37. package/lib/typescript/src/NavigationBuilderContext.d.ts +8 -5
  38. package/lib/typescript/src/NavigationBuilderContext.d.ts.map +1 -1
  39. package/lib/typescript/src/NavigationIndependentTree.d.ts.map +1 -1
  40. package/lib/typescript/src/getPathFromState.d.ts.map +1 -1
  41. package/lib/typescript/src/getPatternParts.d.ts +4 -2
  42. package/lib/typescript/src/getPatternParts.d.ts.map +1 -1
  43. package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
  44. package/lib/typescript/src/index.d.ts +1 -0
  45. package/lib/typescript/src/index.d.ts.map +1 -1
  46. package/lib/typescript/src/types.d.ts +1 -1
  47. package/lib/typescript/src/types.d.ts.map +1 -1
  48. package/lib/typescript/src/useDescriptors.d.ts.map +1 -1
  49. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  50. package/lib/typescript/src/useOnAction.d.ts.map +1 -1
  51. package/lib/typescript/src/useOnGetNavigation.d.ts.map +1 -1
  52. package/lib/typescript/src/useOnGetState.d.ts.map +1 -1
  53. package/lib/typescript/src/useOnPreventRemove.d.ts.map +1 -1
  54. package/lib/typescript/src/useOnRouteFocus.d.ts.map +1 -1
  55. package/lib/typescript/src/useOptionsGetters.d.ts.map +1 -1
  56. package/lib/typescript/src/utilities.d.ts +3 -0
  57. package/lib/typescript/src/utilities.d.ts.map +1 -1
  58. package/package.json +2 -3
  59. package/src/NavigationBuilderContext.tsx +21 -17
  60. package/src/NavigationIndependentTree.tsx +48 -22
  61. package/src/getPathFromState.tsx +63 -15
  62. package/src/getPatternParts.tsx +75 -14
  63. package/src/getStateFromPath.tsx +251 -114
  64. package/src/index.tsx +1 -0
  65. package/src/types.tsx +1 -1
  66. package/src/useDescriptors.tsx +2 -1
  67. package/src/useFocusedListenersChildrenAdapter.tsx +2 -2
  68. package/src/useNavigationBuilder.tsx +2 -4
  69. package/src/useNavigationCache.tsx +3 -3
  70. package/src/useNavigationHelpers.tsx +2 -2
  71. package/src/useOnAction.tsx +6 -5
  72. package/src/useOnGetNavigation.tsx +3 -2
  73. package/src/useOnGetState.tsx +4 -2
  74. package/src/useOnPreventRemove.tsx +4 -2
  75. package/src/useOnRouteFocus.tsx +2 -4
  76. package/src/useOptionsGetters.tsx +3 -2
  77. package/src/useScheduleUpdate.tsx +2 -2
  78. package/src/utilities.tsx +5 -2
@@ -1,7 +1,11 @@
1
1
  import type { NavigationState, PartialState } from '@react-navigation/routers';
2
2
  import queryString from 'query-string';
3
3
 
4
- import { getPatternParts, type PatternPart } from './getPatternParts';
4
+ import {
5
+ combinePatternParts,
6
+ getPatternParts,
7
+ type PatternPart,
8
+ } from './getPatternParts';
5
9
  import { getStateFromRouteParams } from './getStateFromRouteParams';
6
10
  import type { PathConfig, PathConfigMap } from './types';
7
11
  import { validatePathConfig } from './validatePathConfig';
@@ -228,16 +232,16 @@ export function getPathFromState<ParamList extends {}>(
228
232
  const value = params[key];
229
233
 
230
234
  if (value === undefined) {
231
- let optional = false;
235
+ let canBeOmitted = false;
232
236
 
233
237
  for (const part of ownParts) {
234
- if (part.param === key) {
235
- optional = part.optional === true;
238
+ if (part.name === key) {
239
+ canBeOmitted = part.optional === true || part.repeat != null;
236
240
  break;
237
241
  }
238
242
  }
239
243
 
240
- if (optional) {
244
+ if (canBeOmitted) {
241
245
  continue;
242
246
  }
243
247
  }
@@ -252,12 +256,12 @@ export function getPathFromState<ParamList extends {}>(
252
256
  const claimedParams = new Set<string>();
253
257
 
254
258
  for (const part of ownParts) {
255
- if (part.param && part.param in currentParams) {
256
- const value = currentParams[part.param];
259
+ if (part.name && part.name in currentParams) {
260
+ const value = currentParams[part.name];
257
261
 
258
262
  if (value !== undefined) {
259
263
  partValues.set(part, value);
260
- claimedParams.add(part.param);
264
+ claimedParams.add(part.name);
261
265
  }
262
266
  }
263
267
  }
@@ -283,7 +287,7 @@ export function getPathFromState<ParamList extends {}>(
283
287
  }
284
288
 
285
289
  for (const part of ownParts) {
286
- if (part.param && !partValues.has(part)) {
290
+ if (part.name && !partValues.has(part)) {
287
291
  partValues.set(part, undefined);
288
292
  }
289
293
  }
@@ -326,7 +330,7 @@ export function getPathFromState<ParamList extends {}>(
326
330
  let index = 0;
327
331
 
328
332
  for (const part of parts) {
329
- const { segment, param, optional } = part;
333
+ const { segment, name, optional, repeat } = part;
330
334
 
331
335
  if (index > 0) {
332
336
  path += '/';
@@ -343,7 +347,7 @@ export function getPathFromState<ParamList extends {}>(
343
347
  }
344
348
 
345
349
  // If the path has a pattern for a param, put the param in the path
346
- if (param) {
350
+ if (name) {
347
351
  const value = partValues.get(part);
348
352
 
349
353
  if (value === undefined && optional) {
@@ -351,6 +355,42 @@ export function getPathFromState<ParamList extends {}>(
351
355
  continue;
352
356
  }
353
357
 
358
+ if (repeat) {
359
+ if (value === null) {
360
+ throw new Error(
361
+ `The path pattern '${segment}' does not allow null for param '${name}'.`
362
+ );
363
+ }
364
+
365
+ if (Array.isArray(value)) {
366
+ throw new Error(
367
+ `The path pattern '${segment}' requires a string for param '${name}'.`
368
+ );
369
+ }
370
+
371
+ const values =
372
+ value === undefined || value === '' ? [] : value.split('/');
373
+
374
+ if (repeat === 'one-or-more' && values.length === 0) {
375
+ throw new Error(
376
+ `The path pattern '${segment}' requires at least one value for param '${name}'.`
377
+ );
378
+ }
379
+
380
+ if (values.some((item) => item.length === 0)) {
381
+ throw new Error(
382
+ `The path pattern '${segment}' does not allow empty values for param '${name}'.`
383
+ );
384
+ }
385
+
386
+ if (values.length === 0) {
387
+ path = path.replace(/\/$/, '');
388
+ }
389
+
390
+ path += values.map(encodePathParam).join('/');
391
+ continue;
392
+ }
393
+
354
394
  // Valid characters according to
355
395
  // https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 (see pchar definition)
356
396
  path += encodePathParam(String(value));
@@ -397,8 +437,13 @@ export function getPathFromState<ParamList extends {}>(
397
437
  }
398
438
 
399
439
  // Remove multiple as well as trailing slashes
400
- path = path.replace(/\/+/g, '/');
401
- path = path.length > 1 ? path.replace(/\/$/, '') : path;
440
+ if (path.includes('//')) {
441
+ path = path.replace(/\/+/g, '/');
442
+ }
443
+
444
+ if (path.length > 1 && path.endsWith('/')) {
445
+ path = path.slice(0, -1);
446
+ }
402
447
 
403
448
  // If path doesn't start with a slash, add it
404
449
  // This makes sure that history.pushState will update the path correctly instead of appending
@@ -417,7 +462,10 @@ const createConfigItem = (
417
462
  const ownParts = getPatternParts(config);
418
463
 
419
464
  if (parentParts) {
420
- return { parts: [...parentParts, ...ownParts], ownParts };
465
+ return {
466
+ parts: combinePatternParts(parentParts, ownParts),
467
+ ownParts,
468
+ };
421
469
  }
422
470
 
423
471
  return { parts: ownParts, ownParts };
@@ -434,7 +482,7 @@ const createConfigItem = (
434
482
  const ownParts = config.path ? getPatternParts(config.path) : [];
435
483
  const parts =
436
484
  config.exact !== true
437
- ? [...(parentParts || []), ...ownParts]
485
+ ? combinePatternParts(parentParts || [], ownParts)
438
486
  : ownParts.length
439
487
  ? ownParts
440
488
  : undefined;
@@ -1,8 +1,11 @@
1
+ export type PatternPartRepeat = 'zero-or-more' | 'one-or-more';
2
+
1
3
  export type PatternPart = {
2
4
  segment: string;
3
- param?: string;
4
- regex?: string;
5
+ name?: string;
6
+ pattern?: string;
5
7
  optional?: boolean;
8
+ repeat?: PatternPartRepeat;
6
9
  };
7
10
 
8
11
  /**
@@ -15,6 +18,7 @@ export function getPatternParts(path: string): PatternPart[] {
15
18
 
16
19
  let isRegex = false;
17
20
  let isParam = false;
21
+ let hasRepeat = false;
18
22
  let isEscaped = false;
19
23
  let isInCharClass = false;
20
24
  let regexInnerParens = 0;
@@ -55,7 +59,7 @@ export function getPatternParts(path: string): PatternPart[] {
55
59
  // The ')' is part of the regex if we're already inside one
56
60
  regexInnerParens--;
57
61
  } else {
58
- current.regex += char;
62
+ current.pattern += char;
59
63
  isRegex = false;
60
64
  isParam = false;
61
65
  }
@@ -65,7 +69,7 @@ export function getPatternParts(path: string): PatternPart[] {
65
69
  );
66
70
  }
67
71
  } else if (char === '?' && !isRegex) {
68
- if (current.param) {
72
+ if (current.name) {
69
73
  isParam = false;
70
74
 
71
75
  current.optional = true;
@@ -74,6 +78,27 @@ export function getPatternParts(path: string): PatternPart[] {
74
78
  `Encountered '?' without preceding ':' in path: ${path}`
75
79
  );
76
80
  }
81
+ } else if (
82
+ current.name &&
83
+ !isRegex &&
84
+ (char === '*' || char === '+') &&
85
+ (path[i + 1] == null ||
86
+ path[i + 1] === '/' ||
87
+ path[i + 1] === '?' ||
88
+ path[i + 1] === '*' ||
89
+ path[i + 1] === '+')
90
+ ) {
91
+ if (
92
+ current.optional ||
93
+ (path[i + 1] !== undefined && path[i + 1] !== '/')
94
+ ) {
95
+ throw new Error(`Cannot combine path param modifiers in path: ${path}`);
96
+ }
97
+
98
+ isParam = false;
99
+
100
+ current.repeat = char === '*' ? 'zero-or-more' : 'one-or-more';
101
+ hasRepeat = true;
77
102
  } else if (char == null || (char === '/' && !isRegex)) {
78
103
  isParam = false;
79
104
 
@@ -84,12 +109,12 @@ export function getPatternParts(path: string): PatternPart[] {
84
109
  continue;
85
110
  }
86
111
 
87
- if (current.param) {
88
- current.param = current.param.replace(/^:/, '');
112
+ if (current.name) {
113
+ current.name = current.name.replace(/^:/, '');
89
114
  }
90
115
 
91
- if (current.regex) {
92
- current.regex = current.regex.replace(/^\(/, '').replace(/\)$/, '');
116
+ if (current.pattern) {
117
+ current.pattern = current.pattern.replace(/^\(/, '').replace(/\)$/, '');
93
118
  }
94
119
 
95
120
  parts.push(current);
@@ -102,8 +127,8 @@ export function getPatternParts(path: string): PatternPart[] {
102
127
  }
103
128
 
104
129
  if (isRegex) {
105
- current.regex = current.regex || '';
106
- current.regex += char;
130
+ current.pattern = current.pattern || '';
131
+ current.pattern += char;
107
132
 
108
133
  // Track escapes and character classes so parens inside them are not special
109
134
  if (isEscaped) {
@@ -118,8 +143,8 @@ export function getPatternParts(path: string): PatternPart[] {
118
143
  }
119
144
 
120
145
  if (isParam && !isRegex) {
121
- current.param = current.param || '';
122
- current.param += char;
146
+ current.name = current.name || '';
147
+ current.name += char;
123
148
  }
124
149
  }
125
150
 
@@ -127,7 +152,7 @@ export function getPatternParts(path: string): PatternPart[] {
127
152
  throw new Error(`Could not find closing ')' in path: ${path}`);
128
153
  }
129
154
 
130
- const params = parts.map((part) => part.param).filter(Boolean);
155
+ const params = parts.map((part) => part.name).filter(Boolean);
131
156
 
132
157
  for (const [index, param] of params.entries()) {
133
158
  if (params.indexOf(param) !== index) {
@@ -135,13 +160,49 @@ export function getPatternParts(path: string): PatternPart[] {
135
160
  }
136
161
  }
137
162
 
163
+ if (hasRepeat) {
164
+ validateRepeatedParts(parts, path);
165
+ }
166
+
138
167
  return parts;
139
168
  }
140
169
 
170
+ function validateRepeatedParts(parts: PatternPart[], path: string) {
171
+ let variablePart: PatternPart | undefined;
172
+
173
+ for (const part of parts) {
174
+ if (!part.name && part.segment !== '*') {
175
+ variablePart = undefined;
176
+ continue;
177
+ }
178
+
179
+ if (!part.repeat && !part.optional && part.segment !== '*') {
180
+ continue;
181
+ }
182
+
183
+ if (variablePart && (variablePart.repeat || part.repeat)) {
184
+ throw new Error(
185
+ `A repeated param must be separated from optional, repeated, or wildcard segments by a static segment: ${path}`
186
+ );
187
+ }
188
+
189
+ variablePart = part;
190
+ }
191
+ }
192
+
141
193
  export function combinePatternParts<T extends PatternPart>(
142
194
  parentParts: T[],
143
195
  parts: T[],
144
196
  exact = false
145
197
  ): T[] {
146
- return exact ? parts : [...parentParts, ...parts];
198
+ const combined = exact ? parts : [...parentParts, ...parts];
199
+
200
+ if (combined.some((part) => part.repeat)) {
201
+ validateRepeatedParts(
202
+ combined,
203
+ combined.map((part) => part.segment).join('/')
204
+ );
205
+ }
206
+
207
+ return combined;
147
208
  }