@react-navigation/core 8.0.0-alpha.26 → 8.0.0-alpha.28

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 +12 -6
  2. package/lib/module/BaseNavigationContainer.js.map +1 -1
  3. package/lib/module/ConsumedParamsContext.js.map +1 -1
  4. package/lib/module/DataLoading.js +29 -4
  5. package/lib/module/DataLoading.js.map +1 -1
  6. package/lib/module/NavigationIndependentTree.js +8 -4
  7. package/lib/module/NavigationIndependentTree.js.map +1 -1
  8. package/lib/module/SceneView.js +16 -39
  9. package/lib/module/SceneView.js.map +1 -1
  10. package/lib/module/StaticNavigation.js +4 -3
  11. package/lib/module/StaticNavigation.js.map +1 -1
  12. package/lib/module/findFocusedRoute.js +1 -1
  13. package/lib/module/findFocusedRoute.js.map +1 -1
  14. package/lib/module/getActionFromState.js +1 -2
  15. package/lib/module/getActionFromState.js.map +1 -1
  16. package/lib/module/getPathFromState.js +41 -26
  17. package/lib/module/getPathFromState.js.map +1 -1
  18. package/lib/module/getPatternParts.js +15 -2
  19. package/lib/module/getPatternParts.js.map +1 -1
  20. package/lib/module/getStateFromPath.js +45 -19
  21. package/lib/module/getStateFromPath.js.map +1 -1
  22. package/lib/module/index.js +1 -0
  23. package/lib/module/index.js.map +1 -1
  24. package/lib/module/useFocusEvents.js +6 -2
  25. package/lib/module/useFocusEvents.js.map +1 -1
  26. package/lib/module/useLazyValue.js.map +1 -1
  27. package/lib/module/useNavigationBuilder.js +51 -27
  28. package/lib/module/useNavigationBuilder.js.map +1 -1
  29. package/lib/module/useOnAction.js +25 -17
  30. package/lib/module/useOnAction.js.map +1 -1
  31. package/lib/module/useOnPreventRemove.js +1 -3
  32. package/lib/module/useOnPreventRemove.js.map +1 -1
  33. package/lib/module/useRouteCache.js +1 -3
  34. package/lib/module/useRouteCache.js.map +1 -1
  35. package/lib/module/useSyncState.js +3 -2
  36. package/lib/module/useSyncState.js.map +1 -1
  37. package/lib/typescript/src/BaseNavigationContainer.d.ts.map +1 -1
  38. package/lib/typescript/src/ConsumedParamsContext.d.ts +1 -6
  39. package/lib/typescript/src/ConsumedParamsContext.d.ts.map +1 -1
  40. package/lib/typescript/src/DataLoading.d.ts +1 -0
  41. package/lib/typescript/src/DataLoading.d.ts.map +1 -1
  42. package/lib/typescript/src/NavigationIndependentTree.d.ts.map +1 -1
  43. package/lib/typescript/src/SceneView.d.ts.map +1 -1
  44. package/lib/typescript/src/StaticNavigation.d.ts +39 -30
  45. package/lib/typescript/src/StaticNavigation.d.ts.map +1 -1
  46. package/lib/typescript/src/getPathFromState.d.ts.map +1 -1
  47. package/lib/typescript/src/getPatternParts.d.ts.map +1 -1
  48. package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
  49. package/lib/typescript/src/index.d.ts +2 -1
  50. package/lib/typescript/src/index.d.ts.map +1 -1
  51. package/lib/typescript/src/types.d.ts +2 -2
  52. package/lib/typescript/src/types.d.ts.map +1 -1
  53. package/lib/typescript/src/useFocusEvents.d.ts.map +1 -1
  54. package/lib/typescript/src/useLazyValue.d.ts.map +1 -1
  55. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  56. package/lib/typescript/src/useOnAction.d.ts.map +1 -1
  57. package/lib/typescript/src/useOnPreventRemove.d.ts.map +1 -1
  58. package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
  59. package/lib/typescript/src/useSyncState.d.ts.map +1 -1
  60. package/lib/typescript/src/utilities.d.ts +10 -0
  61. package/lib/typescript/src/utilities.d.ts.map +1 -1
  62. package/package.json +5 -5
  63. package/src/BaseNavigationContainer.tsx +13 -7
  64. package/src/ConsumedParamsContext.tsx +1 -6
  65. package/src/DataLoading.tsx +68 -4
  66. package/src/NavigationIndependentTree.tsx +8 -5
  67. package/src/SceneView.tsx +18 -52
  68. package/src/StaticNavigation.tsx +88 -64
  69. package/src/findFocusedRoute.tsx +1 -1
  70. package/src/getActionFromState.tsx +7 -3
  71. package/src/getPathFromState.tsx +59 -32
  72. package/src/getPatternParts.tsx +15 -2
  73. package/src/getStateFromPath.tsx +77 -17
  74. package/src/index.tsx +5 -0
  75. package/src/types.tsx +2 -2
  76. package/src/useDescriptors.tsx +1 -1
  77. package/src/useFocusEvents.tsx +7 -5
  78. package/src/useLazyValue.tsx +2 -2
  79. package/src/useNavigationBuilder.tsx +82 -37
  80. package/src/useOnAction.tsx +36 -40
  81. package/src/useOnPreventRemove.tsx +4 -3
  82. package/src/useRouteCache.tsx +1 -4
  83. package/src/useSyncState.tsx +3 -2
  84. package/src/utilities.tsx +19 -0
