@react-navigation/routers 8.0.0-alpha.13 → 8.0.0-alpha.15

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.
package/src/TabRouter.tsx CHANGED
@@ -1,44 +1,16 @@
1
- import { nanoid } from 'nanoid/non-secure';
2
-
3
- import { BaseRouter } from './BaseRouter';
4
- import { createParamsFromAction } from './createParamsFromAction';
1
+ import {
2
+ type SwitchActionType as TabActionType,
3
+ SwitchRouter,
4
+ type SwitchRouterOptions as TabRouterOptions,
5
+ } from './SwitchRouter';
5
6
  import type {
6
7
  CommonNavigationAction,
7
- DefaultRouterOptions,
8
8
  NavigationState,
9
9
  ParamListBase,
10
- PartialState,
11
- Route,
12
10
  Router,
13
11
  } from './types';
14
12
 
15
- export type TabActionType = {
16
- type: 'JUMP_TO';
17
- payload: { name: string; params?: object | undefined };
18
- source?: string | undefined;
19
- target?: string | undefined;
20
- };
21
-
22
- export type BackBehavior =
23
- | 'firstRoute'
24
- | 'initialRoute'
25
- | 'order'
26
- | 'history'
27
- | 'fullHistory'
28
- | 'none';
29
-
30
- export type TabRouterOptions = DefaultRouterOptions & {
31
- /**
32
- * Control how going back should behave
33
- * - `firstRoute` - return to the first defined route
34
- * - `initialRoute` - return to the route from `initialRouteName`
35
- * - `order` - return to the route defined before the focused route
36
- * - `history` - return to last visited route; if the same route is visited multiple times, the older entries are dropped from the history
37
- * - `fullHistory` - return to last visited route; doesn't drop duplicate entries unlike `history` - matches behavior of web pages
38
- * - `none` - do not handle going back
39
- */
40
- backBehavior?: BackBehavior | undefined;
41
- };
13
+ export type { TabActionType, TabRouterOptions };
42
14
 
43
15
  export type TabNavigationState<ParamList extends ParamListBase> = Omit<
44
16
  NavigationState<ParamList>,
@@ -74,8 +46,6 @@ export type TabActionHelpers<ParamList extends ParamListBase> = {
74
46
  ): void;
75
47
  };
76
48
 
