@react-navigation/routers 8.0.0-alpha.2 → 8.0.0-alpha.20

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 (42) hide show
  1. package/lib/module/BaseRouter.js +64 -61
  2. package/lib/module/BaseRouter.js.map +1 -1
  3. package/lib/module/CommonActions.js +9 -21
  4. package/lib/module/CommonActions.js.map +1 -1
  5. package/lib/module/DrawerRouter.js +15 -14
  6. package/lib/module/DrawerRouter.js.map +1 -1
  7. package/lib/module/StackRouter.js +287 -166
  8. package/lib/module/StackRouter.js.map +1 -1
  9. package/lib/module/SwitchRouter.js +426 -0
  10. package/lib/module/SwitchRouter.js.map +1 -0
  11. package/lib/module/TabRouter.js +14 -290
  12. package/lib/module/TabRouter.js.map +1 -1
  13. package/lib/typescript/src/BaseRouter.d.ts +10 -8
  14. package/lib/typescript/src/BaseRouter.d.ts.map +1 -1
  15. package/lib/typescript/src/CommonActions.d.ts +42 -39
  16. package/lib/typescript/src/CommonActions.d.ts.map +1 -1
  17. package/lib/typescript/src/DrawerRouter.d.ts +6 -5
  18. package/lib/typescript/src/DrawerRouter.d.ts.map +1 -1
  19. package/lib/typescript/src/StackRouter.d.ts +70 -24
  20. package/lib/typescript/src/StackRouter.d.ts.map +1 -1
  21. package/lib/typescript/src/SwitchRouter.d.ts +69 -0
  22. package/lib/typescript/src/SwitchRouter.d.ts.map +1 -0
  23. package/lib/typescript/src/TabRouter.d.ts +4 -24
  24. package/lib/typescript/src/TabRouter.d.ts.map +1 -1
  25. package/lib/typescript/src/createParamsFromAction.d.ts +2 -2
  26. package/lib/typescript/src/createParamsFromAction.d.ts.map +1 -1
  27. package/lib/typescript/src/createRouteFromAction.d.ts +2 -2
  28. package/lib/typescript/src/createRouteFromAction.d.ts.map +1 -1
  29. package/lib/typescript/src/index.d.ts +9 -9
  30. package/lib/typescript/src/index.d.ts.map +1 -1
  31. package/lib/typescript/src/types.d.ts +20 -30
  32. package/lib/typescript/src/types.d.ts.map +1 -1
  33. package/package.json +8 -8
  34. package/src/BaseRouter.tsx +93 -80
  35. package/src/CommonActions.tsx +44 -66
  36. package/src/DrawerRouter.tsx +28 -22
  37. package/src/StackRouter.tsx +551 -238
  38. package/src/SwitchRouter.tsx +718 -0
  39. package/src/TabRouter.tsx +24 -463
  40. package/src/createParamsFromAction.tsx +1 -1
  41. package/src/createRouteFromAction.tsx +1 -1
  42. package/src/types.tsx +39 -41
@@ -45,7 +45,57 @@ export const StackActions = {
45
45
  merge: options?.merge
46
46
  }
47
47
  };