@@ -19,7 +19,6 @@ import { ConsumedParamsContext } from './ConsumedParamsContext';
19
19
  import { deepFreeze } from './deepFreeze';
20
20
  import { Group } from './Group';
21
21
  import { isArrayEqual } from './isArrayEqual';
22
- import { isRecordEqual } from './isRecordEqual';
23
22
  import { NavigationBuilderContext } from './NavigationBuilderContext';
24
23
  import { NavigationHelpersContext } from './NavigationHelpersContext';
25
24
  import { NavigationMetaContext } from './NavigationMetaContext';
@@ -104,7 +103,7 @@ const getRouteConfigsFromChildren = <
104
103
  EventMap extends EventMapBase,
105
104
  >(
106
105
  children: React.ReactNode,
107
- groupKey?: string,
106
+ groupKeys: string[] = [],
108
107
  groupOptions?: ScreenConfigWithParent<
109
108
  State,
110
109
  ScreenOptions,
@@ -147,7 +146,7 @@ const getRouteConfigsFromChildren = <
147
146
  }
148
147
 
149
148
  acc.push({
150
- keys: [groupKey, child.props.navigationKey],
149
+ keys: [...groupKeys, child.props.navigationKey],
151
150
  options: groupOptions,
152
151
  layout: groupLayout,
153
152
  props: child.props as RouteConfig<
@@ -177,7 +176,9 @@ const getRouteConfigsFromChildren = <
177
176
  acc.push(
178
177
  ...getRouteConfigsFromChildren<State, ScreenOptions, EventMap>(
179
178
  child.props.children as React.ReactNode,
180
- child.props.navigationKey,
179
+ child.props.navigationKey != null
180
+ ? [...groupKeys, child.props.navigationKey]
181
+ : groupKeys,
181
182
  // FIXME
182
183
  // @ts-expect-error: add validation
183
184
  child.type !== Group
@@ -285,10 +286,23 @@ const getRouteConfigsFromChildren = <
285
286
  return configs;
286
287
  };
287
288
 
288
- const getStateFromParams = (params: NavigatorRoute['params']) => {
289
+ const getStateFromParams = (
290
+ params: NavigatorRoute['params'],
291
+ type: string
292
+ ): NavigationState | PartialState<NavigationState> | undefined => {
289
293
  const state = params?.state;
290
294
 
291
295
  if (isNavigationState(state)) {
296
+ if (state.type != null && state.type !== type) {
297
+ if (process.env.NODE_ENV !== 'production') {
298
+ console.error(
299
+ `The state passed in the params is for a navigator of type '${state.type}', but it was passed to a navigator of type '${type}'. This can happen if the state was meant for a different navigator. The state will be discarded.`
300
+ );
301
+ }
302
+
303
+ return undefined;
304
+ }
305
+
292
306
  return state;
293
307
  } else if (typeof params?.screen === 'string' && params?.initial !== false) {
294
308
  return {
@@ -337,7 +351,7 @@ export function useNavigationBuilder<
337
351
 
338
352
  const isNestedParamsConsumed =
339
353
  typeof route?.params === 'object' && route.params != null
340
- ? consumedParams?.isConsumed(route.params)
354
+ ? consumedParams?.has(route.params)
341
355
  : false;
342
356
 
343
357
  const {
@@ -395,7 +409,7 @@ export function useNavigationBuilder<
395
409
  ScreenConfigWithParent<State, ScreenOptions, EventMap>
396
410
  > = {};
397
411
 
398
- const routeKeyList: Record<string, React.Key | undefined> = {};
412
+ const routeKeyList: Record<string, (string | undefined)[]> = {};
399
413
  const routeParamList: Record<string, object | undefined> = {};
400
414
  const routeGetIdList: RouterConfigOptions['routeGetIdList'] = {};
401
415
 
@@ -409,7 +423,7 @@ export function useNavigationBuilder<
409
423
  }
410
424
 
411
425
  screens[name] = config;
412
- routeKeyList[name] = config.keys.map((key) => key ?? '').join(':');
426
+ routeKeyList[name] = config.keys;
413
427
  routeParamList[name] = config.props.initialParams;
414
428
 
415
429
  Object.assign(routeGetIdList, { [name]: config.props.getId });
@@ -543,7 +557,7 @@ export function useNavigationBuilder<
543
557
  } else {
544
558
  const paramsForState = isNestedParamsConsumed ? undefined : route?.params;
545
559
  const stateFromParams = paramsForState
546
- ? getStateFromParams(paramsForState)
560
+ ? getStateFromParams(paramsForState, router.type)
547
561
  : undefined;
548
562
 
549
563
  const stateBeforeInitialization = (stateFromParams ?? currentState) as
@@ -565,13 +579,21 @@ export function useNavigationBuilder<
565
579
 
566
580
  if (
567
581
  stateBeforeInitialization != null &&
582
+ isStateValid(stateBeforeInitialization) &&
568
583
  options.routeNamesChangeBehavior === 'lastUnhandled' &&
569
584
  doesStateHaveOnlyInvalidRoutes(stateBeforeInitialization)
570
585
  ) {
571
586
  return [stateBeforeInitialization, hydratedState, true, paramsForState];
572
587
  }
573
588
 
574
- return [undefined, hydratedState, false, paramsForState];
589
+ return [
590
+ undefined,
591
+ hydratedState,
592
+ false,
593
+ // Current state is already initialized
594
+ // So params weren't used, and need to be handled later
595
+ isStateInitialized(currentState) ? undefined : paramsForState,
596
+ ];
575
597
  }
576
598
  // We explicitly don't include routeNames, route.params etc. in the dep list
577
599
  // below. We want to avoid forcing a new state to be calculated in those cases
@@ -589,6 +611,16 @@ export function useNavigationBuilder<
589
611
 
590
612
  const previousRouteKeyList = previousRouteKeyListRef.current;
591
613
 
614
+ // Names of the screens whose navigation key changed since the last render
615
+ const routeKeyChanges = Object.keys(routeKeyList).filter((name) => {
616
+ const current = routeKeyList[name];
617
+ const previous = previousRouteKeyList[name];
618
+
619
+ return (
620
+ current != null && previous != null && !isArrayEqual(current, previous)
621
+ );
622
+ });
623
+
592
624
  const [unhandledState, setUnhandledState] = React.useState<
593
625
  NavigationState | PartialState<NavigationState> | undefined
594
626
  >(stateBeforeInitialization);
@@ -616,10 +648,14 @@ export function useNavigationBuilder<
616
648
  let nextState: State = state;
617
649
  let shouldClearUnhandledState = false;
618
650
 
619
- // Previously unhandled state is now valid again
620
- // And current state no longer has any valid routes
621
- // We should reuse the unhandled state instead of re-calculating the state
622
- if (
651
+ if (unhandledState != null && !isStateValid(unhandledState)) {
652
+ // The stored unhandled state is for a different type of navigator
653
+ // It can't be used with this navigator, so we discard it
654
+ setUnhandledState(undefined);
655
+ } else if (
656
+ // Previously unhandled state is now valid again
657
+ // And current state no longer has any valid routes
658
+ // We should reuse the unhandled state instead of re-calculating the state
623
659
  unhandledState?.routes.every((r) => routeNames.includes(r.name)) &&
624
660
  state?.routes.every((r) => !routeNames.includes(r.name))
625
661
  ) {
@@ -634,18 +670,14 @@ export function useNavigationBuilder<
634
670
  );
635
671
  } else if (
636
672
  !isArrayEqual(state.routeNames, routeNames) ||
637
- !isRecordEqual(routeKeyList, previousRouteKeyList)
673
+ routeKeyChanges.length !== 0
638
674
  ) {
639
675
  // When the list of route names change, the router should handle it to remove invalid routes
640
676
  nextState = router.getStateForRouteNamesChange(state, {
641
677
  routeNames,
642
678
  routeParamList,
643
679
  routeGetIdList,
644
- routeKeyChanges: Object.keys(routeKeyList).filter(
645
- (name) =>
646
- name in previousRouteKeyList &&
647
- routeKeyList[name] !== previousRouteKeyList[name]
648
- ),
680
+ routeKeyChanges,
649
681
  });
650
682
  }
651
683
 
@@ -653,21 +685,24 @@ export function useNavigationBuilder<
653
685
 
654
686
  if (route?.params && !didConsumeNestedParams) {
655
687
  let action: CommonActions.Action | undefined;
656
- const stateFromParams = route.params.state;
657
688
 
658
- if (isNavigationState(stateFromParams) && !isNestedParamsConsumed) {
689
+ if (isNavigationState(route.params.state) && !isNestedParamsConsumed) {
659
690
  didConsumeNestedParams = true;
660
691
 
661
- if (
662
- options.routeNamesChangeBehavior === 'lastUnhandled' &&
663
- doesStateHaveOnlyInvalidRoutes(stateFromParams)
664
- ) {
665
- if (stateFromParams !== unhandledState) {
666
- setUnhandledState(stateFromParams);
692
+ const stateFromParams = getStateFromParams(route.params, router.type);
693
+
694
+ if (stateFromParams != null) {
695
+ if (
696
+ options.routeNamesChangeBehavior === 'lastUnhandled' &&
697
+ doesStateHaveOnlyInvalidRoutes(stateFromParams)
698
+ ) {
699
+ if (stateFromParams !== unhandledState) {
700
+ setUnhandledState(stateFromParams);
701
+ }
702
+ } else {
703
+ // If the route was updated with new state, we should reset to it
704
+ action = CommonActions.reset(stateFromParams);
667
705
  }
668
- } else {
669
- // If the route was updated with new state, we should reset to it
670
- action = CommonActions.reset(stateFromParams);
671
706
  }
672
707
  } else if (
673
708
  typeof route.params.screen === 'string' &&
@@ -680,7 +715,7 @@ export function useNavigationBuilder<
680
715
  options.routeNamesChangeBehavior === 'lastUnhandled' &&
681
716
  !routeNames.includes(route.params.screen)
682
717
  ) {
683
- const state = getStateFromParams(route.params);
718
+ const state = getStateFromParams(route.params, router.type);
684
719
 
685
720
  if (state != null && !deepEqual(state, unhandledState)) {
686
721
  setUnhandledState(state);
@@ -709,6 +744,18 @@ export function useNavigationBuilder<
709
744
  })
710
745
  : null;
711
746
 
747
+ if (
748
+ process.env.NODE_ENV !== 'production' &&
749
+ action != null &&
750
+ updatedState === null
751
+ ) {
752
+ const names = routeNames.map((name) => `'${name}'`).join(', ');
753
+
754
+ console.error(
755
+ `The 'state' or 'screen' passed in the params couldn't be applied to the navigator with the screens: ${names}. This can happen if the params contain names of screens that don't exist in the navigator. The navigation specified in the params will be ignored.`
756
+ );
757
+ }
758
+
712
759
  nextState =
713
760
  updatedState !== null
714
761
  ? router.getRehydratedState(updatedState, {
@@ -719,18 +766,16 @@ export function useNavigationBuilder<
719
766
  : nextState;
720
767
  }
721
768
 
722
- const setConsumedParams = consumedParams?.setConsumed;
723
-
724
769
  React.useEffect(() => {
725
770
  if (
726
- setConsumedParams &&
771
+ consumedParams &&
727
772
  didConsumeNestedParams &&
728
773
  typeof route?.params === 'object' &&
729
774
  route.params != null
730
775
  ) {
731
- setConsumedParams(route.params);
776
+ consumedParams.set(route.params, true);
732
777
  }
733
- }, [didConsumeNestedParams, route?.params, setConsumedParams]);
778
+ }, [consumedParams, didConsumeNestedParams, route?.params]);
734
779
 
735
780
  const shouldUpdate = state !== nextState;
736
781
 
@@ -1,16 +1,14 @@
1
1
  import type {
2
2
  NavigationAction,
3
- NavigationRoute,
4
3
  NavigationState,
5
- ParamListBase,
6
- PartialRoute,
7
4
  PartialState,
8
5
  Router,
9
6
  RouterConfigOptions,
10
7
  } from '@react-navigation/routers';
11
8
  import * as React from 'react';
12
9
 
13
- import { getLoaderForState } from './DataLoading';
10
+ import { ConsumedParamsContext } from './ConsumedParamsContext';
11
+ import { getLoaderForStateChange } from './DataLoading';
14
12
  import {
15
13
  type ChildActionListener,
16
14
  type ChildBeforeRemoveListener,
@@ -32,40 +30,6 @@ type Options<State extends NavigationState> = {
32
30
  emitter: NavigationEventEmitter<EventMapCore<any>>;
33
31
  };
34
32
 
35
- type MaybePartialRoute =
36
- | NavigationRoute<ParamListBase, string>
37
- | PartialRoute<NavigationRoute<ParamListBase, string>>;
38
-
39
- const didFocusedRouteChange = (
40
- currentState: NavigationState | PartialState<NavigationState>,
41
- nextState: NavigationState | PartialState<NavigationState>
42
- ) => {
43
- let currentFocusedRoute: MaybePartialRoute | undefined =
44
- currentState.routes[currentState.index ?? 0];
45
-
46
- let nextFocusedRoute: MaybePartialRoute | undefined =
47
- nextState.routes[nextState.index ?? 0];
48
-
49
- let changed = currentFocusedRoute?.key !== nextFocusedRoute?.key;
50
-
51
- while (
52
- !changed &&
53
- currentFocusedRoute?.state != null &&
54
- nextFocusedRoute?.state != null &&
55
- currentFocusedRoute.state !== nextFocusedRoute.state
56
- ) {
57
- currentFocusedRoute =
58
- currentFocusedRoute.state.routes[currentFocusedRoute.state.index ?? 0];
59
-
60
- nextFocusedRoute =
61
- nextFocusedRoute.state.routes[nextFocusedRoute.state.index ?? 0];
62
-
63
- changed = currentFocusedRoute?.key !== nextFocusedRoute?.key;
64
- }
65
-
66
- return changed;
67
- };
68
-
69
33
  /**
70
34
  * Hook to handle actions for a navigator, including state updates and bubbling.
71
35
  *
@@ -90,9 +54,11 @@ export function useOnAction<State extends NavigationState>({
90
54
  onRouteFocus: onRouteFocusParent,
91
55
  addListener: addListenerParent,
92
56
  onDispatchAction,
57
+ flushUpdates,
93
58
  } = React.use(NavigationBuilderContext);
94
59
 
95
60
  const tree = React.use(StaticTreeContext);
61
+ const consumedParams = React.use(ConsumedParamsContext);
96
62
 
97
63
  const routerConfigOptionsRef =
98
64
  React.useRef<RouterConfigOptions>(routerConfigOptions);
@@ -106,6 +72,11 @@ export function useOnAction<State extends NavigationState>({
106
72
  action: NavigationAction,
107
73
  visitedNavigators: Set<string> = new Set<string>()
108
74
  ) => {
75
+ // Apply any pending state updates scheduled during render before handling the action
76
+ // Otherwise the action will be handled with an outdated state,
77
+ // and the pending updates will overwrite the changes from the action
78
+ flushUpdates();
79
+
109
80
  const state = getState();
110
81
 
111
82
  // Since actions can bubble both up and down, they could come to the same navigator again
@@ -128,6 +99,17 @@ export function useOnAction<State extends NavigationState>({
128
99
  result =
129
100
  result === null && action.target === state.key ? state : result;
130
101
 
102
+ if (result !== null && result.stale !== false) {
103
+ // Some actions (e.g. `RESET`) may return a stale state from the router
104
+ // We rehydrate it before storing so the stored state is always usable
105
+ // Otherwise anything reading the state before the next render
106
+ // (e.g. another dispatch immediately after) would get an outdated state
107
+ result = router.getRehydratedState(
108
+ result,
109
+ routerConfigOptionsRef.current
110
+ );
111
+ }
112
+
131
113
  if (result !== null) {
132
114
  if (state !== result) {
133
115
  const isPrevented = shouldPreventRemove(
@@ -144,10 +126,22 @@ export function useOnAction<State extends NavigationState>({
144
126
  return true;
145
127
  }
146
128
 
129
+ if (getState() !== state) {
130
+ // Listeners for 'beforeRemove' may dispatch actions that change the state
131
+ // Then the result we have is outdated and would overwrite those changes
132
+ // So we handle the action again to calculate the result with the latest state
133
+ return onAction(action, new Set<string>());
134
+ }
135
+
147
136
  onDispatchAction(action, false);
148
137
 
149
- if (tree && didFocusedRouteChange(state, result)) {
150
- const loader = getLoaderForState(tree, result);
138
+ if (tree) {
139
+ const loader = getLoaderForStateChange(
140
+ tree,
141
+ result,
142
+ state,
143
+ consumedParams
144
+ );
151
145
 
152
146
  loader?.();
153
147
  }
@@ -194,7 +188,9 @@ export function useOnAction<State extends NavigationState>({
194
188
  [
195
189
  actionListeners,
196
190
  beforeRemoveListeners,
191
+ consumedParams,
197
192
  emitter,
193
+ flushUpdates,
198
194
  getState,
199
195
  key,
200
196
  onActionParent,
@@ -39,9 +39,10 @@ export const shouldPreventRemove = (
39
39
  .filter(([, route]) => route.key != null)
40
40
  );
41
41
 
42
- const visitedRouteKeys: Set<string> =
43
- // @ts-expect-error: add this property to mark that we've already emitted this action
44
- action[VISITED_ROUTE_KEYS] ?? new Set<string>();
42
+ const visitedRouteKeys =
43
+ VISITED_ROUTE_KEYS in action && action[VISITED_ROUTE_KEYS] instanceof Set
44
+ ? action[VISITED_ROUTE_KEYS]
45
+ : new Set<string>();
45
46
 
46
47
  const beforeRemoveAction = {
47
48
  ...action,
@@ -41,10 +41,7 @@ export function useRouteCache<State extends NavigationState>(
41
41
  // FIXME: since the state is updated with mutation, the route object cannot be frozen
42
42
  // As a workaround, loop through the object and make the properties readonly
43
43
  // Only needed once per proxy - skip if we're reusing a previously-frozen one
44
- for (const key in proxy) {
45
- // @ts-expect-error: this is fine since we are looping through the object
46
- const value = proxy[key];
47
-
44
+ for (const [key, value] of Object.entries(proxy)) {
48
45
  Object.defineProperty(proxy, key, {
49
46
  enumerable: true,
50
47
  configurable: true,
@@ -26,9 +26,10 @@ const createStore = <T,>(getInitialState: () => T) => {
26
26
 
27
27
  const setState = (newState: T) => {
28
28
  state = deepFreeze(newState);
29
- didUpdate = true;
30
29
 
31
- if (!isBatching) {
30
+ if (isBatching) {
31
+ didUpdate = true;
32
+ } else {
32
33
  listeners.forEach((listener) => listener());
33
34
  }
34
35
  };
package/src/utilities.tsx CHANGED
@@ -26,6 +26,25 @@ export type NoExcessObject<Provided, Allowed> = Record<
26
26
  never
27
27
  >;
28
28
 
29
+ /**
30
+ * Check an object captured as a generic type against an allowed type,
31
+ * e.g. an object returned from a callback isn't checked for excess properties.
32
+ */
33
+ export type NoExcessCallbackReturn<
34
+ Provided extends {},
35
+ Allowed extends {},
36
+ > = Allowed & Provided & NoExcessObject<Provided, Allowed>;
37
+
38
+ /**
39
+ * Apply excess property checking to a value,
40
+ * or to the return value if the value is a function.
41
+ */
42
+ export type NoExcessValue<Provided, Allowed extends {}> = Provided extends (
43
+ ...args: never[]
44
+ ) => infer Return
45
+ ? (...args: never[]) => Allowed & NoExcessObject<Return, Allowed>
46
+ : NoExcessObject<Provided, Allowed>;
47
+
29
48
  /**
30
49
  * We get a union type when using keyof, but we want an intersection instead.
31
50
  * https://stackoverflow.com/a/50375286/1665026