@react-navigation/core 8.0.0-alpha.4 → 8.0.0-alpha.5

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 (84) hide show
  1. package/lib/module/BaseNavigationContainer.js.map +1 -1
  2. package/lib/module/NavigationBuilderContext.js.map +1 -1
  3. package/lib/module/NavigationIndependentTree.js +8 -4
  4. package/lib/module/NavigationIndependentTree.js.map +1 -1
  5. package/lib/module/NavigationProvider.js +14 -3
  6. package/lib/module/NavigationProvider.js.map +1 -1
  7. package/lib/module/NavigationStateContext.js.map +1 -1
  8. package/lib/module/SceneView.js +6 -39
  9. package/lib/module/SceneView.js.map +1 -1
  10. package/lib/module/StaticNavigation.js.map +1 -1
  11. package/lib/module/getPathFromState.js +24 -2
  12. package/lib/module/getPathFromState.js.map +1 -1
  13. package/lib/module/getStateFromPath.js +157 -72
  14. package/lib/module/getStateFromPath.js.map +1 -1
  15. package/lib/module/getStateFromRouteParams.js +24 -0
  16. package/lib/module/getStateFromRouteParams.js.map +1 -0
  17. package/lib/module/index.js.map +1 -1
  18. package/lib/module/types.js.map +1 -1
  19. package/lib/module/useIsFocused.js +7 -12
  20. package/lib/module/useIsFocused.js.map +1 -1
  21. package/lib/module/useNavigationBuilder.js +71 -28
  22. package/lib/module/useNavigationBuilder.js.map +1 -1
  23. package/lib/module/useOnAction.js.map +1 -1
  24. package/lib/module/useOnRouteFocus.js.map +1 -1
  25. package/lib/typescript/src/NavigationBuilderContext.d.ts +5 -5
  26. package/lib/typescript/src/NavigationBuilderContext.d.ts.map +1 -1
  27. package/lib/typescript/src/NavigationFocusedRouteStateContext.d.ts +4 -4
  28. package/lib/typescript/src/NavigationFocusedRouteStateContext.d.ts.map +1 -1
  29. package/lib/typescript/src/NavigationIndependentTree.d.ts.map +1 -1
  30. package/lib/typescript/src/NavigationProvider.d.ts +4 -4
  31. package/lib/typescript/src/NavigationProvider.d.ts.map +1 -1
  32. package/lib/typescript/src/NavigationStateContext.d.ts +3 -3
  33. package/lib/typescript/src/NavigationStateContext.d.ts.map +1 -1
  34. package/lib/typescript/src/SceneView.d.ts.map +1 -1
  35. package/lib/typescript/src/StaticNavigation.d.ts +5 -5
  36. package/lib/typescript/src/StaticNavigation.d.ts.map +1 -1
  37. package/lib/typescript/src/findFocusedRoute.d.ts +3 -3
  38. package/lib/typescript/src/findFocusedRoute.d.ts.map +1 -1
  39. package/lib/typescript/src/getActionFromState.d.ts +3 -3
  40. package/lib/typescript/src/getActionFromState.d.ts.map +1 -1
  41. package/lib/typescript/src/getPathFromState.d.ts +2 -2
  42. package/lib/typescript/src/getPathFromState.d.ts.map +1 -1
  43. package/lib/typescript/src/getStateFromPath.d.ts +3 -3
  44. package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
  45. package/lib/typescript/src/getStateFromRouteParams.d.ts +3 -0
  46. package/lib/typescript/src/getStateFromRouteParams.d.ts.map +1 -0
  47. package/lib/typescript/src/index.d.ts +1 -0
  48. package/lib/typescript/src/index.d.ts.map +1 -1
  49. package/lib/typescript/src/types.d.ts +64 -64
  50. package/lib/typescript/src/types.d.ts.map +1 -1
  51. package/lib/typescript/src/useDescriptors.d.ts +2 -2
  52. package/lib/typescript/src/useIsFocused.d.ts +3 -0
  53. package/lib/typescript/src/useIsFocused.d.ts.map +1 -1
  54. package/lib/typescript/src/useNavigationBuilder.d.ts +17 -17
  55. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  56. package/lib/typescript/src/useNavigationHelpers.d.ts +15 -15
  57. package/lib/typescript/src/useOnAction.d.ts +6 -6
  58. package/lib/typescript/src/useOnAction.d.ts.map +1 -1
  59. package/lib/typescript/src/useOnRouteFocus.d.ts +6 -6
  60. package/lib/typescript/src/useOnRouteFocus.d.ts.map +1 -1
  61. package/lib/typescript/src/useRouteCache.d.ts +2 -2
  62. package/lib/typescript/src/utilities.d.ts +35 -3
  63. package/lib/typescript/src/utilities.d.ts.map +1 -1
  64. package/package.json +10 -8
  65. package/src/BaseNavigationContainer.tsx +1 -1
  66. package/src/NavigationBuilderContext.tsx +7 -8
  67. package/src/NavigationFocusedRouteStateContext.tsx +4 -4
  68. package/src/NavigationIndependentTree.tsx +8 -5
  69. package/src/NavigationProvider.tsx +17 -3
  70. package/src/NavigationStateContext.tsx +5 -6
  71. package/src/SceneView.tsx +6 -36
  72. package/src/StaticNavigation.tsx +13 -5
  73. package/src/findFocusedRoute.tsx +3 -3
  74. package/src/getActionFromState.tsx +7 -7
  75. package/src/getPathFromState.tsx +52 -8
  76. package/src/getStateFromPath.tsx +254 -96
  77. package/src/getStateFromRouteParams.tsx +60 -0
  78. package/src/index.tsx +1 -0
  79. package/src/types.tsx +164 -120
  80. package/src/useIsFocused.tsx +14 -21
  81. package/src/useNavigationBuilder.tsx +116 -41
  82. package/src/useOnAction.tsx +7 -7
  83. package/src/useOnRouteFocus.tsx +9 -11
  84. package/src/utilities.tsx +72 -4