48
+ },
49
+ retain(enable) {
50
+ return {
51
+ type: 'RETAIN',
52
+ payload: {
53
+ enable
54
+ }
55
+ };
56
+ },
57
+ dismiss(count = 1) {
58
+ return {
59
+ type: 'DISMISS',
60
+ payload: {
61
+ count
62
+ }
63
+ };
64
+ }
65
+ };
66
+ function retainRoutes(previousState, nextState) {
67
+ if (!nextState.retainedRouteKeys.length) {
68
+ return nextState;
48
69
  }
70
+ const retainedRouteKeySet = new Set(nextState.retainedRouteKeys);
71
+ const nextRouteKeySet = new Set(nextState.routes.map(route => route.key));
72
+ const retainedRoutes = getActiveRoutes(previousState).filter(route => retainedRouteKeySet.has(route.key) && !nextRouteKeySet.has(route.key));
73
+ if (!retainedRoutes.length) {
74
+ return nextState;
75
+ }
76
+ const removedRetainedRouteKeys = new Set(retainedRoutes.map(route => route.key));
77
+ return getStateWithRoutes(nextState, getActiveRoutes(nextState), getPreloadedRoutes(nextState), retainedRoutes.concat(getRetainedRoutes(nextState).filter(route => !removedRetainedRouteKeys.has(route.key))));
78
+ }
79
+ const getActiveRoutes = state => state.routes.slice(0, state.index + 1);
80
+ const getInactiveRoutes = state => state.routes.slice(state.index + 1);
81
+ const getPreloadedRoutes = state => {
82
+ const retainedRouteKeySet = new Set(state.retainedRouteKeys);
83
+ return getInactiveRoutes(state).filter(route => !retainedRouteKeySet.has(route.key));
84
+ };
85
+ const getRetainedRoutes = state => {
86
+ const retainedRouteKeySet = new Set(state.retainedRouteKeys);
87
+ return getInactiveRoutes(state).filter(route => retainedRouteKeySet.has(route.key));
88
+ };
89
+ const getStateWithRoutes = (state, activeRoutes, preloadedRoutes = getPreloadedRoutes(state), retainedRoutes = getRetainedRoutes(state)) => {
90
+ const routeKeySet = new Set(activeRoutes.map(route => route.key));
91
+ const retainedRouteKeySet = new Set(state.retainedRouteKeys);
92
+ const filteredRetainedRoutes = retainedRoutes.filter(route => !routeKeySet.has(route.key));
93
+ const filteredPreloadedRoutes = preloadedRoutes.filter(route => !retainedRouteKeySet.has(route.key) && !routeKeySet.has(route.key));
94
+ return {
95
+ ...state,
96
+ index: activeRoutes.length - 1,
97
+ routes: activeRoutes.concat(filteredRetainedRoutes, filteredPreloadedRoutes)
98
+ };
49
99
  };
