react-native-screen-transitions 2.0.1 → 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.
- package/package.json +4 -3
- package/src/__tests__ /geometry.test.ts +127 -0
- package/src/components/bounds-activator.tsx +29 -0
- package/src/components/controllers/screen-lifecycle.tsx +72 -0
- package/src/components/create-transition-aware-component.tsx +99 -0
- package/src/components/root-transition-aware.tsx +56 -0
- package/src/configs/index.ts +2 -0
- package/src/configs/presets.ts +227 -0
- package/src/configs/specs.ts +9 -0
- package/src/hooks/animation/use-associated-style.tsx +28 -0
- package/src/hooks/animation/use-screen-animation.tsx +142 -0
- package/src/hooks/bounds/use-bound-measurer.tsx +71 -0
- package/src/hooks/gestures/use-build-gestures.tsx +369 -0
- package/src/hooks/gestures/use-scroll-progress.tsx +60 -0
- package/src/hooks/use-stable-callback.tsx +15 -0
- package/src/index.ts +32 -0
- package/src/integrations/native-stack/navigators/createNativeStackNavigator.tsx +112 -0
- package/src/integrations/native-stack/utils/debounce.tsx +14 -0
- package/src/integrations/native-stack/utils/getModalRoutesKeys.ts +21 -0
- package/src/integrations/native-stack/utils/useAnimatedHeaderHeight.tsx +18 -0
- package/src/integrations/native-stack/utils/useDismissedRouteError.tsx +30 -0
- package/src/integrations/native-stack/utils/useInvalidPreventRemoveError.tsx +31 -0
- package/src/integrations/native-stack/views/FontProcessor.native.tsx +12 -0
- package/src/integrations/native-stack/views/FontProcessor.tsx +5 -0
- package/src/integrations/native-stack/views/FooterComponent.tsx +10 -0
- package/src/integrations/native-stack/views/NativeStackView.native.tsx +657 -0
- package/src/integrations/native-stack/views/NativeStackView.tsx +214 -0
- package/src/integrations/native-stack/views/useHeaderConfigProps.tsx +295 -0
- package/src/providers/gestures.tsx +89 -0
- package/src/providers/keys.tsx +38 -0
- package/src/stores/animations.ts +45 -0
- package/src/stores/bounds.ts +71 -0
- package/src/stores/gestures.ts +55 -0
- package/src/stores/navigator-dismiss-state.ts +17 -0
- package/src/stores/utils/reset-stores-for-screen.ts +14 -0
- package/src/types/animation.ts +76 -0
- package/src/types/bounds.ts +82 -0
- package/src/types/core.ts +50 -0
- package/src/types/gesture.ts +33 -0
- package/src/types/navigator.ts +744 -0
- package/src/types/utils.ts +3 -0
- package/src/utils/animation/animate.ts +28 -0
- package/src/utils/animation/run-transition.ts +49 -0
- package/src/utils/bounds/_types/builder.ts +35 -0
- package/src/utils/bounds/_types/geometry.ts +17 -0
- package/src/utils/bounds/_types/get-bounds.ts +10 -0
- package/src/utils/bounds/build-bound-styles.ts +184 -0
- package/src/utils/bounds/constants.ts +28 -0
- package/src/utils/bounds/flatten-styles.ts +21 -0
- package/src/utils/bounds/geometry.ts +113 -0
- package/src/utils/bounds/get-bounds.ts +56 -0
- package/src/utils/bounds/index.ts +46 -0
- package/src/utils/bounds/style-composers.ts +172 -0
- package/src/utils/gesture/apply-gesture-activation-criteria.ts +109 -0
- package/src/utils/gesture/map-gesture-to-progress.ts +11 -0
- package/src/utils/gesture/normalize-gesture-translation.ts +20 -0
- 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,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
|
+
}
|