@@ -42,6 +42,7 @@ import { type ScreenConfigWithParent, useDescriptors } from './useDescriptors';
42
42
  import { useEventEmitter } from './useEventEmitter';
43
43
  import { useFocusedListenersChildrenAdapter } from './useFocusedListenersChildrenAdapter';
44
44
  import { useFocusEvents } from './useFocusEvents';
45
+ import { FocusedRouteKeyContext } from './useIsFocused';
45
46
  import { useKeyedChildListeners } from './useKeyedChildListeners';
46
47
  import { useLazyValue } from './useLazyValue';
47
48
  import { useNavigationHelpers } from './useNavigationHelpers';
@@ -58,9 +59,11 @@ PrivateValueStore;
58
59
 
59
60
  type NavigatorRoute = {
60
61
  key: string;
61
- params?: NavigatorScreenParams<ParamListBase>;
62
+ params?: NavigatorScreenParams<ParamListBase> | undefined;
62
63
  };
63
64
 
65
+ const CONSUMED_PARAMS = Symbol('CONSUMED_PARAMS');
66
+
64
67
  const isScreen = (
65
68
  child: React.ReactElement<unknown>
66
69
  ): child is React.ReactElement<{
@@ -324,6 +327,12 @@ export function useNavigationBuilder<
324
327
  | NavigatorRoute
325
328
  | undefined;
326
329
 
330
+ const isNestedParamsConsumed =
331
+ typeof route?.params === 'object' && route.params != null
332
+ ? CONSUMED_PARAMS in route.params &&
333
+ route.params[CONSUMED_PARAMS] === route.params
334
+ : false;
335
+
327
336
  const {
328
337
  children,
329
338
  layout,
@@ -418,7 +427,9 @@ export function useNavigationBuilder<
418
427
  );
419
428
 
420
429
  const isStateInitialized = React.useCallback(
421
- (state: NavigationState | PartialState<NavigationState> | undefined) =>
430
+ <T extends NavigationState>(
431
+ state: T | PartialState<T> | undefined
432
+ ): state is T =>
422
433
  state !== undefined && state.stale === false && isStateValid(state),
423
434
  [isStateValid]
424
435
  );
@@ -438,14 +449,20 @@ export function useNavigationBuilder<
438
449
  getIsInitial,
439
450
  } = React.useContext(NavigationStateContext);
440
451
 
441
- const stateCleanedUp = React.useRef(false);
452
+ const stateCleanupRef = React.useRef<boolean>(false);
453
+ const lastStateRef = React.useRef<State | PartialState<State> | undefined>(
454
+ undefined
455
+ );
442
456
 
