@amazon-devices/react-navigation__routers 8.0.9000000000 → 8.0.9000000002

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.
@@ -1,709 +0,0 @@
1
- import { nanoid } from 'nanoid/non-secure';
2
-
3
- import { BaseRouter } from './BaseRouter';
4
- import { createParamsFromAction } from './createParamsFromAction';
5
- import { createRouteFromAction } from './createRouteFromAction';
6
- import type {
7
- CommonNavigationAction,
8
- DefaultRouterOptions,
9
- NavigationRoute,
10
- NavigationState,
11
- ParamListBase,
12
- Route,
13
- Router,
14
- } from './types';
15
-
16
- export type StackActionType =
17
- | {
18
- type: 'REPLACE';
19
- payload: { name: string; params?: object };
20
- source?: string;
21
- target?: string;
22
- }
23
- | {
24
- type: 'PUSH';
25
- payload: { name: string; params?: object };
26
- source?: string;
27
- target?: string;
28
- }
29
- | {
30
- type: 'POP';
31
- payload: { count: number };
32
- source?: string;
33
- target?: string;
34
- }
35
- | {
36
- type: 'POP_TO_TOP';
37
- source?: string;
38
- target?: string;
39
- }
40
- | {
41
- type: 'POP_TO';
42
- payload: {
43
- name: string;
44
- params?: object;
45
- merge?: boolean;
46
- };
47
- source?: string;
48
- target?: string;
49
- };
50
-
51
- export type StackRouterOptions = DefaultRouterOptions;
52
-
53
- export type StackNavigationState<ParamList extends ParamListBase> =
54
- NavigationState<ParamList> & {
55
- /**
56
- * Type of the router, in this case, it's stack.
57
- */
58
- type: 'stack';
59
- /**
60
- * List of routes, which are supposed to be preloaded before navigating to.
61
- */
62
- preloadedRoutes: NavigationRoute<ParamList, keyof ParamList>[];
63
- };
64
-
65
- export type StackActionHelpers<ParamList extends ParamListBase> = {
66
- /**
67
- * Replace the current route with a new one.
68
- *
69
- * @param name Name of the new route.
70
- * @param [params] Params object for the new route.
71
- */
72
- replace<RouteName extends keyof ParamList>(
73
- ...args: {
74
- [Screen in keyof ParamList]: undefined extends ParamList[Screen]
75
- ? [screen: Screen] | [screen: Screen, params: ParamList[Screen]]
76
- : [screen: Screen, params: ParamList[Screen]];
77
- }[RouteName]
78
- ): void;
79
-
80
- /**
81
- * Push a new screen onto the stack.
82
- *
83
- * @param name Name of the route to push onto the stack.
84
- * @param [params] Params object for the route.
85
- */
86
- push<RouteName extends keyof ParamList>(
87
- ...args: {
88
- [Screen in keyof ParamList]: undefined extends ParamList[Screen]
89
- ? [screen: Screen] | [screen: Screen, params: ParamList[Screen]]
90
- : [screen: Screen, params: ParamList[Screen]];
91
- }[RouteName]
92
- ): void;
93
-
94
- /**
95
- * Pop a screen from the stack.
96
- */
97
- pop(count?: number): void;
98
-
99
- /**
100
- * Pop to the first route in the stack, dismissing all other screens.
101
- */
102
- popToTop(): void;
103
-
104
- /**
105
- * Pop any screens to go back to the specified screen.
106
- * If the specified screen doesn't exist, it'll be added to the stack.
107
- *
108
- * @param name Name of the route to navigate to.
109
- * @param [params] Params object for the route.
110
- * @param [merge] Whether to merge the params onto the route.
111
- */
112
- popTo<RouteName extends keyof ParamList>(
113
- ...args: {
114
- [Screen in keyof ParamList]: undefined extends ParamList[Screen]
115
- ?
116
- | [screen: Screen]
117
- | [screen: Screen, params: ParamList[Screen]]
118
- | [screen: Screen, params: ParamList[Screen], merge: boolean]
119
- :
120
- | [screen: Screen, params: ParamList[Screen]]
121
- | [screen: Screen, params: ParamList[Screen], merge: boolean];
122
- }[RouteName]
123
- ): void;
124
- };
125
-
126
- export const StackActions = {
127
- replace(name: string, params?: object) {
128
- return {
129
- type: 'REPLACE',
130
- payload: { name, params },
131
- } as const satisfies StackActionType;
132
- },
133
- push(name: string, params?: object) {
134
- return {
135
- type: 'PUSH',
136
- payload: { name, params },
137
- } as const satisfies StackActionType;
138
- },
139
- pop(count: number = 1) {
140
- return {
141
- type: 'POP',
142
- payload: { count },
143
- } as const satisfies StackActionType;
144
- },
145
- popToTop() {
146
- return { type: 'POP_TO_TOP' } as const satisfies StackActionType;
147
- },
148
- popTo(name: string, params?: object, options?: { merge?: boolean }) {
149
- if (typeof options === 'boolean') {
150
- console.warn(
151
- `Passing a boolean as the third argument to 'popTo' is deprecated. Pass '{ merge: true }' instead.`
152
- );
153
- }
154
-
155
- return {
156
- type: 'POP_TO',
157
- payload: {
158
- name,
159
- params,
160
- merge: typeof options === 'boolean' ? options : options?.merge,
161
- },
162
- } as const satisfies StackActionType;
163
- },
164
- };
165
-
166
- export function StackRouter(options: StackRouterOptions) {
167
- const router: Router<
168
- StackNavigationState<ParamListBase>,
169
- CommonNavigationAction | StackActionType
170
- > = {
171
- ...BaseRouter,
172
-
173
- type: 'stack',
174
-
175
- getInitialState({ routeNames, routeParamList }) {
176
- const initialRouteName =
177
- options.initialRouteName !== undefined &&
178
- routeNames.includes(options.initialRouteName)
179
- ? options.initialRouteName
180
- : routeNames[0];
181
-
182
- return {
183
- stale: false,
184
- type: 'stack',
185
- key: `stack-${nanoid()}`,
186
- index: 0,
187
- routeNames,
188
- preloadedRoutes: [],
189
- routes: [
190
- {
191
- key: `${initialRouteName}-${nanoid()}`,
192
- name: initialRouteName,
193
- params: routeParamList[initialRouteName],
194
- },
195
- ],
196
- };
197
- },
198
-
199
- getRehydratedState(partialState, { routeNames, routeParamList }) {
200
- const state = partialState;
201
-
202
- if (state.stale === false) {
203
- return state;
204
- }
205
-
206
- const routes = state.routes
207
- .filter((route) => routeNames.includes(route.name))
208
- .map((route) => ({
209
- ...route,
210
- key: route.key || `${route.name}-${nanoid()}`,
211
- params:
212
- routeParamList[route.name] !== undefined
213
- ? {
214
- ...routeParamList[route.name],
215
- ...route.params,
216
- }
217
- : route.params,
218
- }));
219
-
220
- const preloadedRoutes =
221
- state.preloadedRoutes
222
- ?.filter((route) => routeNames.includes(route.name))
223
- .map(
224
- (route) =>
225
- ({
226
- ...route,
227
- key: route.key || `${route.name}-${nanoid()}`,
228
- params:
229
- routeParamList[route.name] !== undefined
230
- ? {
231
- ...routeParamList[route.name],
232
- ...route.params,
233
- }
234
- : route.params,
235
- }) as Route<string>
236
- ) ?? [];
237
-
238
- if (routes.length === 0) {
239
- const initialRouteName =
240
- options.initialRouteName !== undefined
241
- ? options.initialRouteName
242
- : routeNames[0];
243
-
244
- routes.push({
245
- key: `${initialRouteName}-${nanoid()}`,
246
- name: initialRouteName,
247
- params: routeParamList[initialRouteName],
248
- });
249
- }
250
-
251
- return {
252
- stale: false,
253
- type: 'stack',
254
- key: `stack-${nanoid()}`,
255
- index: routes.length - 1,
256
- routeNames,
257
- routes,
258
- preloadedRoutes,
259
- };
260
- },
261
-
262
- getStateForRouteNamesChange(
263
- state,
264
- { routeNames, routeParamList, routeKeyChanges }
265
- ) {
266
- const routes = state.routes.filter(
267
- (route) =>
268
- routeNames.includes(route.name) &&
269
- !routeKeyChanges.includes(route.name)
270
- );
271
-
272
- if (routes.length === 0) {
273
- const initialRouteName =
274
- options.initialRouteName !== undefined &&
275
- routeNames.includes(options.initialRouteName)
276
- ? options.initialRouteName
277
- : routeNames[0];
278
-
279
- routes.push({
280
- key: `${initialRouteName}-${nanoid()}`,
281
- name: initialRouteName,
282
- params: routeParamList[initialRouteName],
283
- });
284
- }
285
-
286
- return {
287
- ...state,
288
- routeNames,
289
- routes,
290
- index: Math.min(state.index, routes.length - 1),
291
- };
292
- },
293
-
294
- getStateForRouteFocus(state, key) {
295
- const index = state.routes.findIndex((r) => r.key === key);
296
-
297
- if (index === -1 || index === state.index) {
298
- return state;
299
- }
300
-
301
- return {
302
- ...state,
303
- index,
304
- routes: state.routes.slice(0, index + 1),
305
- };
306
- },
307
-
308
- getStateForAction(state, action, options) {
309
- const { routeParamList } = options;
310
-
311
- switch (action.type) {
312
- case 'REPLACE': {
313
- const currentIndex =
314
- action.target === state.key && action.source
315
- ? state.routes.findIndex((r) => r.key === action.source)
316
- : state.index;
317
-
318
- if (currentIndex === -1) {
319
- return null;
320
- }
321
-
322
- if (!state.routeNames.includes(action.payload.name)) {
323
- return null;
324
- }
325
-
326
- const getId = options.routeGetIdList[action.payload.name];
327
- const id = getId?.({ params: action.payload.params });
328
-
329
- // Re-use preloaded route if available
330
- let route = state.preloadedRoutes.find(
331
- (route) =>
332
- route.name === action.payload.name &&
333
- id === getId?.({ params: route.params })
334
- );
335
-
336
- if (!route) {
337
- route = createRouteFromAction({ action, routeParamList });
338
- }
339
-
340
- return {
341
- ...state,
342
- routes: state.routes.map((r, i) =>
343
- i === currentIndex ? route : r
344
- ),
345
- preloadedRoutes: state.preloadedRoutes.filter(
346
- (r) => r.key !== route.key
347
- ),
348
- };
349
- }
350
-
351
- case 'PUSH':
352
- case 'NAVIGATE': {
353
- if (!state.routeNames.includes(action.payload.name)) {
354
- return null;
355
- }
356
-
357
- const getId = options.routeGetIdList[action.payload.name];
358
- const id = getId?.({ params: action.payload.params });
359
-
360
- let route: Route<string> | undefined;
361
-
362
- if (id !== undefined) {
363
- route = state.routes.find(
364
- (route) =>
365
- route.name === action.payload.name &&
366
- id === getId?.({ params: route.params })
367
- );
368
- } else {
369
- const currentRoute = state.routes[state.index];
370
-
371
- // If the route matches the current one, then navigate to it
372
- if (
373
- action.type === 'NAVIGATE' &&
374
- action.payload.name === currentRoute.name
375
- ) {
376
- route = currentRoute;
377
- }
378
- }
379
-
380
- if (!route) {
381
- route = state.preloadedRoutes.find(
382
- (route) =>
383
- route.name === action.payload.name &&
384
- id === getId?.({ params: route.params })
385
- );
386
- }
387
-
388
- let params;
389
-
390
- if (action.type === 'NAVIGATE' && action.payload.merge && route) {
391
- params =
392
- action.payload.params !== undefined ||
393
- routeParamList[action.payload.name] !== undefined
394
- ? {
395
- ...routeParamList[action.payload.name],
396
- ...route.params,
397
- ...action.payload.params,
398
- }
399
- : route.params;
400
- } else {
401
- params = createParamsFromAction({ action, routeParamList });
402
- }
403
-
404
- let routes: Route<string>[];
405
-
406
- if (route) {
407
- const routeKey = route.key;
408
-
409
- routes = state.routes.filter((r) => r.key !== routeKey);
410
- routes.push({
411
- ...route,
412
- path:
413
- action.type === 'NAVIGATE' && action.payload.path !== undefined
414
- ? action.payload.path
415
- : route.path,
416
- params,
417
- });
418
- } else {
419
- routes = [
420
- ...state.routes,
421
- {
422
- key: `${action.payload.name}-${nanoid()}`,
423
- name: action.payload.name,
424
- path:
425
- action.type === 'NAVIGATE' ? action.payload.path : undefined,
426
- params,
427
- },
428
- ];
429
- }
430
-
431
- return {
432
- ...state,
433
- index: routes.length - 1,
434
- preloadedRoutes: state.preloadedRoutes.filter(
435
- (route) => routes[routes.length - 1].key !== route.key
436
- ),
437
- routes,
438
- };
439
- }
440
-
441
- case 'NAVIGATE_DEPRECATED': {
442
- if (!state.routeNames.includes(action.payload.name)) {
443
- return null;
444
- }
445
-
446
- if (
447
- state.preloadedRoutes.find(
448
- (route) =>
449
- route.name === action.payload.name &&
450
- id === getId?.({ params: route.params })
451
- )
452
- ) {
453
- return null;
454
- }
455
-
456
- // If the route already exists, navigate to that
457
- let index = -1;
458
-
459
- const getId = options.routeGetIdList[action.payload.name];
460
- const id = getId?.({ params: action.payload.params });
461
-
462
- if (id !== undefined) {
463
- index = state.routes.findIndex(
464
- (route) =>
465
- route.name === action.payload.name &&
466
- id === getId?.({ params: route.params })
467
- );
468
- } else if (state.routes[state.index].name === action.payload.name) {
469
- index = state.index;
470
- } else {
471
- index = state.routes.findLastIndex(
472
- (route) => route.name === action.payload.name
473
- );
474
- }
475
-
476
- if (index === -1) {
477
- const routes = [
478
- ...state.routes,
479
- createRouteFromAction({ action, routeParamList }),
480
- ];
481
-
482
- return {
483
- ...state,
484
- routes,
485
- index: routes.length - 1,
486
- };
487
- }
488
-
489
- const route = state.routes[index];
490
-
491
- let params;
492
-
493
- if (action.payload.merge) {
494
- params =
495
- action.payload.params !== undefined ||
496
- routeParamList[route.name] !== undefined
497
- ? {
498
- ...routeParamList[route.name],
499
- ...route.params,
500
- ...action.payload.params,
501
- }
502
- : route.params;
503
- } else {
504
- params = createParamsFromAction({ action, routeParamList });
505
- }
506
-
507
- return {
508
- ...state,
509
- index,
510
- routes: [
511
- ...state.routes.slice(0, index),
512
- params !== route.params
513
- ? { ...route, params }
514
- : state.routes[index],
515
- ],
516
- };
517
- }
518
-
519
- case 'POP': {
520
- const currentIndex =
521
- action.target === state.key && action.source
522
- ? state.routes.findIndex((r) => r.key === action.source)
523
- : state.index;
524
-
525
- if (currentIndex > 0) {
526
- const count = Math.max(currentIndex - action.payload.count + 1, 1);
527
- const routes = state.routes
528
- .slice(0, count)
529
- .concat(state.routes.slice(currentIndex + 1));
530
-
531
- return {
532
- ...state,
533
- index: routes.length - 1,
534
- routes,
535
- };
536
- }
537
-
538
- return null;
539
- }
540
-
541
- case 'POP_TO_TOP':
542
- return router.getStateForAction(
543
- state,
544
- {
545
- type: 'POP',
546
- payload: { count: state.routes.length - 1 },
547
- },
548
- options
549
- );
550
-
551
- case 'POP_TO': {
552
- const currentIndex =
553
- action.target === state.key && action.source
554
- ? state.routes.findLastIndex((r) => r.key === action.source)
555
- : state.index;
556
-
557
- if (currentIndex === -1) {
558
- return null;
559
- }
560
-
561
- if (!state.routeNames.includes(action.payload.name)) {
562
- return null;
563
- }
564
-
565
- // If the route already exists, navigate to it
566
- let index = -1;
567
-
568
- const getId = options.routeGetIdList[action.payload.name];
569
- const id = getId?.({ params: action.payload.params });
570
-
571
- if (id !== undefined) {
572
- index = state.routes.findIndex(
573
- (route) =>
574
- route.name === action.payload.name &&
575
- id === getId?.({ params: route.params })
576
- );
577
- } else if (state.routes[currentIndex].name === action.payload.name) {
578
- index = currentIndex;
579
- } else {
580
- for (let i = currentIndex; i >= 0; i--) {
581
- if (state.routes[i].name === action.payload.name) {
582
- index = i;
583
- break;
584
- }
585
- }
586
- }
587
-
588
- // If the route doesn't exist, remove the current route and add the new one
589
- if (index === -1) {
590
- // Re-use preloaded route if available
591
- let route = state.preloadedRoutes.find(
592
- (route) =>
593
- route.name === action.payload.name &&
594
- id === getId?.({ params: route.params })
595
- );
596
-
597
- if (!route) {
598
- route = createRouteFromAction({ action, routeParamList });
599
- }
600
-
601
- const routes = state.routes.slice(0, currentIndex).concat(route);
602
-
603
- return {
604
- ...state,
605
- index: routes.length - 1,
606
- routes,
607
- preloadedRoutes: state.preloadedRoutes.filter(
608
- (r) => r.key !== route.key
609
- ),
610
- };
611
- }
612
-
613
- const route = state.routes[index];
614
-
615
- let params;
616
-
617
- if (action.payload.merge) {
618
- params =
619
- action.payload.params !== undefined ||
620
- routeParamList[route.name] !== undefined
621
- ? {
622
- ...routeParamList[route.name],
623
- ...route.params,
624
- ...action.payload.params,
625
- }
626
- : route.params;
627
- } else {
628
- params = createParamsFromAction({ action, routeParamList });
629
- }
630
-
631
- return {
632
- ...state,
633
- index,
634
- routes: [
635
- ...state.routes.slice(0, index),
636
- params !== route.params
637
- ? { ...route, params }
638
- : state.routes[index],
639
- ],
640
- };
641
- }
642
-
643
- case 'GO_BACK':
644
- if (state.index > 0) {
645
- return router.getStateForAction(
646
- state,
647
- {
648
- type: 'POP',
649
- payload: { count: 1 },
650
- target: action.target,
651
- source: action.source,
652
- },
653
- options
654
- );
655
- }
656
-
657
- return null;
658
-
659
- case 'PRELOAD': {
660
- const getId = options.routeGetIdList[action.payload.name];
661
- const id = getId?.({ params: action.payload.params });
662
-
663
- let route: Route<string> | undefined;
664
-
665
- if (id !== undefined) {
666
- route = state.routes.find(
667
- (route) =>
668
- route.name === action.payload.name &&
669
- id === getId?.({ params: route.params })
670
- );
671
- }
672
-
673
- if (route) {
674
- return {
675
- ...state,
676
- routes: state.routes.map((r) => {
677
- if (r.key !== route?.key) {
678
- return r;
679
- }
680
- return {
681
- ...r,
682
- params: createParamsFromAction({ action, routeParamList }),
683
- };
684
- }),
685
- };
686
- } else {
687
- return {
688
- ...state,
689
- preloadedRoutes: state.preloadedRoutes
690
- .filter(
691
- (r) =>
692
- r.name !== action.payload.name ||
693
- id !== getId?.({ params: r.params })
694
- )
695
- .concat(createRouteFromAction({ action, routeParamList })),
696
- };
697
- }
698
- }
699
-
700
- default:
701
- return BaseRouter.getStateForAction(state, action);
702
- }
703
- },
704
-
705
- actionCreators: StackActions,
706
- };
707
-
708
- return router;
709
- }