77
- const TYPE_ROUTE = 'route' as const;
78
-
79
49
  export const TabActions = {
80
50
  jumpTo(name: string, params?: object) {
81
51
  return {
@@ -85,513 +55,43 @@ export const TabActions = {
85
55
  },
86
56
  };
87
57
 
88
- const getRouteHistory = (
89
- routes: Route<string>[],
90
- index: number,
91
- backBehavior: BackBehavior,
92
- initialRouteName: string | undefined
93
- ) => {
94
- const route = routes[index];
95
-
96
- if (route == null) {
97
- throw new Error(`Couldn't find a route at index ${index}.`);
98
- }
99
-
100
- const history = [
101
- {
102
- type: TYPE_ROUTE,
103
- key: route.key,
104
- },
105
- ];
106
-
107
- let initialRouteIndex;
108
-
109
- switch (backBehavior) {
110
- case 'order':
111
- for (let i = index; i > 0; i--) {
112
- const route = routes[i - 1];
113
-
114
- if (route == null) {
115
- throw new Error(`Couldn't find a route at index ${i - 1}.`);
116
- }
117
-
118
- history.unshift({
119
- type: TYPE_ROUTE,
120
- key: route.key,
121
- });
122
- }
123
- break;
124
- case 'firstRoute':
125
- if (index !== 0) {
126
- const route = routes[0];
127
-
128
- if (route == null) {
129
- throw new Error("Couldn't find a route at index 0.");
130
- }
131
-
132
- history.unshift({
133
- type: TYPE_ROUTE,
134
- key: route.key,
135
- });
136
- }
137
- break;
138
- case 'initialRoute':
139
- initialRouteIndex = routes.findIndex(
140
- (route) => route.name === initialRouteName
141
- );
142
- initialRouteIndex = initialRouteIndex === -1 ? 0 : initialRouteIndex;
143
-
144
- if (index !== initialRouteIndex) {
145
- const route = routes[initialRouteIndex];
146
-
147
- if (route == null) {
148
- throw new Error(
149
- `Couldn't find a route at index ${initialRouteIndex}.`
150
- );
151
- }
152
-
153
- history.unshift({
154
- type: TYPE_ROUTE,
155
- key: route.key,
156
- });
157
- }
158
- break;
159
- case 'history':
160
- case 'fullHistory':
161
- // The history will fill up on navigation
162
- break;
163
- }
164
-
165
- return history;
166
- };
167
-
168
- const changeIndex = (
169
- state: TabNavigationState<ParamListBase>,
170
- index: number,
171
- backBehavior: BackBehavior,
172
- initialRouteName: string | undefined
173
- ) => {
174
- let history = state.history;
175
-
176
- if (backBehavior === 'history' || backBehavior === 'fullHistory') {
177
- const currentRoute = state.routes[index];
178
-
179
- if (currentRoute == null) {
180
- throw new Error(`Couldn't find a route at index ${index}.`);
181
- }
182
-
183
- if (backBehavior === 'history') {
184
- // Remove the existing key from the history to de-duplicate it
185
- history = history.filter((it) =>
186
- it.type === 'route' ? it.key !== currentRoute.key : false
187
- );
188
- } else if (backBehavior === 'fullHistory') {
189
- const lastHistoryRouteItemIndex = history.findLastIndex(
190
- (item) => item.type === 'route'
191
- );
192
-
193
- if (currentRoute.key === history[lastHistoryRouteItemIndex]?.key) {
194
- // For full-history, only remove if it matches the last route
195
- // Useful for drawer, if current route was in history, then drawer state changed
196
- // Then we only need to move the route to the front
197
- history = [
198
- ...history.slice(0, lastHistoryRouteItemIndex),
199
- ...history.slice(lastHistoryRouteItemIndex + 1),
200
- ];
201
- }
202
- }
203
-
204
- history = history.concat({
205
- type: TYPE_ROUTE,
206
- key: currentRoute.key,
207
- params: backBehavior === 'fullHistory' ? currentRoute.params : undefined,
208
- });
209
- } else {
210
- history = getRouteHistory(
211
- state.routes,
212
- index,
213
- backBehavior,
214
- initialRouteName
215
- );
216
- }
58
+ export function TabRouter(
59
+ options: TabRouterOptions
60
+ ): Router<
61
+ TabNavigationState<ParamListBase>,
62
+ TabActionType | CommonNavigationAction
63
+ > {
64
+ const router = SwitchRouter<'tab'>(options);
217
65
 
218
66
  return {
219
- ...state,
220
- index,
221
- history,
222
- };
223
- };
224
-
225
- export function TabRouter({
226
- initialRouteName,
227
- backBehavior = 'firstRoute',
228
- }: TabRouterOptions) {
229
- const router: Router<
230
- TabNavigationState<ParamListBase>,
231
- TabActionType | CommonNavigationAction
232
- > = {
233
- ...BaseRouter,
67
+ ...router,
234
68
 
235
69
  type: 'tab',
236
70
 
237
- getInitialState({ routeNames, routeParamList }) {
238
- const index =
239
- initialRouteName !== undefined && routeNames.includes(initialRouteName)
240
- ? routeNames.indexOf(initialRouteName)
241
- : 0;
242
-
243
- const routes = routeNames.map((name) => ({
244
- name,
245
- key: `${name}-${nanoid()}`,
246
- params: routeParamList[name],
247
- }));
248
-
249
- const history = getRouteHistory(
250
- routes,
251
- index,
252
- backBehavior,
253
- initialRouteName
254
- );
71
+ getInitialState(routerOptions) {
72
+ const state = router.getInitialState(routerOptions);
255
73
 
256
74
  return {
257
- stale: false,
75
+ ...state,
258
76
  type: 'tab',
259
- key: `tab-${nanoid()}`,
260
- index,
261
- routeNames,
262
- history,
263
- routes,
264
- preloadedRouteKeys: [],
77
+ key: `tab-${state.key}`,
265
78
  };
266
79
  },
267
80
 
268
- getRehydratedState(partialState, { routeNames, routeParamList }) {
269
- const state = partialState;
270
-
271
- if (state.stale === false) {
272
- return state;
81
+ getRehydratedState(partialState, routerOptions) {
82
+ if (partialState.stale === false) {
83
+ return partialState;
273
84
  }
274
85
 
275
- const routes = routeNames.map((name) => {
276
- const route = (
277
- state as PartialState<TabNavigationState<ParamListBase>>
278
- ).routes.find((r) => r.name === name);
279
-
280
- return {
281
- ...route,
282
- name,
283
- key:
284
- route && route.name === name && route.key
285
- ? route.key
286
- : `${name}-${nanoid()}`,
287
- params:
288
- routeParamList[name] !== undefined
289
- ? {
290
- ...routeParamList[name],
291
- ...(route ? route.params : undefined),
292
- }
293
- : route
294
- ? route.params
295
- : undefined,
296
- } as Route<string>;
297
- });
298
-
299
- const currentRoute = state.routes[state.index ?? 0];
300
- const index = Math.min(
301
- Math.max(currentRoute ? routeNames.indexOf(currentRoute.name) : 0, 0),
302
- routes.length - 1
303
- );
304
-
305
- const routeKeys = routes.map((route) => route.key);
306
-
307
- const history =
308
- state.history?.filter((it) => routeKeys.includes(it.key)) ?? [];
309
-
310
- return changeIndex(
311
- {
312
- stale: false,
313
- type: 'tab',
314
- key: `tab-${nanoid()}`,
315
- index,
316
- routeNames,
317
- history,
318
- routes,
319
- preloadedRouteKeys:
320
- state.preloadedRouteKeys?.filter((key) =>
321
- routeKeys.includes(key)
322
- ) ?? [],
323
- },
324
- index,
325
- backBehavior,
326
- initialRouteName
327
- );
328
- },
329
-
330
- getStateForRouteNamesChange(
331
- state,
332
- { routeNames, routeParamList, routeKeyChanges }
333
- ) {
334
- const routes = routeNames.map(
335
- (name) =>
336
- state.routes.find(
337
- (r) => r.name === name && !routeKeyChanges.includes(r.name)
338
- ) || {
339
- name,
340
- key: `${name}-${nanoid()}`,
341
- params: routeParamList[name],
342
- }
343
- );
344
-
345
- const currentRoute = state.routes[state.index];
346
-
347
- if (currentRoute == null) {
348
- throw new Error(`Couldn't find a route at index ${state.index}.`);
349
- }
350
-
351
- const index = Math.max(0, routeNames.indexOf(currentRoute.name));
352
-
353
- let history = state.history.filter(
354
- // Type will always be 'route' for tabs, but could be different in a router extending this (e.g. drawer)
355
- (it) => it.type !== 'route' || routes.find((r) => r.key === it.key)
356
- );
357
-
358
- if (!history.length) {
359
- history = getRouteHistory(
360
- routes,
361
- index,
362
- backBehavior,
363
- initialRouteName
364
- );
365
- }
86
+ const state = router.getRehydratedState(partialState, routerOptions);
366
87
 
367
88
  return {
368
89
  ...state,
369
- history,
370
- routeNames,
371
- routes,
372
- index,
90
+ type: 'tab',
91
+ key: `tab-${state.key}`,
373
92
  };
374
93
  },
375
94
 
376
- getStateForRouteFocus(state, key) {
377
- const index = state.routes.findIndex((r) => r.key === key);
378
-
379
- if (index === -1 || index === state.index) {
380
- return state;
381
- }
382
-
383
- return changeIndex(state, index, backBehavior, initialRouteName);
384
- },
385
-
386
- getStateForAction(state, action, { routeParamList, routeGetIdList }) {
387
- switch (action.type) {
388
- case 'JUMP_TO':
389
- case 'NAVIGATE': {
390
- const index = state.routes.findIndex(
391
- (route) => route.name === action.payload.name
392
- );
393
-
394
- if (index === -1) {
395
- return null;
396
- }
397
-
398
- const updatedState = changeIndex(
399
- {
400
- ...state,
401
- routes: state.routes.map((route) => {
402
- if (route.name !== action.payload.name) {
403
- return route;
404
- }
405
-
406
- const getId = routeGetIdList[route.name];
407
-
408
- const currentId = getId?.({ params: route.params });
409
- const nextId = getId?.({ params: action.payload.params });
410
-
411
- const key =
412
- currentId === nextId
413
- ? route.key
414
- : `${route.name}-${nanoid()}`;
415
-
416
- let params;
417
-
418
- if (
419
- action.type === 'NAVIGATE' &&
420
- action.payload.merge &&
421
- currentId === nextId
422
- ) {
423
- params =
424
- action.payload.params !== undefined ||
425
- routeParamList[route.name] !== undefined
426
- ? {
427
- ...routeParamList[route.name],
428
- ...route.params,
429
- ...action.payload.params,
430
- }
431
- : route.params;
432
- } else {
433
- params = createParamsFromAction({ action, routeParamList });
434
- }
435
-
436
- const path =
437
- action.type === 'NAVIGATE' && action.payload.path != null
438
- ? action.payload.path
439
- : route.path;
440
-
441
- return params !== route.params || path !== route.path
442
- ? { ...route, key, path, params }
443
- : route;
444
- }),
445
- },
446
- index,
447
- backBehavior,
448
- initialRouteName
449
- );
450
-
451
- const updatedRoute = updatedState.routes[updatedState.index];
452
-
453
- if (updatedRoute == null) {
454
- throw new Error(
455
- `Couldn't find a route at index ${updatedState.index}.`
456
- );
457
- }
458
-
459
- return {
460
- ...updatedState,
461
- preloadedRouteKeys: updatedState.preloadedRouteKeys.filter(
462
- (key) => key !== updatedRoute.key
463
- ),
464
- };
465
- }
466
-
467
- case 'GO_BACK': {
468
- const focusedRoute = state.routes[state.index];
469
-
470
- if (focusedRoute == null) {
471
- throw new Error(`Couldn't find a route at index ${state.index}.`);
472
- }
473
-
474
- if (state.history.length === 1 && !focusedRoute.history?.length) {
475
- return null;
476
- }
477
-
478
- const lastHistoryItem = state.history[state.history.length - 1];
479
-
480
- if (
481
- lastHistoryItem?.type === 'route' &&
482
- focusedRoute.history?.length
483
- ) {
484
- const routes = [...state.routes];
485
- const history = [...focusedRoute.history];
486
- const last = history.pop();
487
-
488
- routes[state.index] = {
489
- ...focusedRoute,
490
- params:
491
- last?.type === 'params' ? last.params : focusedRoute.params,
492
- history,
493
- };
494
-
495
- return {
496
- ...state,
497
- routes,
498
- };
499
- }
500
-
501
- const previousHistoryItem = state.history[state.history.length - 2];
502
-
503
- if (previousHistoryItem == null) {
504
- return null;
505
- }
506
-
507
- const previousKey = previousHistoryItem.key;
508
- const index = state.routes.findLastIndex(
509
- (route) => route.key === previousKey
510
- );
511
-
512
- if (index === -1) {
513
- return null;
514
- }
515
-
516
- let routes = state.routes;
517
-
518
- const previousRoute = routes[index];
519
-
520
- if (previousRoute == null) {
521
- throw new Error(`Couldn't find a route at index ${index}.`);
522
- }
523
-
524
- if (
525
- backBehavior === 'fullHistory' &&
526
- previousRoute.params !== previousHistoryItem.params
527
- ) {
528
- routes = [...state.routes];
529
- routes[index] = {
530
- ...previousRoute,
531
- params: previousHistoryItem.params,
532
- };
533
- }
534
-
535
- return {
536
- ...state,
537
- routes,
538
- preloadedRouteKeys: state.preloadedRouteKeys.filter(
539
- (key) => key !== previousRoute.key
540
- ),
541
- history: state.history.slice(0, -1),
542
- index,
543
- };
544
- }
545
-
546
- case 'PRELOAD': {
547
- const routeIndex = state.routes.findIndex(
548
- (route) => route.name === action.payload.name
549
- );
550
-
551
- if (routeIndex === -1) {
552
- return null;
553
- }
554
-
555
- const route = state.routes[routeIndex];
556
-
557
- if (route == null) {
558
- throw new Error(`Couldn't find a route at index ${routeIndex}.`);
559
- }
560
-
561
- const getId = routeGetIdList[route.name];
562
-
563
- const currentId = getId?.({ params: route.params });
564
- const nextId = getId?.({ params: action.payload.params });
565
-
566
- const key =
567
- currentId === nextId ? route.key : `${route.name}-${nanoid()}`;
568
-
569
- const params = createParamsFromAction({ action, routeParamList });
570
- const newRoute =
571
- params !== route.params ? { ...route, key, params } : route;
572
-
573
- return {
574
- ...state,
575
- preloadedRouteKeys: state.preloadedRouteKeys
576
- .filter((key) => key !== route.key)
577
- .concat(newRoute.key),
578
- routes: state.routes.map((route, index) =>
579
- index === routeIndex ? newRoute : route
580
- ),
581
- history:
582
- key === route.key
583
- ? state.history
584
- : state.history.filter((record) => record.key !== route.key),
585
- };
586
- }
587
-
588
- default:
589
- return BaseRouter.getStateForAction(state, action);
590
- }
591
- },
592
-
593
95
  actionCreators: TabActions,
594
96
  };
595
-
596
- return router;
597
97
  }