50
100
  export function StackRouter(options) {
51
101
  const router = {
@@ -56,13 +106,16 @@ export function StackRouter(options) {
56
106
  routeParamList
57
107
  }) {
58
108
  const initialRouteName = options.initialRouteName !== undefined && routeNames.includes(options.initialRouteName) ? options.initialRouteName : routeNames[0];
109
+ if (initialRouteName == null) {
110
+ throw new Error("Couldn't initialize the stack navigator. Have you defined any screens?");
111
+ }
59
112
  return {
60
113
  stale: false,
61
114
  type: 'stack',
62
115
  key: `stack-${nanoid()}`,
63
116
  index: 0,
64
117
  routeNames,
65
- preloadedRoutes: [],
118
+ retainedRouteKeys: [],
66
119
  routes: [{
67
120
  key: `${initialRouteName}-${nanoid()}`,
68
121
  name: initialRouteName,
@@ -86,30 +139,28 @@ export function StackRouter(options) {
86
139
  ...route.params
87
140
  } : route.params
88
141
  }));
89
- const preloadedRoutes = state.preloadedRoutes?.filter(route => routeNames.includes(route.name)).map(route => ({
90
- ...route,
91
- key: route.key || `${route.name}-${nanoid()}`,
92
- params: routeParamList[route.name] !== undefined ? {
93
- ...routeParamList[route.name],
94
- ...route.params
95
- } : route.params
96
- })) ?? [];
97
142
  if (routes.length === 0) {
98
- const initialRouteName = options.initialRouteName !== undefined ? options.initialRouteName : routeNames[0];
143
+ const initialRouteName = options.initialRouteName !== undefined && routeNames.includes(options.initialRouteName) ? options.initialRouteName : routeNames[0];
144
+ if (initialRouteName == null) {
145
+ throw new Error("Couldn't initialize the stack navigator. Have you defined any screens?");
146
+ }
99
147
  routes.push({
100
148
  key: `${initialRouteName}-${nanoid()}`,
101
149
  name: initialRouteName,
102
150
  params: routeParamList[initialRouteName]
103
151
  });
104
152
  }
153
+ const index = Math.min(Math.max(state.index ?? routes.length - 1, 0), routes.length - 1);
154
+ const routeKeys = new Set(routes.map(route => route.key));
155
+ const retainedRouteKeys = state.retainedRouteKeys?.filter(key => routeKeys.has(key)) ?? [];
105
156
  return {
106
157
  stale: false,
107
158
  type: 'stack',
108
159
  key: `stack-${nanoid()}`,
109
- index: routes.length - 1,
160
+ index,
110
161
  routeNames,
111
162
  routes,
112
- preloadedRoutes
163
+ retainedRouteKeys
113
164
  };
114
165
  },
115
166
  getStateForRouteNamesChange(state, {
@@ -117,45 +168,53 @@ export function StackRouter(options) {
117
168
  routeParamList,
118
169
  routeKeyChanges
119
170
  }) {
120
- const routes = state.routes.filter(route => routeNames.includes(route.name) && !routeKeyChanges.includes(route.name));
171
+ const routes = getActiveRoutes(state).filter(route => routeNames.includes(route.name) && !routeKeyChanges.includes(route.name));
172
+ const preloadedRoutes = getPreloadedRoutes(state).filter(route => routeNames.includes(route.name) && !routeKeyChanges.includes(route.name));
173
+ const retainedRoutes = getRetainedRoutes(state).filter(route => routeNames.includes(route.name) && !routeKeyChanges.includes(route.name));
121
174
  if (routes.length === 0) {
122
175
  const initialRouteName = options.initialRouteName !== undefined && routeNames.includes(options.initialRouteName) ? options.initialRouteName : routeNames[0];
176
+ if (initialRouteName == null) {
177
+ throw new Error("Couldn't initialize the stack navigator. Have you defined any screens?");
178
+ }
123
179
  routes.push({
124
180
  key: `${initialRouteName}-${nanoid()}`,
125
181
  name: initialRouteName,
126
182
  params: routeParamList[initialRouteName]
127
183
  });
128
184
  }
129
- return {
185
+ const routeKeys = new Set([...routes, ...preloadedRoutes, ...retainedRoutes].map(route => route.key));
186
+ const retainedRouteKeys = state.retainedRouteKeys.filter(key => routeKeys.has(key));
187
+ return getStateWithRoutes({
130
188
  ...state,
131
189
  routeNames,
132
- routes,
133
- index: Math.min(state.index, routes.length - 1)
134
- };
190
+ retainedRouteKeys
191
+ }, routes, preloadedRoutes, retainedRoutes);
135
192
  },
136
193
  getStateForRouteFocus(state, key) {
137
- const index = state.routes.findIndex(r => r.key === key);
138
- if (index === -1 || index === state.index) {
194
+ const activeRoutes = getActiveRoutes(state);
195
+ const route = state.routes.find(route => route.key === key);
196
+ if (route == null || route === activeRoutes.at(-1)) {
139
197
  return state;
140
198
  }
141
- return {
142
- ...state,
143
- index,
144
- routes: state.routes.slice(0, index + 1)
145
- };
199
+ const index = activeRoutes.indexOf(route);
200
+ const routes = index === -1 ? activeRoutes.concat(route) : activeRoutes.slice(0, index + 1);
201
+ return retainRoutes(state, getStateWithRoutes(state, routes));
146
202
  },
147
203
  getStateForAction(state, action, options) {
148
204
  const {
149
205
  routeParamList
150
206
  } = options;
207
+ const routes = getActiveRoutes(state);
208
+ const preloadedRoutes = getPreloadedRoutes(state);
209
+ const inactiveRoutes = getInactiveRoutes(state);
151
210
  switch (action.type) {
152
211
  case 'REPLACE':
153
212
  {
154
- const currentIndex = action.target === state.key && action.source ? state.routes.findIndex(r => r.key === action.source) : state.index;
155
- if (currentIndex === -1) {
213
+ if (!state.routeNames.includes(action.payload.name)) {
156
214
  return null;
157
215
  }
158
- if (!state.routeNames.includes(action.payload.name)) {
216
+ const currentIndex = action.source !== undefined ? routes.findIndex(r => r.key === action.source) : state.index;
217
+ if (currentIndex === -1) {
159
218
  return null;
160
219
  }
161
220
  const getId = options.routeGetIdList[action.payload.name];
@@ -163,8 +222,8 @@ export function StackRouter(options) {
163
222
  params: action.payload.params
164
223
  });
165
224
 
166
- // Re-use preloaded route if available
167
- let route = state.preloadedRoutes.find(route => route.name === action.payload.name && id === getId?.({
225
+ // Re-use inactive route if available
226
+ let route = inactiveRoutes.find(route => route.name === action.payload.name && id === getId?.({
168
227
  params: route.params
169
228
  }));
170
229
  if (!route) {
@@ -172,12 +231,19 @@ export function StackRouter(options) {
172
231
  action,
173
232
  routeParamList
174
233
  });
234
+ } else {
235
+ const params = createParamsFromAction({
236
+ action,
237
+ routeParamList
238
+ });
239
+ if (route.params !== params) {
240
+ route = {
241
+ ...route,
242
+ params
243
+ };
244
+ }
175
245
  }
176
- return {
177
- ...state,
178
- routes: state.routes.map((r, i) => i === currentIndex ? route : r),
179
- preloadedRoutes: state.preloadedRoutes.filter(r => r.key !== route.key)
180
- };
246
+ return retainRoutes(state, getStateWithRoutes(state, routes.map((r, i) => i === currentIndex ? route : r), preloadedRoutes.filter(r => r.key !== route.key)));
181
247
  }
182
248
  case 'PUSH':
183
249
  case 'NAVIGATE':
@@ -189,17 +255,31 @@ export function StackRouter(options) {
189
255
  const id = getId?.({
190
256
  params: action.payload.params
191
257
  });
258
+ let remainingRoutes = routes;
192
259
  let route;
193
260
  if (action.type === 'NAVIGATE') {
194
- const currentRoute = state.routes[state.index];
195
- if (id !== undefined) {
196
- if (currentRoute.name === action.payload.name && id === getId?.({
197
- params: currentRoute.params
198
- })) {
199
- route = currentRoute;
200
- } else if (action.payload.pop) {
201
- for (let i = state.routes.length - 1; i >= 0; i--) {
202
- const r = state.routes[i];
261
+ const currentIndex = action.source !== undefined ? routes.findIndex(r => r.key === action.source) : state.index;
262
+ if (currentIndex === -1) {
263
+ return null;
264
+ }
265
+
266
+ /**
267
+ * When navigating,
268
+ * - If the current route matches the destination, reuse it.
269
+ * - Or, if a route matching the destination exists after the current, reuse it.
270
+ * - Remove all non-matching routes after the current.
271
+ */
272
+ remainingRoutes = routes.slice(0, currentIndex + 1);
273
+ route = routes.find((route, i) => i >= currentIndex && route.name === action.payload.name && (id === undefined || id === getId?.({
274
+ params: route.params
275
+ })));
276
+ if (!route && action.payload.pop) {
277
+ if (id !== undefined) {
278
+ for (let i = remainingRoutes.length - 1; i >= 0; i--) {
279
+ const r = remainingRoutes[i];
280
+ if (r == null) {
281
+ throw new Error(`Couldn't find a route at index ${i}.`);
282
+ }
203
283
  if (r.name !== action.payload.name) {
204
284
  continue;
205
285
  }
@@ -211,12 +291,16 @@ export function StackRouter(options) {
211
291
  }
212
292
  if (r.history?.length) {
213
293
  for (let j = r.history.length - 1; j >= 0; j--) {
214
- if (r.history[j].type === 'params' && id === getId?.({
215
- params: r.history[j].params
294
+ const historyEntry = r.history[j];
295
+ if (historyEntry == null) {
296
+ throw new Error(`Couldn't find a route history entry at index ${j}.`);
297
+ }
298
+ if (historyEntry.type === 'params' && id === getId?.({
299
+ params: historyEntry.params
216
300
  })) {
217
301
  route = {
218
302
  ...r,
219
- params: r.history[j].params,
303
+ params: historyEntry.params,
220
304
  history: r.history.slice(0, j)
221
305
  };
222
306
  break;
@@ -227,18 +311,13 @@ export function StackRouter(options) {
227
311
  }
228
312
  }
229
313
  }
230
- }
231
- } else {
232
- // If the route matches the current one, then navigate to it
233
- if (action.payload.name === currentRoute.name) {
234
- route = currentRoute;
235
- } else if (action.payload.pop) {
236
- route = state.routes.findLast(route => route.name === action.payload.name);
314
+ } else {
315
+ route = remainingRoutes.findLast(route => route.name === action.payload.name);
237
316
  }
238
317
  }
239
318
  }
240
319
  if (!route) {
241
- route = state.preloadedRoutes.find(route => route.name === action.payload.name && id === getId?.({
320
+ route = inactiveRoutes.find(route => route.name === action.payload.name && id === getId?.({
242
321
  params: route.params
243
322
  }));
244
323
  }
@@ -255,98 +334,107 @@ export function StackRouter(options) {
255
334
  routeParamList
256
335
  });
257
336
  }
258
- let routes;
337
+ let nextRoutes;
259
338
  if (route) {
260
339
  if (action.type === 'NAVIGATE' && action.payload.pop) {
261
- routes = [];
340
+ nextRoutes = [];
262
341
 
263
- // Get all routes until the matching one
264
- for (const r of state.routes) {
342
+ // Get all routes before the matching one
343
+ for (const r of remainingRoutes) {
265
344
  if (r.key === route.key) {
266
- routes.push({
267
- ...route,
268
- path: action.payload.path !== undefined ? action.payload.path : route.path,
269
- params
270
- });
271
345
  break;
272
346
  }
273
- routes.push(r);
347
+ nextRoutes.push(r);
274
348
  }
275
349
  } else {
276
- routes = state.routes.filter(r => r.key !== route.key);
277
- routes.push({
278
- ...route,
279
- path: action.type === 'NAVIGATE' && action.payload.path !== undefined ? action.payload.path : route.path,
280
- params
281
- });
350
+ nextRoutes = remainingRoutes.filter(r => r.key !== route.key);
282
351
  }
352
+ nextRoutes.push({
353
+ ...route,
354
+ path: action.type === 'NAVIGATE' && action.payload.path !== undefined ? action.payload.path : route.path,
355
+ params
356
+ });
283
357
  } else {
284
- routes = [...state.routes, {
358
+ nextRoutes = [...remainingRoutes, {
285
359
  key: `${action.payload.name}-${nanoid()}`,
286
360
  name: action.payload.name,
287
361
  path: action.type === 'NAVIGATE' ? action.payload.path : undefined,
288
362
  params
289
363
  }];
290
364
  }
291
- return {
292
- ...state,
293
- index: routes.length - 1,
294
- preloadedRoutes: state.preloadedRoutes.filter(route => routes[routes.length - 1].key !== route.key),
295
- routes
296
- };
365
+ return retainRoutes(state, getStateWithRoutes(state, nextRoutes));
297
366
  }
298
367
  case 'POP':
299
368
  {
300
- let currentIndex = action.target === state.key && action.source ? state.routes.findIndex(r => r.key === action.source) : state.index;
301
- let route = state.routes[currentIndex];
369
+ let currentIndex = action.source !== undefined ? routes.findIndex(r => r.key === action.source) : state.index;
370
+ if (currentIndex === -1) {
371
+ return null;
372
+ }
373
+ let route = routes[currentIndex];
374
+ if (route == null) {
375
+ throw new Error(`Couldn't find a route at index ${currentIndex}.`);
376
+ }
302
377
 
303
378
  /**
304
379
  * When popping entries,
305
380
  * - Pop entries from route.history
306
- * - Pop routes from state.routes
381
+ * - Pop the route itself and continue from the route below it
307
382
  *
308
383
  * Repeat until:
309
384
  * - We have popped the amount of items in count
310
385
  * - There are no more items to pop
386
+ *
387
+ * Routes above the current route are removed.
311
388
  */
312
389
  if (route.history?.length || currentIndex > 0) {
313
390
  let count = action.payload.count;
314
- let routes = state.routes;
315
391
  while (count > 0 && (route.history?.length || currentIndex > 0)) {
316
392
  if (route.history?.length) {
317
- routes = [...routes];
318
393
  const end = Math.min(count, route.history.length);
319
394
  const last = route.history.at(-end);
320
395
  const history = route.history.slice(0, -end);
321
396
  const removed = route.history.length - history.length;
322
397
  count = count - removed;
323
- routes[currentIndex] = {
398
+ route = {
324
399
  ...route,
325
400
  params: last?.type === 'params' ? last.params : route.params,
326
401
  history
327
402
  };
328
403
  }
329
404
  if (currentIndex > 0 && count > 0) {
330
- const length = routes.length;
331
- const end = Math.max(currentIndex - count + 1, 1);
332
- routes = routes.slice(0, end).concat(routes.slice(currentIndex + 1));
333
- const removed = length - routes.length;
334
- count = count - removed;
335
- currentIndex = routes.length - 1;
336
- route = routes[currentIndex];
405
+ count = count - 1;
406
+ currentIndex = currentIndex - 1;
407
+ const currentRoute = routes[currentIndex];
408
+ if (currentRoute == null) {
409
+ throw new Error(`Couldn't find a route at index ${currentIndex}.`);
410
+ }
411
+ route = currentRoute;
337
412
  }
338
413
  }
339
- return {
340
- ...state,
341
- index: routes.length - 1,
342
- routes
343
- };
414
+ const nextRoutes = routes.slice(0, currentIndex + 1);
415
+ if (route !== routes[currentIndex]) {
416
+ nextRoutes[currentIndex] = route;
417
+ }
418
+ return retainRoutes(state, getStateWithRoutes(state, nextRoutes));
344
419
  }
345
420
  return null;
346
421
  }
422
+ case 'DISMISS':
423
+ {
424
+ const currentIndex = action.source !== undefined ? routes.findIndex(route => route.key === action.source) : state.index;
425
+ if (currentIndex === -1 || routes.length === 1 || action.payload.count < 1) {
426
+ return null;
427
+ }
428
+ const count = Math.min(action.payload.count, currentIndex + 1, routes.length - 1);
429
+ const firstIndex = currentIndex - count + 1;
430
+ return retainRoutes(state, getStateWithRoutes(state, routes.slice(0, firstIndex).concat(routes.slice(currentIndex + 1))));
431
+ }
347
432
  case 'POP_TO_TOP':
348
433
  {
349
- let route = state.routes[0];
434
+ let route = routes[0];
435
+ if (route == null) {
436
+ throw new Error("Couldn't find a route at index 0.");
437
+ }
350
438
  if (state.index > 0 || route.history?.length) {
351
439
  if (route.history?.length) {
352
440
  route = {
@@ -354,21 +442,17 @@ export function StackRouter(options) {
354
442
  history: undefined
355
443
  };
356
444
  }
357
- return {
358
- ...state,
359
- index: 0,
360
- routes: [route]
361
- };
445
+ return retainRoutes(state, getStateWithRoutes(state, [route]));
362
446
  }
363
447
  return null;
364
448
  }
365
449
  case 'POP_TO':
366
450
  {
367
- const currentIndex = action.target === state.key && action.source ? state.routes.findLastIndex(r => r.key === action.source) : state.index;
368
- if (currentIndex === -1) {
451
+ if (!state.routeNames.includes(action.payload.name)) {
369
452
  return null;
370
453
  }
371
- if (!state.routeNames.includes(action.payload.name)) {
454
+ const currentIndex = action.source !== undefined ? routes.findIndex(r => r.key === action.source) : state.index;
455
+ if (currentIndex === -1) {
372
456
  return null;
373
457
  }
374
458
 
@@ -381,7 +465,10 @@ export function StackRouter(options) {
381
465
  });
382
466
  if (id !== undefined) {
383
467
  for (let i = currentIndex; i >= 0; i--) {
384
- const r = state.routes[i];
468
+ const r = routes[i];
469
+ if (r == null) {
470
+ throw new Error(`Couldn't find a route at index ${i}.`);
471
+ }
385
472
  if (r.name !== action.payload.name) {
386
473
  continue;
387
474
  }
@@ -393,8 +480,12 @@ export function StackRouter(options) {
393
480
  }
394
481
  if (r.history?.length) {
395
482
  for (let j = r.history.length - 1; j >= 0; j--) {
396
- if (r.history[j].type === 'params' && id === getId?.({
397
- params: r.history[j].params
483
+ const historyEntry = r.history[j];
484
+ if (historyEntry == null) {
485
+ throw new Error(`Couldn't find a route history entry at index ${j}.`);
486
+ }
487
+ if (historyEntry.type === 'params' && id === getId?.({
488
+ params: historyEntry.params
398
489
  })) {
399
490
  index = i;
400
491
  historyIndex = j;
@@ -406,11 +497,22 @@ export function StackRouter(options) {
406
497
  }
407
498
  }
408
499
  }
409
- } else if (state.routes[currentIndex].name === action.payload.name) {
410
- index = currentIndex;
411
500
  } else {
501
+ const currentRoute = routes[currentIndex];
502
+ if (currentRoute == null) {
503
+ throw new Error(`Couldn't find a route at index ${currentIndex}.`);
504
+ }
505
+ if (currentRoute.name === action.payload.name) {
506
+ index = currentIndex;
507
+ }
508
+ }
509
+ if (id == null && index === -1) {
412
510
  for (let i = currentIndex; i >= 0; i--) {
413
- if (state.routes[i].name === action.payload.name) {
511
+ const route = routes[i];
512
+ if (route == null) {
513
+ throw new Error(`Couldn't find a route at index ${i}.`);
514
+ }
515
+ if (route.name === action.payload.name) {
414
516
  index = i;
415
517
  break;
416
518
  }
@@ -419,8 +521,8 @@ export function StackRouter(options) {
419
521
 
420
522
  // If the route doesn't exist, remove the current route and add the new one
421
523
  if (index === -1) {
422
- // Re-use preloaded route if available
423
- let route = state.preloadedRoutes.find(route => route.name === action.payload.name && id === getId?.({
524
+ // Re-use inactive route if available
525
+ let route = inactiveRoutes.find(route => route.name === action.payload.name && id === getId?.({
424
526
  params: route.params
425
527
  }));
426
528
  if (!route) {
@@ -428,20 +530,33 @@ export function StackRouter(options) {
428
530
  action,
429
531
  routeParamList
430
532
  });
533
+ } else {
534
+ const params = createParamsFromAction({
535
+ action,
536
+ routeParamList
537
+ });
538
+ if (route.params !== params) {
539
+ route = {
540
+ ...route,
541
+ params
542
+ };
543
+ }
431
544
  }
432
- const routes = state.routes.slice(0, currentIndex).concat(route);
433
- return {
434
- ...state,
435
- index: routes.length - 1,
436
- routes,
437
- preloadedRoutes: state.preloadedRoutes.filter(r => r.key !== route.key)
438
- };
545
+ const nextRoutes = routes.slice(0, currentIndex).concat(route);
546
+ return retainRoutes(state, getStateWithRoutes(state, nextRoutes, preloadedRoutes.filter(r => r.key !== route.key)));
547
+ }
548
+ const route = routes[index];
549
+ if (route == null) {
550
+ throw new Error(`Couldn't find a route at index ${index}.`);
439
551
  }
440
- const route = state.routes[index];
441
552
  let baseParams = route.params;
442
553
  let history = route.history;
443
554
  if (historyIndex !== undefined && route.history) {
444
- baseParams = route.history[historyIndex].params;
555
+ const historyEntry = route.history[historyIndex];
556
+ if (historyEntry == null) {
557
+ throw new Error(`Couldn't find a route history entry at index ${historyIndex}.`);
558
+ }
559
+ baseParams = historyEntry.params;
445
560
  history = route.history.slice(0, historyIndex);
446
561
  }
447
562
  let params;
@@ -457,18 +572,14 @@ export function StackRouter(options) {
457
572
  routeParamList
458
573
  });
459
574
  }
460
- return {
461
- ...state,
462
- index,
463
- routes: [...state.routes.slice(0, index), params !== baseParams || historyIndex !== undefined ? {
464
- ...route,
465
- params,
466
- history
467
- } : state.routes[index]]
468
- };
575
+ return retainRoutes(state, getStateWithRoutes(state, [...routes.slice(0, index), params !== baseParams || historyIndex !== undefined ? {
576
+ ...route,
577
+ params,
578
+ history
579
+ } : route]));
469
580
  }
470
581
  case 'GO_BACK':
471
- if (state.index > 0 || state.routes[state.index].history?.length) {
582
+ {
472
583
  return router.getStateForAction(state, {
473
584
  type: 'POP',
474
585
  payload: {
@@ -478,54 +589,64 @@ export function StackRouter(options) {
478
589
  source: action.source
479
590
  }, options);
480
591
  }
481
- return null;
592
+ case 'RETAIN':
593
+ {
594
+ const route = routes[state.index];
595
+ if (route == null) {
596
+ throw new Error(`Couldn't find a route at index ${state.index}.`);
597
+ }
598
+ const routeKey = action.source ?? route.key;
599
+ if (!state.routes.some(route => route.key === routeKey)) {
600
+ return null;
601
+ }
602
+ if (action.payload.enable) {
603
+ if (state.retainedRouteKeys.includes(routeKey)) {
604
+ return state;
605
+ }
606
+ return {
607
+ ...state,
608
+ retainedRouteKeys: [...state.retainedRouteKeys, routeKey]
609
+ };
610
+ }
611
+ if (!state.retainedRouteKeys.includes(routeKey)) {
612
+ return state;
613
+ }
614
+ return getStateWithRoutes({
615
+ ...state,
616
+ retainedRouteKeys: state.retainedRouteKeys.filter(key => key !== routeKey)
617
+ }, routes, preloadedRoutes.filter(route => route.key !== routeKey));
618
+ }
482
619
  case 'PRELOAD':
483
620
  {
621
+ if (!state.routeNames.includes(action.payload.name)) {
622
+ return null;
623
+ }
484
624
  const getId = options.routeGetIdList[action.payload.name];
485
625
  const id = getId?.({
486
626
  params: action.payload.params
487
627
  });
488
- let route;
489
- if (id !== undefined) {
490
- route = state.routes.find(route => route.name === action.payload.name && id === getId?.({
491
- params: route.params
492
- }));
493
- }
628
+ const params = createParamsFromAction({
629
+ action,
630
+ routeParamList
631
+ });
632
+ const route = preloadedRoutes.findLast(route => route.name === action.payload.name && id === getId?.({
633
+ params: route.params
634
+ }));
494
635
  if (route) {
495
- return {
496
- ...state,
497
- routes: state.routes.map(r => {
498
- if (r.key !== route?.key) {
499
- return r;
500
- }
501
- return {
502
- ...r,
503
- params: createParamsFromAction({
504
- action,
505
- routeParamList
506
- })
507
- };
508
- })
509
- };
510
- } else {
511
- return {
512
- ...state,
513
- preloadedRoutes: state.preloadedRoutes.filter(r => r.name !== action.payload.name || id !== getId?.({
514
- params: r.params
515
- })).concat(createRouteFromAction({
516
- action,
517
- routeParamList
518
- }))
519
- };
636
+ return getStateWithRoutes(state, routes, preloadedRoutes.map(r => r.key === route.key && r.params !== params ? {
637
+ ...r,
638
+ params
639
+ } : r));
520
640
  }
641
+ return getStateWithRoutes(state, routes, preloadedRoutes.concat(createRouteFromAction({
642
+ action,
643
+ routeParamList
644
+ })));
521
645
  }
522
646
  default:
523
647
  return BaseRouter.getStateForAction(state, action);
524
648
  }
525
649
  },
526
- getRoutesFromState(state) {
527
- return [...state.routes, ...state.preloadedRoutes];
528
- },
529
650
  actionCreators: StackActions
530
651
  };
531
652
  return router;