react-native-screen-transitions 2.0.2 → 2.0.3

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 (57) hide show
  1. package/package.json +5 -3
  2. package/src/__tests__ /geometry.test.ts +127 -0
  3. package/src/components/bounds-activator.tsx +29 -0
  4. package/src/components/controllers/screen-lifecycle.tsx +72 -0
  5. package/src/components/create-transition-aware-component.tsx +99 -0
  6. package/src/components/root-transition-aware.tsx +56 -0
  7. package/src/configs/index.ts +2 -0
  8. package/src/configs/presets.ts +227 -0
  9. package/src/configs/specs.ts +9 -0
  10. package/src/hooks/animation/use-associated-style.tsx +28 -0
  11. package/src/hooks/animation/use-screen-animation.tsx +142 -0
  12. package/src/hooks/bounds/use-bound-measurer.tsx +71 -0
  13. package/src/hooks/gestures/use-build-gestures.tsx +369 -0
  14. package/src/hooks/gestures/use-scroll-progress.tsx +60 -0
  15. package/src/hooks/use-stable-callback.tsx +15 -0
  16. package/src/index.ts +32 -0
  17. package/src/integrations/native-stack/navigators/createNativeStackNavigator.tsx +112 -0
  18. package/src/integrations/native-stack/utils/debounce.tsx +14 -0
  19. package/src/integrations/native-stack/utils/getModalRoutesKeys.ts +21 -0
  20. package/src/integrations/native-stack/utils/useAnimatedHeaderHeight.tsx +18 -0
  21. package/src/integrations/native-stack/utils/useDismissedRouteError.tsx +30 -0
  22. package/src/integrations/native-stack/utils/useInvalidPreventRemoveError.tsx +31 -0
  23. package/src/integrations/native-stack/views/FontProcessor.native.tsx +12 -0
  24. package/src/integrations/native-stack/views/FontProcessor.tsx +5 -0
  25. package/src/integrations/native-stack/views/FooterComponent.tsx +10 -0
  26. package/src/integrations/native-stack/views/NativeStackView.native.tsx +657 -0
  27. package/src/integrations/native-stack/views/NativeStackView.tsx +214 -0
  28. package/src/integrations/native-stack/views/useHeaderConfigProps.tsx +295 -0
  29. package/src/providers/gestures.tsx +89 -0
  30. package/src/providers/keys.tsx +38 -0
  31. package/src/stores/animations.ts +45 -0
  32. package/src/stores/bounds.ts +71 -0
  33. package/src/stores/gestures.ts +55 -0
  34. package/src/stores/navigator-dismiss-state.ts +17 -0
  35. package/src/stores/utils/reset-stores-for-screen.ts +14 -0
  36. package/src/types/animation.ts +76 -0
  37. package/src/types/bounds.ts +82 -0
  38. package/src/types/core.ts +50 -0
  39. package/src/types/gesture.ts +33 -0
  40. package/src/types/navigator.ts +744 -0
  41. package/src/types/utils.ts +3 -0
  42. package/src/utils/animation/animate.ts +28 -0
  43. package/src/utils/animation/run-transition.ts +49 -0
  44. package/src/utils/bounds/_types/builder.ts +35 -0
  45. package/src/utils/bounds/_types/geometry.ts +17 -0
  46. package/src/utils/bounds/_types/get-bounds.ts +10 -0
  47. package/src/utils/bounds/build-bound-styles.ts +184 -0
  48. package/src/utils/bounds/constants.ts +28 -0
  49. package/src/utils/bounds/flatten-styles.ts +21 -0
  50. package/src/utils/bounds/geometry.ts +113 -0
  51. package/src/utils/bounds/get-bounds.ts +56 -0
  52. package/src/utils/bounds/index.ts +46 -0
  53. package/src/utils/bounds/style-composers.ts +172 -0
  54. package/src/utils/gesture/apply-gesture-activation-criteria.ts +109 -0
  55. package/src/utils/gesture/map-gesture-to-progress.ts +11 -0
  56. package/src/utils/gesture/normalize-gesture-translation.ts +20 -0
  57. package/src/utils/index.ts +1 -0