443
457
  const setState = useLatestCallback(
444
- (state: NavigationState | PartialState<NavigationState> | undefined) => {
445
- if (stateCleanedUp.current) {
458
+ (state: State | PartialState<State> | undefined) => {
459
+ if (stateCleanupRef.current) {
460
+ // Store the state locally in case the current navigator is in `Activity`
461
+ lastStateRef.current = state;
462
+
446
463
  // State might have been already cleaned up due to unmount
447
- // We do not want to expose API allowing to override this
448
- // This would lead to old data preservation on main navigator unmount
464
+ // We don't want to update `route.state` in parent
465
+ // Otherwise it will be reused if a new navigator gets mounted
449
466
  return;
450
467
  }
451
468
 
@@ -457,11 +474,32 @@ export function useNavigationBuilder<
457
474
  stateBeforeInitialization,
458
475
  initializedState,
459
476
  isFirstStateInitialization,
477
+ paramsUsedForInitialization,
460
478
  ] = React.useMemo((): [
461
479
  PartialState<State> | undefined,
462
480
  State | undefined,
463
481
  boolean,
482
+ object | undefined,
464
483
  ] => {
484
+ // If the state was already cleaned up, but we have it stored in ref,
485
+ // It likely got cleaned up due to `<Activity mode="hidden">`
486
+ // We should reuse this state to avoid remounting screens
487
+ if (
488
+ stateCleanupRef.current &&
489
+ lastStateRef.current &&
490
+ isStateValid(lastStateRef.current)
491
+ ) {
492
+ const state: State = isStateInitialized(lastStateRef.current)
493
+ ? lastStateRef.current
494
+ : router.getRehydratedState(lastStateRef.current, {
495
+ routeNames,
496
+ routeParamList,
497
+ routeGetIdList,
498
+ });
499
+
500
+ return [undefined, state, false, undefined];
501
+ }
502
+
465
503
  const initialRouteParamList = routeNames.reduce<
466
504
  Record<string, object | undefined>
467
505
  >((acc, curr) => {
@@ -494,7 +532,8 @@ export function useNavigationBuilder<
494
532
  !(
495
533
  typeof route?.params?.screen === 'string' &&
496
534
  route?.params?.initial !== false
497
- )
535
+ ) &&
536
+ !isNestedParamsConsumed
498
537
  ) {
499
538
  return [
500
539
  undefined,
@@ -504,28 +543,40 @@ export function useNavigationBuilder<
504
543
  routeGetIdList,
505
544
  }),
506
545
  true,
546
+ undefined,
507
547
  ];
508
548
  } else {
509
- const stateFromParams = getStateFromParams(route?.params);
510
- const stateBeforeInitialization = (stateFromParams ??
511
- currentState) as PartialState<State>;
512
- const hydratedState = router.getRehydratedState(
513
- stateBeforeInitialization,
514
- {
515
- routeNames,
516
- routeParamList: initialRouteParamList,
517
- routeGetIdList,
518
- }
519
- );
549
+ const paramsForState = isNestedParamsConsumed ? undefined : route?.params;
550
+ const stateFromParams = paramsForState
551
+ ? getStateFromParams(paramsForState)
552
+ : undefined;
553
+
554
+ const stateBeforeInitialization = (stateFromParams ?? currentState) as
555
+ | PartialState<State>
556
+ | undefined;
557
+
558
+ const hydratedState =
559
+ stateBeforeInitialization == null
560
+ ? router.getInitialState({
561
+ routeNames,
562
+ routeParamList: initialRouteParamList,
563
+ routeGetIdList,
564
+ })
565
+ : router.getRehydratedState(stateBeforeInitialization, {
566
+ routeNames,
567
+ routeParamList: initialRouteParamList,
568
+ routeGetIdList,
569
+ });
520
570
 
521
571
  if (
572
+ stateBeforeInitialization != null &&
522
573
  options.routeNamesChangeBehavior === 'lastUnhandled' &&
523
574
  doesStateHaveOnlyInvalidRoutes(stateBeforeInitialization)
524
575
  ) {
525
- return [stateBeforeInitialization, hydratedState, true];
576
+ return [stateBeforeInitialization, hydratedState, true, paramsForState];
526
577
  }
527
578
 
528
- return [undefined, hydratedState, false];
579
+ return [undefined, hydratedState, false, paramsForState];
529
580
  }
530
581
  // We explicitly don't include routeNames, route.params etc. in the dep list
531
582
  // below. We want to avoid forcing a new state to be calculated in those cases
@@ -603,22 +654,18 @@ export function useNavigationBuilder<
603
654
  });
604
655
  }
