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

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 +724 -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
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 };
18
- source?: string;
19
- target?: string;
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;
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,452 +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 history = [
95
- {
96
- type: TYPE_ROUTE,
97
- key: routes[index].key,
98
- },
99
- ];
100
-
101
- let initialRouteIndex;
102
-
103
- switch (backBehavior) {
104
- case 'order':
105
- for (let i = index; i > 0; i--) {
106
- history.unshift({
107
- type: TYPE_ROUTE,
108
- key: routes[i - 1].key,
109
- });
110
- }
111
- break;
112
- case 'firstRoute':
113
- if (index !== 0) {
114
- history.unshift({
115
- type: TYPE_ROUTE,
116
- key: routes[0].key,
117
- });
118
- }
119
- break;
120
- case 'initialRoute':
121
- initialRouteIndex = routes.findIndex(
122
- (route) => route.name === initialRouteName
123
- );
124
- initialRouteIndex = initialRouteIndex === -1 ? 0 : initialRouteIndex;
125
-
126
- if (index !== initialRouteIndex) {
127
- history.unshift({
128
- type: TYPE_ROUTE,
129
- key: routes[initialRouteIndex].key,
130
- });
131
- }
132
- break;
133
- case 'history':
134
- case 'fullHistory':
135
- // The history will fill up on navigation
136
- break;
137
- }
138
-
139
- return history;
140
- };
141
-
142
- const changeIndex = (
143
- state: TabNavigationState<ParamListBase>,
144
- index: number,
145
- backBehavior: BackBehavior,
146
- initialRouteName: string | undefined
147
- ) => {
148
- let history = state.history;
149
-
150
- if (backBehavior === 'history' || backBehavior === 'fullHistory') {
151
- const currentRoute = state.routes[index];
152
-
153
- if (backBehavior === 'history') {
154
- // Remove the existing key from the history to de-duplicate it
155
- history = history.filter((it) =>
156
- it.type === 'route' ? it.key !== currentRoute.key : false
157
- );
158
- } else if (backBehavior === 'fullHistory') {
159
- const lastHistoryRouteItemIndex = history.findLastIndex(
160
- (item) => item.type === 'route'
161
- );
162
-
163
- if (currentRoute.key === history[lastHistoryRouteItemIndex]?.key) {
164
- // For full-history, only remove if it matches the last route
165
- // Useful for drawer, if current route was in history, then drawer state changed
166
- // Then we only need to move the route to the front
167
- history = [
168
- ...history.slice(0, lastHistoryRouteItemIndex),
169
- ...history.slice(lastHistoryRouteItemIndex + 1),
170
- ];
171
- }
172
- }
173
-
174
- history = history.concat({
175
- type: TYPE_ROUTE,
176
- key: currentRoute.key,
177
- params: backBehavior === 'fullHistory' ? currentRoute.params : undefined,
178
- });
179
- } else {
180
- history = getRouteHistory(
181
- state.routes,
182
- index,
183
- backBehavior,
184
- initialRouteName
185
- );
186
- }
58
+ export function TabRouter(
59
+ options: TabRouterOptions
60
+ ): Router<
61
+ TabNavigationState<ParamListBase>,
62
+ TabActionType | CommonNavigationAction
63
+ > {
64
+ const router = SwitchRouter<'tab'>(options);
187
65
 
188
66
  return {
189
- ...state,
190
- index,
191
- history,
192
- };
193
- };
194
-
195
- export function TabRouter({
196
- initialRouteName,
197
- backBehavior = 'firstRoute',
198
- }: TabRouterOptions) {
199
- const router: Router<
200
- TabNavigationState<ParamListBase>,
201
- TabActionType | CommonNavigationAction
202
- > = {
203
- ...BaseRouter,
67
+ ...router,
204
68
 
205
69
  type: 'tab',
206
70
 
207
- getInitialState({ routeNames, routeParamList }) {
208
- const index =
209
- initialRouteName !== undefined && routeNames.includes(initialRouteName)
210
- ? routeNames.indexOf(initialRouteName)
211
- : 0;
212
-
213
- const routes = routeNames.map((name) => ({
214
- name,
215
- key: `${name}-${nanoid()}`,
216
- params: routeParamList[name],
217
- }));
218
-
219
- const history = getRouteHistory(
220
- routes,
221
- index,
222
- backBehavior,
223
- initialRouteName
224
- );
71
+ getInitialState(routerOptions) {
72
+ const state = router.getInitialState(routerOptions);
225
73
 
226
74
  return {
227
- stale: false,
75
+ ...state,
228
76
  type: 'tab',
229
- key: `tab-${nanoid()}`,
230
- index,
231
- routeNames,
232
- history,
233
- routes,
234
- preloadedRouteKeys: [],
77
+ key: `tab-${state.key}`,
235
78
  };
236
79
  },
237
80
 
238
- getRehydratedState(partialState, { routeNames, routeParamList }) {
239
- const state = partialState;
240
-
241
- if (state.stale === false) {
242
- return state;
81
+ getRehydratedState(partialState, routerOptions) {
82
+ if (partialState.stale === false) {
83
+ return partialState;
243
84
  }
244
85
 
245
- const routes = routeNames.map((name) => {
246
- const route = (
247
- state as PartialState<TabNavigationState<ParamListBase>>
248
- ).routes.find((r) => r.name === name);
249
-
250
- return {
251
- ...route,
252
- name,
253
- key:
254
- route && route.name === name && route.key
255
- ? route.key
256
- : `${name}-${nanoid()}`,
257
- params:
258
- routeParamList[name] !== undefined
259
- ? {
260
- ...routeParamList[name],
261
- ...(route ? route.params : undefined),
262
- }
263
- : route
264
- ? route.params
265
- : undefined,
266
- } as Route<string>;
267
- });
268
-
269
- const index = Math.min(
270
- Math.max(routeNames.indexOf(state.routes[state?.index ?? 0]?.name), 0),
271
- routes.length - 1
272
- );
273
-
274
- const routeKeys = routes.map((route) => route.key);
275
-
276
- const history =
277
- state.history?.filter((it) => routeKeys.includes(it.key)) ?? [];
278
-
279
- return changeIndex(
280
- {
281
- stale: false,
282
- type: 'tab',
283
- key: `tab-${nanoid()}`,
284
- index,
285
- routeNames,
286
- history,
287
- routes,
288
- preloadedRouteKeys:
289
- state.preloadedRouteKeys?.filter((key) =>
290
- routeKeys.includes(key)
291
- ) ?? [],
292
- },
293
- index,
294
- backBehavior,
295
- initialRouteName
296
- );
297
- },
298
-
299
- getStateForRouteNamesChange(
300
- state,
301
- { routeNames, routeParamList, routeKeyChanges }
302
- ) {
303
- const routes = routeNames.map(
304
- (name) =>
305
- state.routes.find(
306
- (r) => r.name === name && !routeKeyChanges.includes(r.name)
307
- ) || {
308
- name,
309
- key: `${name}-${nanoid()}`,
310
- params: routeParamList[name],
311
- }
312
- );
313
-
314
- const index = Math.max(
315
- 0,
316
- routeNames.indexOf(state.routes[state.index].name)
317
- );
318
-
319
- let history = state.history.filter(
320
- // Type will always be 'route' for tabs, but could be different in a router extending this (e.g. drawer)
321
- (it) => it.type !== 'route' || routes.find((r) => r.key === it.key)
322
- );
323
-
324
- if (!history.length) {
325
- history = getRouteHistory(
326
- routes,
327
- index,
328
- backBehavior,
329
- initialRouteName
330
- );
331
- }
86
+ const state = router.getRehydratedState(partialState, routerOptions);
332
87
 
333
88
  return {
334
89
  ...state,
335
- history,
336
- routeNames,
337
- routes,
338
- index,
90
+ type: 'tab',
91
+ key: `tab-${state.key}`,
339
92
  };
340
93
  },
341
94
 
342
- getStateForRouteFocus(state, key) {
343
- const index = state.routes.findIndex((r) => r.key === key);
344
-
345
- if (index === -1 || index === state.index) {
346
- return state;
347
- }
348
-
349
- return changeIndex(state, index, backBehavior, initialRouteName);
350
- },
351
-
352
- getStateForAction(state, action, { routeParamList, routeGetIdList }) {
353
- switch (action.type) {
354
- case 'JUMP_TO':
355
- case 'NAVIGATE': {
356
- const index = state.routes.findIndex(
357
- (route) => route.name === action.payload.name
358
- );
359
-
360
- if (index === -1) {
361
- return null;
362
- }
363
-
364
- const updatedState = changeIndex(
365
- {
366
- ...state,
367
- routes: state.routes.map((route) => {
368
- if (route.name !== action.payload.name) {
369
- return route;
370
- }
371
-
372
- const getId = routeGetIdList[route.name];
373
-
374
- const currentId = getId?.({ params: route.params });
375
- const nextId = getId?.({ params: action.payload.params });
376
-
377
- const key =
378
- currentId === nextId
379
- ? route.key
380
- : `${route.name}-${nanoid()}`;
381
-
382
- let params;
383
-
384
- if (
385
- action.type === 'NAVIGATE' &&
386
- action.payload.merge &&
387
- currentId === nextId
388
- ) {
389
- params =
390
- action.payload.params !== undefined ||
391
- routeParamList[route.name] !== undefined
392
- ? {
393
- ...routeParamList[route.name],
394
- ...route.params,
395
- ...action.payload.params,
396
- }
397
- : route.params;
398
- } else {
399
- params = createParamsFromAction({ action, routeParamList });
400
- }
401
-
402
- const path =
403
- action.type === 'NAVIGATE' && action.payload.path != null
404
- ? action.payload.path
405
- : route.path;
406
-
407
- return params !== route.params || path !== route.path
408
- ? { ...route, key, path, params }
409
- : route;
410
- }),
411
- },
412
- index,
413
- backBehavior,
414
- initialRouteName
415
- );
416
-
417
- return {
418
- ...updatedState,
419
- preloadedRouteKeys: updatedState.preloadedRouteKeys.filter(
420
- (key) => key !== state.routes[updatedState.index].key
421
- ),
422
- };
423
- }
424
-
425
- case 'GO_BACK': {
426
- const focusedRoute = state.routes[state.index];
427
-
428
- if (state.history.length === 1 && !focusedRoute.history?.length) {
429
- return null;
430
- }
431
-
432
- const lastHistoryItem = state.history[state.history.length - 1];
433
-
434
- if (
435
- lastHistoryItem?.type === 'route' &&
436
- focusedRoute.history?.length
437
- ) {
438
- const routes = [...state.routes];
439
- const history = [...focusedRoute.history];
440
- const last = history.pop();
441
-
442
- routes[state.index] = {
443
- ...focusedRoute,
444
- params:
445
- last?.type === 'params' ? last.params : focusedRoute.params,
446
- history,
447
- };
448
-
449
- return {
450
- ...state,
451
- routes,
452
- };
453
- }
454
-
455
- const previousHistoryItem = state.history[state.history.length - 2];
456
- const previousKey = previousHistoryItem?.key;
457
- const index = state.routes.findLastIndex(
458
- (route) => route.key === previousKey
459
- );
460
-
461
- if (index === -1) {
462
- return null;
463
- }
464
-
465
- let routes = state.routes;
466
-
467
- if (
468
- backBehavior === 'fullHistory' &&
469
- routes[index].params !== previousHistoryItem.params
470
- ) {
471
- routes = [...state.routes];
472
- routes[index] = {
473
- ...routes[index],
474
- params: previousHistoryItem.params,
475
- };
476
- }
477
-
478
- return {
479
- ...state,
480
- routes,
481
- preloadedRouteKeys: state.preloadedRouteKeys.filter(
482
- (key) => key !== state.routes[index].key
483
- ),
484
- history: state.history.slice(0, -1),
485
- index,
486
- };
487
- }
488
-
489
- case 'PRELOAD': {
490
- const routeIndex = state.routes.findIndex(
491
- (route) => route.name === action.payload.name
492
- );
493
-
494
- if (routeIndex === -1) {
495
- return null;
496
- }
497
-
498
- const route = state.routes[routeIndex];
499
-
500
- const getId = routeGetIdList[route.name];
501
-
502
- const currentId = getId?.({ params: route.params });
503
- const nextId = getId?.({ params: action.payload.params });
504
-
505
- const key =
506
- currentId === nextId ? route.key : `${route.name}-${nanoid()}`;
507
-
508
- const params = createParamsFromAction({ action, routeParamList });
509
- const newRoute =
510
- params !== route.params ? { ...route, key, params } : route;
511
-
512
- return {
513
- ...state,
514
- preloadedRouteKeys: state.preloadedRouteKeys
515
- .filter((key) => key !== route.key)
516
- .concat(newRoute.key),
517
- routes: state.routes.map((route, index) =>
518
- index === routeIndex ? newRoute : route
519
- ),
520
- history:
521
- key === route.key
522
- ? state.history
523
- : state.history.filter((record) => record.key !== route.key),
524
- };
525
- }
526
-
527
- default:
528
- return BaseRouter.getStateForAction(state, action);
529
- }
530
- },
531
-
532
95
  actionCreators: TabActions,
533
96
  };
534
-
535
- return router;
536
97
  }
@@ -4,7 +4,7 @@ type Options = {
4
4
  action: {
5
5
  payload: {
6
6
  name: string;
7
- params?: object;
7
+ params?: object | undefined;
8
8
  };
9
9
  };
10
10
  routeParamList: ParamListBase;
@@ -7,7 +7,7 @@ type Options = {
7
7
  action: {
8
8
  payload: {
9
9
  name: string;
10
- params?: object;
10
+ params?: object | undefined;
11
11
  };
12
12
  };
13
13
  routeParamList: ParamListBase;
package/src/types.tsx CHANGED
@@ -6,7 +6,7 @@ export type NavigationRoute<
6
6
  ParamList extends ParamListBase,
7
7
  RouteName extends keyof ParamList,
8
8
  > = Route<Extract<RouteName, string>, ParamList[RouteName]> & {
9
- state?: NavigationState | PartialState<NavigationState>;
9
+ state?: NavigationState | PartialState<NavigationState> | undefined;
10
10
  };
11
11
 
12
12
  export type NavigationState<ParamList extends ParamListBase = ParamListBase> =
@@ -26,13 +26,13 @@ export type NavigationState<ParamList extends ParamListBase = ParamListBase> =
26
26
  /**
27
27
  * Alternative entries for history.
28
28
  */
29
- history?: unknown[];
29
+ history?: unknown[] | undefined;
30
30
  /**
31
31
  * List of rendered routes.
32
32
  */
33
33
  routes: NavigationRoute<ParamList, keyof ParamList>[];
34
34
  /**
35
- * Custom type for the state, whether it's for tab, stack, drawer etc.
35
+ * The type of the state, i.e., whether it's for tab, stack, drawer etc.
36
36
  * During rehydration, the state will be discarded if type doesn't match with router type.
37
37
  * It can also be used to detect the type of the navigator we're dealing with.
38
38
  */
@@ -45,21 +45,23 @@ export type NavigationState<ParamList extends ParamListBase = ParamListBase> =
45
45
 
46
46
  export type InitialState = Readonly<
47
47
  Partial<Omit<NavigationState, 'stale' | 'routes'>> & {
48
- routes: (Omit<Route<string>, 'key'> & { state?: InitialState })[];
48
+ routes: (Omit<Route<string>, 'key'> & {
49
+ state?: InitialState | undefined;
50
+ })[];
49
51
  }
50
52
  >;
51
53
 
52
54
  export type PartialRoute<R extends Route<string>> = Omit<R, 'key'> & {
53
- key?: string;
54
- state?: PartialState<NavigationState>;
55
+ key?: string | undefined;
56
+ state?: NavigationState | PartialState<NavigationState> | undefined;
55
57
  };
56
58
 
57
59
  export type PartialState<State extends NavigationState> = Partial<
58
60
  Omit<State, 'stale' | 'routes'>
59
61
  > &
60
62
  Readonly<{
61
- stale?: true;
62
- routes: PartialRoute<Route<State['routeNames'][number]>>[];
63
+ stale?: true | undefined;
64
+ routes: PartialRoute<State['routes'][number]>[];
63
65
  }>;
64
66
 
65
67
  export type Route<
@@ -79,26 +81,31 @@ export type Route<
79
81
  * Path associated with the route.
80
82
  * Usually present when the screen was opened from a deep link.
81
83
  */
82
- path?: string;
84
+ path?: string | undefined;
83
85
  /**
84
86
  * History of param changes for this route.
85
87
  */
86
- history?: { type: 'params'; params: object }[];
87
- } & Readonly<
88
- undefined extends Params
89
- ? {
90
- /**
91
- * Params for this route
92
- */
93
- params?: Readonly<Params>;
94
- }
95
- : {
96
- /**
97
- * Params for this route
98
- */
99
- params: Readonly<Params>;
100
- }
101
- >
88
+ history?:
89
+ | {
90
+ type: 'params';
91
+ params: undefined extends Params
92
+ ? Readonly<Params> | undefined
93
+ : Readonly<Params>;
94
+ }[]
95
+ | undefined;
96
+ } & (undefined extends Params
97
+ ? {
98
+ /**
99
+ * Params for this route
100
+ */
101
+ params?: Readonly<Params> | undefined;
102
+ }
103
+ : {
104
+ /**
105
+ * Params for this route
106
+ */
107
+ params: Readonly<Params>;
108
+ })
102
109
  >;
103
110
 
104
111
  export type ParamListBase = Record<string, object | undefined>;
@@ -111,15 +118,15 @@ export type NavigationAction = Readonly<{
111
118
  /**
112
119
  * Additional data for the action
113
120
  */
114
- payload?: object;
121
+ payload?: object | undefined;
115
122
  /**
116
123
  * Key of the route which dispatched this action.
117
124
  */
118
- source?: string;
125
+ source?: string | undefined;
119
126
  /**
120
127
  * Key of the navigator which should handle this action.
121
128
  */
122
- target?: string;
129
+ target?: string | undefined;
123
130
  }>;
124
131
 
125
132
  export type ActionCreators<Action extends NavigationAction> = {
@@ -131,7 +138,7 @@ export type DefaultRouterOptions<RouteName extends string = string> = {
131
138
  * Name of the route to focus by on initial render.
132
139
  * If not specified, usually the first route is used.
133
140
  */
134
- initialRouteName?: RouteName;
141
+ initialRouteName?: RouteName | undefined;
135
142
  };
136
143
 
137
144
  export type RouterFactory<
@@ -145,7 +152,9 @@ export type RouterConfigOptions = {
145
152
  routeParamList: ParamListBase;
146
153
  routeGetIdList: Record<
147
154
  string,
148
- | ((options: { params?: Record<string, any> }) => string | undefined)
155
+ | ((options: {
156
+ params?: Record<string, any> | undefined;
157
+ }) => string | undefined)
149
158
  | undefined
150
159
  >;
151
160
  };
@@ -228,17 +237,6 @@ export type Router<
228
237
  */
229
238
  shouldActionChangeFocus(action: NavigationAction): boolean;
230
239
 
231
- /**
232
- * Get the list of routes from the navigation state.
233
- *
234
- * By default, `state.routes` is used.
235
- * But in some cases (like `preloadedRoutes` in stack),
236
- * the actual list of routes may be different.
237
- *
238
- * @param state Navigation state to get the routes from.
239
- */
240
- getRoutesFromState?(state: State): Route<string>[];
241
-
242
240
  /**
243
241
  * Action creators for the router.
244
242
  */