@@ -0,0 +1,112 @@
1
+ import {
2
+ createNavigatorFactory,
3
+ type EventArg,
4
+ type NavigatorTypeBagBase,
5
+ type ParamListBase,
6
+ type StackActionHelpers,
7
+ StackActions,
8
+ type StackNavigationState,
9
+ StackRouter,
10
+ type StackRouterOptions,
11
+ type StaticConfig,
12
+ type TypedNavigator,
13
+ useNavigationBuilder,
14
+ } from "@react-navigation/native";
15
+ import * as React from "react";
16
+
17
+ import type {
18
+ NativeStackNavigationEventMap,
19
+ NativeStackNavigationOptions,
20
+ NativeStackNavigationProp,
21
+ NativeStackNavigatorProps,
22
+ } from "../../../types/navigator";
23
+ import { NativeStackView } from "../views/NativeStackView";
24
+
25
+ function NativeStackNavigator({
26
+ id,
27
+ initialRouteName,
28
+ children,
29
+ layout,
30
+ screenListeners,
31
+ screenOptions,
32
+ screenLayout,
33
+ ...rest
34
+ }: NativeStackNavigatorProps) {
35
+ const { state, describe, descriptors, navigation, NavigationContent } =
36
+ useNavigationBuilder<
37
+ StackNavigationState<ParamListBase>,
38
+ StackRouterOptions,
39
+ StackActionHelpers<ParamListBase>,
40
+ NativeStackNavigationOptions,
41
+ NativeStackNavigationEventMap
42
+ >(StackRouter, {
43
+ id,
44
+ initialRouteName,
45
+ children,
46
+ layout,
47
+ screenListeners,
48
+ screenOptions,
49
+ screenLayout,
50
+ });
51
+
52
+ React.useEffect(
53
+ () =>
54
+ // @ts-expect-error: there may not be a tab navigator in parent
55
+ navigation?.addListener?.("tabPress", (e: any) => {
56
+ const isFocused = navigation.isFocused();
57
+
58
+ // Run the operation in the next frame so we're sure all listeners have been run
59
+ // This is necessary to know if preventDefault() has been called
60
+ requestAnimationFrame(() => {
61
+ if (
62
+ state.index > 0 &&
63
+ isFocused &&
64
+ !(e as EventArg<"tabPress", true>).defaultPrevented
65
+ ) {
66
+ // When user taps on already focused tab and we're inside the tab,
67
+ // reset the stack to replicate native behaviour
68
+ navigation.dispatch({
69
+ ...StackActions.popToTop(),
70
+ target: state.key,
71
+ });
72
+ }
73
+ });
74
+ }),
75
+ [navigation, state.index, state.key],
76
+ );
77
+
78
+ return (
79
+ <NavigationContent>
80
+ <NativeStackView
81
+ {...rest}
82
+ state={state}
83
+ navigation={navigation}
84
+ descriptors={descriptors}
85
+ describe={describe}
86
+ />
87
+ </NavigationContent>
88
+ );
89
+ }
90
+
91
+ export function createNativeStackNavigator<
92
+ const ParamList extends ParamListBase,
93
+ const NavigatorID extends string | undefined = undefined,
94
+ const TypeBag extends NavigatorTypeBagBase = {
95
+ ParamList: ParamList;
96
+ NavigatorID: NavigatorID;
97
+ State: StackNavigationState<ParamList>;
98
+ ScreenOptions: NativeStackNavigationOptions;
99
+ EventMap: NativeStackNavigationEventMap;
100
+ NavigationList: {
101
+ [RouteName in keyof ParamList]: NativeStackNavigationProp<
102
+ ParamList,
103
+ RouteName,
104
+ NavigatorID
105
+ >;
106
+ };
107
+ Navigator: typeof NativeStackNavigator;
108
+ },
109
+ const Config extends StaticConfig<TypeBag> = StaticConfig<TypeBag>,
110
+ >(config?: Config): TypedNavigator<TypeBag, Config> {
111
+ return createNavigatorFactory(NativeStackNavigator)(config);
112
+ }
@@ -0,0 +1,14 @@
1
+ export function debounce<T extends (...args: any[]) => void>(
2
+ func: T,
3
+ duration: number
4
+ ): T {
5
+ let timeout: ReturnType<typeof setTimeout>;
6
+
7
+ return function (this: unknown, ...args) {
8
+ clearTimeout(timeout);
9
+
10
+ timeout = setTimeout(() => {
11
+ func.apply(this, args);
12
+ }, duration);
13
+ } as T;
14
+ }
@@ -0,0 +1,21 @@
1
+ import type { Route } from "@react-navigation/native";
2
+
3
+ import type { NativeStackDescriptorMap } from "../../../types/navigator";
4
+
5
+ export const getModalRouteKeys = (
6
+ routes: Route<string>[],
7
+ descriptors: NativeStackDescriptorMap,
8
+ ) =>
9
+ routes.reduce<string[]>((acc, route) => {
10
+ const { presentation } = descriptors[route.key]?.options ?? {};
11
+
12
+ if (
13
+ (acc.length && !presentation) ||
14
+ presentation === "modal" ||
15
+ presentation === "transparentModal"
16
+ ) {
17
+ acc.push(route.key);
18
+ }
19
+
20
+ return acc;
21
+ }, []);
@@ -0,0 +1,18 @@
1
+ import * as React from 'react';
2
+ import type { Animated } from 'react-native';
3
+
4
+ export const AnimatedHeaderHeightContext = React.createContext<
5
+ Animated.AnimatedInterpolation<number> | undefined
6
+ >(undefined);
7
+
8
+ export function useAnimatedHeaderHeight() {
9
+ const animatedValue = React.useContext(AnimatedHeaderHeightContext);
10
+
11
+ if (animatedValue === undefined) {
12
+ throw new Error(
13
+ "Couldn't find the header height. Are you inside a screen in a native stack navigator?"
14
+ );
15
+ }
16
+
17
+ return animatedValue;
18
+ }
@@ -0,0 +1,30 @@
1
+ import type {
2
+ ParamListBase,
3
+ StackNavigationState,
4
+ } from '@react-navigation/native';
5
+ import * as React from 'react';
6
+
7
+ export function useDismissedRouteError(
8
+ state: StackNavigationState<ParamListBase>
9
+ ) {
10
+ const [nextDismissedKey, setNextDismissedKey] = React.useState<string | null>(
11
+ null
12
+ );
13
+
14
+ const dismissedRouteName = nextDismissedKey
15
+ ? state.routes.find((route) => route.key === nextDismissedKey)?.name
16
+ : null;
17
+
18
+ React.useEffect(() => {
19
+ if (dismissedRouteName) {
20
+ const message =
21
+ `The screen '${dismissedRouteName}' was removed natively but didn't get removed from JS state. ` +
22
+ `This can happen if the action was prevented in a 'beforeRemove' listener, which is not fully supported in native-stack.\n\n` +
23
+ `Consider using a 'usePreventRemove' hook with 'headerBackButtonMenuEnabled: false' to prevent users from natively going back multiple screens.`;
24
+
25
+ console.error(message);
26
+ }
27
+ }, [dismissedRouteName]);
28
+
29
+ return { setNextDismissedKey };
30
+ }
@@ -0,0 +1,31 @@
1
+ import { usePreventRemoveContext } from "@react-navigation/native";
2
+ import * as React from "react";
3
+
4
+ import type { NativeStackDescriptorMap } from "../../../types/navigator";
5
+
6
+ export function useInvalidPreventRemoveError(
7
+ descriptors: NativeStackDescriptorMap,
8
+ ) {
9
+ const { preventedRoutes } = usePreventRemoveContext();
10
+ const preventedRouteKey = Object.keys(preventedRoutes)[0];
11
+ const preventedDescriptor = descriptors[preventedRouteKey];
12
+ const isHeaderBackButtonMenuEnabledOnPreventedScreen =
13
+ preventedDescriptor?.options?.headerBackButtonMenuEnabled;
14
+ const preventedRouteName = preventedDescriptor?.route?.name;
15
+
16
+ React.useEffect(() => {
17
+ if (
18
+ preventedRouteKey != null &&
19
+ isHeaderBackButtonMenuEnabledOnPreventedScreen
20
+ ) {
21
+ const message =
22
+ `The screen ${preventedRouteName} uses 'usePreventRemove' hook alongside 'headerBackButtonMenuEnabled: true', which is not supported. \n\n` +
23
+ `Consider removing 'headerBackButtonMenuEnabled: true' from ${preventedRouteName} screen to get rid of this error.`;
24
+ console.error(message);
25
+ }
26
+ }, [
27
+ preventedRouteKey,
28
+ isHeaderBackButtonMenuEnabledOnPreventedScreen,
29
+ preventedRouteName,
30
+ ]);
31
+ }
@@ -0,0 +1,12 @@
1
+ // @ts-expect-error importing private module
2
+ import ReactNativeStyleAttributes from 'react-native/Libraries/Components/View/ReactNativeStyleAttributes';
3
+
4
+ export function processFonts(
5
+ fontFamilies: (string | undefined)[]
6
+ ): (string | undefined)[] {
7
+ const fontFamilyProcessor = ReactNativeStyleAttributes.fontFamily?.process;
8
+ if (typeof fontFamilyProcessor === 'function') {
9
+ return fontFamilies.map(fontFamilyProcessor);
10
+ }
11
+ return fontFamilies;
12
+ }
@@ -0,0 +1,5 @@
1
+ export function processFonts(
2
+ _: (string | undefined)[]
3
+ ): (string | undefined)[] {
4
+ throw new Error('Not supported on Web');
5
+ }
@@ -0,0 +1,10 @@
1
+ import type React from "react";
2
+ import { ScreenFooter } from "react-native-screens";
3
+
4
+ type FooterProps = {
5
+ children?: React.ReactNode;
6
+ };
7
+
8
+ export function FooterComponent({ children }: FooterProps) {
9
+ return <ScreenFooter collapsable={false}>{children}</ScreenFooter>;
10
+ }