605
656
 
606
- const previousNestedParamsRef = React.useRef(route?.params);
607
-
608
- React.useEffect(() => {
609
- previousNestedParamsRef.current = route?.params;
610
- }, [route?.params]);
611
-
612
- if (route?.params) {
613
- const previousParams = previousNestedParamsRef.current;
657
+ let didConsumeNestedParams = route?.params === paramsUsedForInitialization;
614
658
 
659
+ if (route?.params && !didConsumeNestedParams) {
615
660
  let action: CommonActions.Action | undefined;
616
661
 
617
662
  if (
618
663
  typeof route.params.state === 'object' &&
619
664
  route.params.state != null &&
620
- route.params !== previousParams
665
+ !isNestedParamsConsumed
621
666
  ) {
667
+ didConsumeNestedParams = true;
668
+
622
669
  if (
623
670
  options.routeNamesChangeBehavior === 'lastUnhandled' &&
624
671
  doesStateHaveOnlyInvalidRoutes(route.params.state)
@@ -633,8 +680,10 @@ export function useNavigationBuilder<
633
680
  } else if (
634
681
  typeof route.params.screen === 'string' &&
635
682
  ((route.params.initial === false && isFirstStateInitialization) ||
636
- route.params !== previousParams)
683
+ !isNestedParamsConsumed)
637
684
  ) {
685
+ didConsumeNestedParams = true;
686
+
638
687
  if (
639
688
  options.routeNamesChangeBehavior === 'lastUnhandled' &&
640
689
  !routeNames.includes(route.params.screen)
@@ -675,14 +724,26 @@ export function useNavigationBuilder<
675
724
  : nextState;
676
725
  }
677
726
 
678
- const shouldUpdate =
679
- state !== nextState ||
680
- typeof route?.params?.state === 'object' ||
681
- typeof route?.params?.screen === 'string';
727
+ React.useEffect(() => {
728
+ if (
729
+ didConsumeNestedParams &&
730
+ typeof route?.params === 'object' &&
731
+ route.params != null
732
+ ) {
733
+ // Track whether the params have been already consumed
734
+ // Set it to the same object, so merged params can be handled again
735
+ Object.defineProperty(route.params, CONSUMED_PARAMS, {
736
+ value: route.params,
737
+ enumerable: false,
738
+ });
739
+ }
740
+ }, [didConsumeNestedParams, route?.params]);
741
+
742
+ const shouldUpdate = state !== nextState;
682
743
 
683
744
  useScheduleUpdate(() => {
684
745
  if (shouldUpdate) {
685
- // If the state needs to be updated, we'll schedule an update
746
+ // Schedule an update if the state needs to be updated
686
747
  setState(nextState);
687
748
 
688
749
  if (shouldClearUnhandledState) {
@@ -696,25 +757,35 @@ export function useNavigationBuilder<
696
757
  // So we override the state object we return to use the latest state as soon as possible
697
758
  state = nextState;
698
759
 
760
+ // Last state to reuse if component gets cleaned up due to `<Activity mode="hidden">`
761
+ React.useEffect(() => {
762
+ lastStateRef.current = state;
763
+ });
764
+
765
+ const lastNotifiedStateRef = React.useRef<State | null>(null);
766
+
699
767
  React.useEffect(() => {
700
768
  // In strict mode, React will double-invoke effects.
701
769
  // So we need to reset the flag if component was not unmounted
702
- stateCleanedUp.current = false;
770
+ stateCleanupRef.current = false;
703
771
 
704
772
  setKey(navigatorKey);
705
773
 
706
- if (!getIsInitial()) {
774
+ if (!getIsInitial() && lastNotifiedStateRef.current !== state) {
707
775
  // If it's not initial render, we need to update the state
708
776
  // This will make sure that our container gets notifier of state changes due to new mounts
709
777
  // This is necessary for proper screen tracking, URL updates etc.
710
- setState(nextState);
778
+ // We only notify if the state is different what we already notified
779
+ // Otherwise this goes into a loop when inside `<Activity mode="hidden">`
780
+ setState(state);
781
+ lastNotifiedStateRef.current = state;
711
782
  }
712
783
 
713
784
  return () => {
714
785
  // We need to clean up state for this navigator on unmount
715
786
  if (getCurrentState() !== undefined && getKey() === navigatorKey) {
716
787
  setCurrentState(undefined);
717
- stateCleanedUp.current = true;
788
+ stateCleanupRef.current = true;
718
789
  }
719
790
  };
720
791
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -942,7 +1013,11 @@ export function useNavigationBuilder<
942
1013
  <NavigationMetaContext.Provider value={undefined}>
943
1014
  <NavigationHelpersContext.Provider value={navigation}>
944
1015
  <NavigationStateListenerProvider state={state}>
945
- <PreventRemoveProvider>{element}</PreventRemoveProvider>
1016
+ <FocusedRouteKeyContext.Provider
1017
+ value={state.routes[state.index].key}
1018
+ >
1019
+ <PreventRemoveProvider>{element}</PreventRemoveProvider>
1020
+ </FocusedRouteKeyContext.Provider>
946
1021
  </NavigationStateListenerProvider>
947
1022
  </NavigationHelpersContext.Provider>
948
1023
  </NavigationMetaContext.Provider>
@@ -16,11 +16,11 @@ import type { EventMapCore } from './types';
16
16
  import type { NavigationEventEmitter } from './useEventEmitter';
17
17
  import { shouldPreventRemove, useOnPreventRemove } from './useOnPreventRemove';
18
18
 
19
- type Options = {
20
- router: Router<NavigationState, NavigationAction>;
21
- key?: string;
22
- getState: () => NavigationState;
23
- setState: (state: NavigationState | PartialState<NavigationState>) => void;
19
+ type Options<State extends NavigationState> = {
20
+ router: Router<State, NavigationAction>;
21
+ key?: string | undefined;
22
+ getState: () => State;
23
+ setState: (state: State | PartialState<State>) => void;
24
24
  actionListeners: ChildActionListener[];
25
25
  beforeRemoveListeners: Record<string, ChildBeforeRemoveListener | undefined>;
26
26
  routerConfigOptions: RouterConfigOptions;
@@ -36,7 +36,7 @@ type Options = {
36
36
  *
37
37
  * When the action handler handles as action, it returns `true`, otherwise `false`.
38
38
  */
39
- export function useOnAction({
39
+ export function useOnAction<State extends NavigationState>({
40
40
  router,
41
41
  getState,
42
42
  setState,
@@ -45,7 +45,7 @@ export function useOnAction({
45
45
  beforeRemoveListeners,
46
46
  routerConfigOptions,
47
47
  emitter,
48
- }: Options) {
48
+ }: Options<State>) {
49
49
  const {
50
50
  onAction: onActionParent,
51
51
  onRouteFocus: onRouteFocusParent,
@@ -7,11 +7,11 @@ import * as React from 'react';
7
7
 
8
8
  import { NavigationBuilderContext } from './NavigationBuilderContext';
9
9
 
10
- type Options<Action extends NavigationAction> = {
11
- router: Router<NavigationState, Action>;
12
- getState: () => NavigationState;
13
- setState: (state: NavigationState) => void;
14
- key?: string;
10
+ type Options<State extends NavigationState, Action extends NavigationAction> = {
11
+ router: Router<State, Action>;
12
+ getState: () => State;
13
+ setState: (state: State) => void;
14
+ key?: string | undefined;
15
15
  };
16
16
 
17
17
  /**
@@ -19,12 +19,10 @@ type Options<Action extends NavigationAction> = {
19
19
  * Focus action needs to be treated specially, coz when a nested route is focused,
20
20
  * the parent navigators also needs to be focused.
21
21
  */
22
- export function useOnRouteFocus<Action extends NavigationAction>({
23
- router,
24
- getState,
25
- key: sourceRouteKey,
26
- setState,
27
- }: Options<Action>) {
22
+ export function useOnRouteFocus<
23
+ State extends NavigationState,
24
+ Action extends NavigationAction,
25
+ >({ router, getState, key: sourceRouteKey, setState }: Options<State, Action>) {
28
26
  const { onRouteFocus: onRouteFocusParent } = React.useContext(
29
27
  NavigationBuilderContext
30
28
  );
package/src/utilities.tsx CHANGED
@@ -66,6 +66,24 @@ type ExtractSegmentParam<Segment extends string> =
66
66
  ? { [K in StripRegex<Param>]: string }
67
67
  : {};
68
68
 
69
+ export type StandardSchemaValidationResult<Output> =
70
+ | { value: Output; issues?: undefined }
71
+ | { value?: undefined; issues: readonly unknown[] };
72
+
73
+ export type StandardSchemaV1<Input = unknown, Output = Input> = {
74
+ readonly '~standard': {
75
+ readonly version: 1;
76
+ readonly vendor: string;
77
+ readonly validate: (
78
+ value: Input
79
+ ) =>
80
+ | StandardSchemaValidationResult<Output>
81
+ | Promise<StandardSchemaValidationResult<Output>>;
82
+ };
83
+ };
84
+
85
+ export type QueryParamInput = string | string[] | null | undefined;
86
+
69
87
  /**
70
88
  * Extract path params from a path string.
71
89
  * e.g. `/foo/:userId/:postId` -> `{ userId: string; postId: string }`
@@ -78,17 +96,67 @@ export type ExtractParamStrings<Path extends string> =
78
96
  : ExtractSegmentParam<Path>;
79
97
 
80
98
  /**
81
- * Extract the parsed params type from base params and parse functions.
82
- * Applies the return type of parse functions to the corresponding params.
99
+ * Get the type of params based on the `parse` config and the path pattern.
83
100
  */
84
101
  export type ExtractParamsType<Params, Parse> = {
102
+ /**
103
+ * Base param types from path pattern
104
+ * Refine the type based on standard schema, then parse function
105
+ * Otherwise, use the type from path pattern
106
+ */
85
107
  [K in keyof Params]: K extends keyof Parse
86
- ? Parse[K] extends (value: string) => infer R
108
+ ? Parse[K] extends StandardSchemaV1<unknown, infer R>
87
109
  ? R
88
- : Params[K]
110
+ : Parse[K] extends (value: string) => infer R
111
+ ? R
112
+ : Params[K]
89
113
  : Params[K];
114
+ } & {
115
+ /**
116
+ * Optional param types not in path pattern (for query params)
117
+ */
118
+ [K in QueryParamOptionalKeys<Params, Parse>]?: QueryParamValue<Parse[K]>;
119
+ } & {
120
+ /**
121
+ * Required param types not in path pattern (for query params)
122
+ */
123
+ [K in QueryParamRequiredKeys<Params, Parse>]: QueryParamValue<Parse[K]>;
90
124
  };
91
125
 
126
+ type QueryParamValue<ParseValue> =
127
+ ParseValue extends StandardSchemaV1<unknown, infer R>
128
+ ? R
129
+ : ParseValue extends (value: string) => infer R
130
+ ? R
131
+ : never;
132
+
133
+ /**
134
+ * For schema, it's optional if the output has `undefined`, otherwise required
135
+ * For parse function, it's always optional as it can't say if it's required or not
136
+ */
137
+ type QueryParamOptionalKeys<Params, Parse> = {
138
+ [K in Exclude<keyof Parse, keyof Params>]: Parse[K] extends StandardSchemaV1<
139
+ unknown,
140
+ infer R
141
+ >
142
+ ? undefined extends R
143
+ ? K
144
+ : never
145
+ : Parse[K] extends (value: string) => unknown
146
+ ? K
147
+ : never;
148
+ }[Exclude<keyof Parse, keyof Params>];
149
+
150
+ /**
151
+ * Exclude optional keys to get required keys
152
+ * For schema, it's required if the output doesn't have `undefined`, otherwise optional
153
+ * It doesn't include parse functions (as they are always optional)
154
+ */
155
+ type QueryParamRequiredKeys<Params, Parse> = Exclude<
156
+ Exclude<keyof Parse, keyof Params>,
157
+ QueryParamOptionalKeys<Params, Parse>
158
+ >;
159
+
92
160
  /**
93
161
  * Infer the path string from a linking config.
94
162
  */