@react-navigation/stack 8.0.0-alpha.5 → 8.0.0-alpha.51

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 (133) hide show
  1. package/lib/module/TransitionConfigs/CardStyleInterpolators.js +36 -0
  2. package/lib/module/TransitionConfigs/CardStyleInterpolators.js.map +1 -1
  3. package/lib/module/index.js +2 -2
  4. package/lib/module/index.js.map +1 -1
  5. package/lib/module/navigators/createStackNavigator.js +3 -7
  6. package/lib/module/navigators/createStackNavigator.js.map +1 -1
  7. package/lib/module/utils/GestureHandlerContext.js +5 -0
  8. package/lib/module/utils/GestureHandlerContext.js.map +1 -0
  9. package/lib/module/utils/gestureActivationCriteria.js +10 -8
  10. package/lib/module/utils/gestureActivationCriteria.js.map +1 -1
  11. package/lib/module/utils/useCardAnimation.js +1 -1
  12. package/lib/module/utils/useCardAnimation.js.map +1 -1
  13. package/lib/module/utils/useGestureHandler.js +9 -0
  14. package/lib/module/utils/useGestureHandler.js.map +1 -0
  15. package/lib/module/utils/useKeyboardManager.js +71 -26
  16. package/lib/module/utils/useKeyboardManager.js.map +1 -1
  17. package/lib/module/views/GestureHandler.js +15 -14
  18. package/lib/module/views/GestureHandler.js.map +1 -1
  19. package/lib/module/views/GestureHandlerNative.js +16 -10
  20. package/lib/module/views/GestureHandlerNative.js.map +1 -1
  21. package/lib/module/views/Header/Header.js +2 -2
  22. package/lib/module/views/Header/Header.js.map +1 -1
  23. package/lib/module/views/Header/HeaderContainer.js +52 -18
  24. package/lib/module/views/Header/HeaderContainer.js.map +1 -1
  25. package/lib/module/views/Header/HeaderSegment.js +3 -3
  26. package/lib/module/views/Header/HeaderSegment.js.map +1 -1
  27. package/lib/module/views/Stack/Card.js +108 -93
  28. package/lib/module/views/Stack/Card.js.map +1 -1
  29. package/lib/module/views/Stack/CardContainer.js +72 -98
  30. package/lib/module/views/Stack/CardContainer.js.map +1 -1
  31. package/lib/module/views/Stack/CardContent.js +2 -0
  32. package/lib/module/views/Stack/CardContent.js.map +1 -1
  33. package/lib/module/views/Stack/CardStack.js +183 -132
  34. package/lib/module/views/Stack/CardStack.js.map +1 -1
  35. package/lib/module/views/Stack/StackView.js +103 -54
  36. package/lib/module/views/Stack/StackView.js.map +1 -1
  37. package/lib/typescript/src/TransitionConfigs/CardStyleInterpolators.d.ts +1 -1
  38. package/lib/typescript/src/TransitionConfigs/CardStyleInterpolators.d.ts.map +1 -1
  39. package/lib/typescript/src/TransitionConfigs/HeaderStyleInterpolators.d.ts +1 -1
  40. package/lib/typescript/src/TransitionConfigs/HeaderStyleInterpolators.d.ts.map +1 -1
  41. package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts +1 -1
  42. package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts.map +1 -1
  43. package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts +1 -1
  44. package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts.map +1 -1
  45. package/lib/typescript/src/index.d.ts +12 -12
  46. package/lib/typescript/src/index.d.ts.map +1 -1
  47. package/lib/typescript/src/navigators/createStackNavigator.d.ts +10 -15
  48. package/lib/typescript/src/navigators/createStackNavigator.d.ts.map +1 -1
  49. package/lib/typescript/src/types.d.ts +52 -55
  50. package/lib/typescript/src/types.d.ts.map +1 -1
  51. package/lib/typescript/src/utils/CardAnimationContext.d.ts +1 -1
  52. package/lib/typescript/src/utils/CardAnimationContext.d.ts.map +1 -1
  53. package/lib/typescript/src/utils/GestureHandlerContext.d.ts +3 -0
  54. package/lib/typescript/src/utils/GestureHandlerContext.d.ts.map +1 -0
  55. package/lib/typescript/src/utils/gestureActivationCriteria.d.ts +4 -51
  56. package/lib/typescript/src/utils/gestureActivationCriteria.d.ts.map +1 -1
  57. package/lib/typescript/src/utils/getDistanceForDirection.d.ts +1 -1
  58. package/lib/typescript/src/utils/getDistanceForDirection.d.ts.map +1 -1
  59. package/lib/typescript/src/utils/getInvertedMultiplier.d.ts +1 -1
  60. package/lib/typescript/src/utils/getInvertedMultiplier.d.ts.map +1 -1
  61. package/lib/typescript/src/utils/getModalRoutesKeys.d.ts +1 -1
  62. package/lib/typescript/src/utils/getModalRoutesKeys.d.ts.map +1 -1
  63. package/lib/typescript/src/utils/getShadowStyle.d.ts +5 -5
  64. package/lib/typescript/src/utils/useCardAnimation.d.ts +1 -1
  65. package/lib/typescript/src/utils/useCardAnimation.d.ts.map +1 -1
  66. package/lib/typescript/src/utils/useGestureHandler.d.ts +2 -0
  67. package/lib/typescript/src/utils/useGestureHandler.d.ts.map +1 -0
  68. package/lib/typescript/src/utils/useKeyboardManager.d.ts +12 -2
  69. package/lib/typescript/src/utils/useKeyboardManager.d.ts.map +1 -1
  70. package/lib/typescript/src/views/GestureHandler.android.d.ts +1 -1
  71. package/lib/typescript/src/views/GestureHandler.android.d.ts.map +1 -1
  72. package/lib/typescript/src/views/GestureHandler.d.ts +7 -11
  73. package/lib/typescript/src/views/GestureHandler.d.ts.map +1 -1
  74. package/lib/typescript/src/views/GestureHandler.ios.d.ts +1 -1
  75. package/lib/typescript/src/views/GestureHandler.ios.d.ts.map +1 -1
  76. package/lib/typescript/src/views/GestureHandlerNative.d.ts +9 -4
  77. package/lib/typescript/src/views/GestureHandlerNative.d.ts.map +1 -1
  78. package/lib/typescript/src/views/Header/Header.d.ts +2 -2
  79. package/lib/typescript/src/views/Header/Header.d.ts.map +1 -1
  80. package/lib/typescript/src/views/Header/HeaderContainer.d.ts +9 -3
  81. package/lib/typescript/src/views/Header/HeaderContainer.d.ts.map +1 -1
  82. package/lib/typescript/src/views/Header/HeaderSegment.d.ts +5 -4
  83. package/lib/typescript/src/views/Header/HeaderSegment.d.ts.map +1 -1
  84. package/lib/typescript/src/views/Stack/Card.d.ts +5 -5
  85. package/lib/typescript/src/views/Stack/Card.d.ts.map +1 -1
  86. package/lib/typescript/src/views/Stack/CardContainer.d.ts +5 -7
  87. package/lib/typescript/src/views/Stack/CardContainer.d.ts.map +1 -1
  88. package/lib/typescript/src/views/Stack/CardContent.d.ts +1 -1
  89. package/lib/typescript/src/views/Stack/CardContent.d.ts.map +1 -1
  90. package/lib/typescript/src/views/Stack/CardStack.d.ts +9 -6
  91. package/lib/typescript/src/views/Stack/CardStack.d.ts.map +1 -1
  92. package/lib/typescript/src/views/Stack/StackView.d.ts +14 -17
  93. package/lib/typescript/src/views/Stack/StackView.d.ts.map +1 -1
  94. package/package.json +23 -24
  95. package/src/TransitionConfigs/CardStyleInterpolators.tsx +44 -0
  96. package/src/index.tsx +3 -2
  97. package/src/navigators/createStackNavigator.tsx +9 -40
  98. package/src/types.tsx +79 -75
  99. package/src/utils/GestureHandlerContext.tsx +5 -0
  100. package/src/utils/gestureActivationCriteria.tsx +14 -10
  101. package/src/utils/useCardAnimation.tsx +1 -1
  102. package/src/utils/useGestureHandler.tsx +9 -0
  103. package/src/utils/useKeyboardManager.tsx +96 -33
  104. package/src/views/GestureHandler.tsx +27 -16
  105. package/src/views/GestureHandlerNative.tsx +26 -12
  106. package/src/views/Header/Header.tsx +2 -2
  107. package/src/views/Header/HeaderContainer.tsx +69 -33
  108. package/src/views/Header/HeaderSegment.tsx +6 -7
  109. package/src/views/Stack/Card.tsx +154 -119
  110. package/src/views/Stack/CardContainer.tsx +86 -127
  111. package/src/views/Stack/CardContent.tsx +1 -0
  112. package/src/views/Stack/CardStack.tsx +409 -330
  113. package/src/views/Stack/StackView.tsx +176 -79
  114. package/lib/module/utils/GestureHandlerRefContext.js +0 -5
  115. package/lib/module/utils/GestureHandlerRefContext.js.map +0 -1
  116. package/lib/module/utils/findLastIndex.js +0 -11
  117. package/lib/module/utils/findLastIndex.js.map +0 -1
  118. package/lib/module/utils/useGestureHandlerRef.js +0 -12
  119. package/lib/module/utils/useGestureHandlerRef.js.map +0 -1
  120. package/lib/module/views/Stack/CardA11yWrapper.js +0 -43
  121. package/lib/module/views/Stack/CardA11yWrapper.js.map +0 -1
  122. package/lib/typescript/src/utils/GestureHandlerRefContext.d.ts +0 -3
  123. package/lib/typescript/src/utils/GestureHandlerRefContext.d.ts.map +0 -1
  124. package/lib/typescript/src/utils/findLastIndex.d.ts +0 -2
  125. package/lib/typescript/src/utils/findLastIndex.d.ts.map +0 -1
  126. package/lib/typescript/src/utils/useGestureHandlerRef.d.ts +0 -3
  127. package/lib/typescript/src/utils/useGestureHandlerRef.d.ts.map +0 -1
  128. package/lib/typescript/src/views/Stack/CardA11yWrapper.d.ts +0 -15
  129. package/lib/typescript/src/views/Stack/CardA11yWrapper.d.ts.map +0 -1
  130. package/src/utils/GestureHandlerRefContext.tsx +0 -5
  131. package/src/utils/findLastIndex.tsx +0 -9
  132. package/src/utils/useGestureHandlerRef.tsx +0 -15
  133. package/src/views/Stack/CardA11yWrapper.tsx +0 -67
@@ -1,12 +1,18 @@
1
1
  import { getHeaderTitle, HeaderBackContext } from '@react-navigation/elements';
2
+ import { ActivityView } from '@react-navigation/elements/internal';
2
3
  import {
3
4
  NavigationProvider,
4
- type ParamListBase,
5
5
  type Route,
6
6
  useLinkBuilder,
7
7
  } from '@react-navigation/native';
8
8
  import * as React from 'react';
9
- import { type StyleProp, StyleSheet, View, type ViewStyle } from 'react-native';
9
+ import {
10
+ type LayoutChangeEvent,
11
+ type StyleProp,
12
+ StyleSheet,
13
+ View,
14
+ type ViewStyle,
15
+ } from 'react-native';
10
16
 
11
17
  import {
12
18
  forNoAnimation,
@@ -14,20 +20,21 @@ import {
14
20
  forSlideRight,
15
21
  forSlideUp,
16
22
  } from '../../TransitionConfigs/HeaderStyleInterpolators';
17
- import type {
18
- Scene,
19
- StackHeaderMode,
20
- StackHeaderProps,
21
- StackNavigationProp,
22
- } from '../../types';
23
+ import type { Scene, StackHeaderMode, StackHeaderProps } from '../../types';
23
24
  import { Header } from './Header';
24
25
 
26
+ export type HeaderHeight = {
27
+ measured: boolean;
28
+ value: number;
29
+ };
30
+
25
31
  export type Props = {
26
32
  mode: StackHeaderMode;
27
33
  scenes: (Scene | undefined)[];
28
34
  getPreviousScene: (props: { route: Route<string> }) => Scene | undefined;
29
35
  getFocusedRoute: () => Route<string>;
30
- onContentHeightChange?: (props: {
36
+ contentHeight: HeaderHeight | undefined;
37
+ onContentHeightChange: (props: {
31
38
  route: Route<string>;
32
39
  height: number;
33
40
  }) => void;
@@ -39,19 +46,17 @@ export function HeaderContainer({
39
46
  scenes,
40
47
  getPreviousScene,
41
48
  getFocusedRoute,
49
+ contentHeight,
42
50
  onContentHeightChange,
43
51
  style,
44
52
  }: Props) {
45
53
  const focusedRoute = getFocusedRoute();
46
- const parentHeaderBack = React.useContext(HeaderBackContext);
54
+ const parentHeaderBack = React.use(HeaderBackContext);
47
55
  const { buildHref } = useLinkBuilder();
48
56
 
49
57
  return (
50
58
  <View style={[styles.container, style]}>
51
- {/* We render header only on two top-most headers as
52
- a workaround for https://github.com/react-navigation/react-navigation/issues/12456.
53
- If the header is persisted, it might be placed incorrectly when navigating back. */}
54
- {scenes.slice(-2).map((scene, i, self) => {
59
+ {scenes.map((scene, i, self) => {
55
60
  if ((mode === 'screen' && i !== self.length - 1) || !scene) {
56
61
  return null;
57
62
  }
@@ -96,6 +101,9 @@ export function HeaderContainer({
96
101
  headerMode: previousHeaderMode,
97
102
  } = previousDescriptor?.options || {};
98
103
 
104
+ const { headerTransparent: nextHeaderTransparent } =
105
+ nextDescriptor?.options || {};
106
+
99
107
  // If any of the next screens don't have a header or header is part of the screen
100
108
  // Then we need to move this header offscreen so that it doesn't cover it
101
109
  const nextHeaderlessScene = self.slice(i + 1).find((scene) => {
@@ -115,6 +123,9 @@ export function HeaderContainer({
115
123
  // We still need to animate when coming back from next scene
116
124
  // A hacky way to check this is if the next scene exists
117
125
  !nextDescriptor) ||
126
+ // If next header is tranparent, we want to treat this header as static
127
+ // Otherwise it may show through the next header
128
+ nextHeaderTransparent ||
118
129
  nextHeaderlessScene;
119
130
 
120
131
  const props: StackHeaderProps = {
@@ -122,8 +133,7 @@ export function HeaderContainer({
122
133
  progress: scene.progress,
123
134
  options: scene.descriptor.options,
124
135
  route: scene.descriptor.route,
125
- navigation: scene.descriptor
126
- .navigation as StackNavigationProp<ParamListBase>,
136
+ navigation: scene.descriptor.navigation,
127
137
  styleInterpolator:
128
138
  mode === 'float'
129
139
  ? isHeaderStatic
@@ -137,6 +147,26 @@ export function HeaderContainer({
137
147
  : forNoAnimation,
138
148
  };
139
149
 
150
+ const onRef = (node: React.ComponentRef<typeof View> | null) => {
151
+ node?.measure((_x, _y, _width, height) => {
152
+ if (height && !contentHeight?.measured) {
153
+ onContentHeightChange({
154
+ route: scene.descriptor.route,
155
+ height,
156
+ });
157
+ }
158
+ });
159
+ };
160
+
161
+ const onLayout = (e: LayoutChangeEvent) => {
162
+ const { height } = e.nativeEvent.layout;
163
+
164
+ onContentHeightChange({
165
+ route: scene.descriptor.route,
166
+ height,
167
+ });
168
+ };
169
+
140
170
  return (
141
171
  <NavigationProvider
142
172
  key={scene.descriptor.route.key}
@@ -144,31 +174,37 @@ export function HeaderContainer({
144
174
  route={scene.descriptor.route}
145
175
  >
146
176
  <View
147
- onLayout={
148
- onContentHeightChange
149
- ? (e) => {
150
- const { height } = e.nativeEvent.layout;
151
-
152
- onContentHeightChange({
153
- route: scene.descriptor.route,
154
- height,
155
- });
156
- }
157
- : undefined
158
- }
159
- aria-hidden={!isFocused}
160
177
  style={[
161
- // Avoid positioning the focused header absolutely
178
+ // Avoid absolutely positioning the focused header unnecessarily
162
179
  // Otherwise accessibility tools don't seem to be able to find it
163
180
  (mode === 'float' && !isFocused) || headerTransparent
164
- ? styles.header
181
+ ? [
182
+ styles.absolute,
183
+ mode === 'screen'
184
+ ? // For transparent headers, specify a min height
185
+ // This is needed for screen readers, and testing tools on Android
186
+ // For float header, it's handled in CardStack
187
+ { minHeight: contentHeight?.value }
188
+ : null,
189
+ ]
165
190
  : null,
166
191
  {
167
192
  pointerEvents: isFocused ? 'box-none' : 'none',
168
193
  },
169
194
  ]}
170
195
  >
171
- {header !== undefined ? header(props) : <Header {...props} />}
196
+ <View
197
+ ref={onRef}
198
+ onLayout={onLayout}
199
+ style={{ pointerEvents: 'box-none' }}
200
+ >
201
+ <ActivityView
202
+ mode={isFocused ? 'normal' : 'inert'}
203
+ visible={true}
204
+ >
205
+ {header !== undefined ? header(props) : <Header {...props} />}
206
+ </ActivityView>
207
+ </View>
172
208
  </View>
173
209
  </NavigationProvider>
174
210
  );
@@ -181,7 +217,7 @@ const styles = StyleSheet.create({
181
217
  container: {
182
218
  pointerEvents: 'box-none',
183
219
  },
184
- header: {
220
+ absolute: {
185
221
  position: 'absolute',
186
222
  top: 0,
187
223
  start: 0,
@@ -20,8 +20,8 @@ type Props = Omit<StackHeaderOptions, 'headerStatusBarHeight'> & {
20
20
  headerStatusBarHeight: number;
21
21
  title: string;
22
22
  modal: boolean;
23
- onGoBack?: () => void;
24
- backHref?: string;
23
+ onGoBack?: (() => void) | undefined;
24
+ backHref?: string | undefined;
25
25
  progress: SceneProgress;
26
26
  styleInterpolator: StackHeaderStyleInterpolator;
27
27
  };
@@ -40,7 +40,7 @@ export function HeaderSegment(props: Props) {
40
40
  ? (props: HeaderBackButtonProps) => <HeaderBackButton {...props} />
41
41
  : undefined,
42
42
  headerRight: right,
43
- headerBackImage,
43
+ headerBackIcon,
44
44
  headerBackTitle,
45
45
  headerBackButtonDisplayMode,
46
46
  headerBackTruncatedTitle,
@@ -64,9 +64,8 @@ export function HeaderSegment(props: Props) {
64
64
  topInset: headerStatusBarHeight,
65
65
  });
66
66
 
67
- const { height = defaultHeight } = StyleSheet.flatten(
68
- customHeaderStyle || {}
69
- ) as ViewStyle;
67
+ const { height = defaultHeight } = (StyleSheet.flatten(customHeaderStyle) ??
68
+ {}) as ViewStyle;
70
69
 
71
70
  const headerHeight = typeof height === 'number' ? height : defaultHeight;
72
71
 
@@ -93,7 +92,7 @@ export function HeaderSegment(props: Props) {
93
92
  left({
94
93
  ...props,
95
94
  href: backHref,
96
- backImage: headerBackImage,
95
+ icon: headerBackIcon,
97
96
  accessibilityLabel: headerBackAccessibilityLabel,
98
97
  testID: headerBackTestID,
99
98
  allowFontScaling: headerBackAllowFontScaling,
@@ -9,6 +9,7 @@ import {
9
9
  View,
10
10
  type ViewStyle,
11
11
  } from 'react-native';
12
+ import type { PanGestureConfig } from 'react-native-gesture-handler';
12
13
  import type { EdgeInsets } from 'react-native-safe-area-context';
13
14
  import useLatestCallback from 'use-latest-callback';
14
15
 
@@ -23,11 +24,7 @@ import { gestureActivationCriteria } from '../../utils/gestureActivationCriteria
23
24
  import { getDistanceForDirection } from '../../utils/getDistanceForDirection';
24
25
  import { getInvertedMultiplier } from '../../utils/getInvertedMultiplier';
25
26
  import { getShadowStyle } from '../../utils/getShadowStyle';
26
- import {
27
- GestureState,
28
- PanGestureHandler,
29
- type PanGestureHandlerGestureEvent,
30
- } from '../GestureHandler';
27
+ import { GestureDetector, usePanGesture } from '../GestureHandler';
31
28
  import { CardContent } from './CardContent';
32
29
 
33
30
  type Props = {
@@ -58,7 +55,7 @@ type Props = {
58
55
  overlayEnabled: boolean;
59
56
  shadowEnabled: boolean | undefined;
60
57
  gestureEnabled: boolean;
61
- gestureResponseDistance?: number;
58
+ gestureResponseDistance?: number | undefined;
62
59
  gestureVelocityImpact: number | undefined;
63
60
  transitionSpec: {
64
61
  open: TransitionSpec;
@@ -66,8 +63,8 @@ type Props = {
66
63
  };
67
64
  preloaded: boolean;
68
65
  styleInterpolator: StackCardStyleInterpolator;
69
- containerStyle?: StyleProp<ViewStyle>;
70
- contentStyle?: StyleProp<ViewStyle>;
66
+ containerStyle?: StyleProp<ViewStyle> | undefined;
67
+ contentStyle?: StyleProp<ViewStyle> | undefined;
71
68
  };
72
69
 
73
70
  const GESTURE_VELOCITY_IMPACT = 0.3;
@@ -83,7 +80,11 @@ const hasOpacityStyle = (
83
80
  if (style) {
84
81
  const flattenedStyle = StyleSheet.flatten(style);
85
82
 
86
- return 'opacity' in flattenedStyle && flattenedStyle.opacity != null;
83
+ return (
84
+ flattenedStyle != null &&
85
+ 'opacity' in flattenedStyle &&
86
+ flattenedStyle.opacity != null
87
+ );
87
88
  }
88
89
 
89
90
  return false;
@@ -151,14 +152,14 @@ function Card({
151
152
  containerStyle: customContainerStyle,
152
153
  contentStyle,
153
154
  }: Props) {
154
- const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
155
-
156
155
  const didInitiallyAnimate = React.useRef(false);
157
156
  const lastToValueRef = React.useRef<number | undefined>(undefined);
158
157
 
159
158
  const animationHandleRef = React.useRef<number | undefined>(undefined);
160
159
  const pendingGestureCallbackRef =
161
160
  React.useRef<ReturnType<typeof setTimeout>>(undefined);
161
+ const pendingOnCloseCallbackRef =
162
+ React.useRef<ReturnType<typeof setTimeout>>(undefined);
162
163
 
163
164
  const [isClosing] = React.useState(() => new Animated.Value(FALSE));
164
165
 
@@ -218,10 +219,8 @@ function Card({
218
219
  }
219
220
 
220
221
  animationHandleRef.current = requestAnimationFrame(() => {
221
- if (didInitiallyAnimate.current) {
222
- // Make sure to re-open screen if it wasn't removed
223
- forceUpdate();
224
- }
222
+ // Make sure to re-open screen if it wasn't removed
223
+ maybeAnimate();
225
224
  });
226
225
  };
227
226
 
@@ -245,79 +244,6 @@ function Card({
245
244
  }
246
245
  );
247
246
 
248
- const onGestureStateChange = useLatestCallback(
249
- ({ nativeEvent }: PanGestureHandlerGestureEvent) => {
250
- switch (nativeEvent.state) {
251
- case GestureState.ACTIVE:
252
- isSwiping.setValue(TRUE);
253
- onGestureBegin?.();
254
- break;
255
- case GestureState.CANCELLED:
256
- case GestureState.FAILED: {
257
- isSwiping.setValue(FALSE);
258
-
259
- const velocity =
260
- gestureDirection === 'vertical' ||
261
- gestureDirection === 'vertical-inverted'
262
- ? nativeEvent.velocityY
263
- : nativeEvent.velocityX;
264
-
265
- animate({
266
- closing,
267
- velocity,
268
- });
269
-
270
- onGestureCanceled?.();
271
- break;
272
- }
273
- case GestureState.END: {
274
- isSwiping.setValue(FALSE);
275
-
276
- let distance;
277
- let translation;
278
- let velocity;
279
-
280
- if (
281
- gestureDirection === 'vertical' ||
282
- gestureDirection === 'vertical-inverted'
283
- ) {
284
- distance = layout.height;
285
- translation = nativeEvent.translationY;
286
- velocity = nativeEvent.velocityY;
287
- } else {
288
- distance = layout.width;
289
- translation = nativeEvent.translationX;
290
- velocity = nativeEvent.velocityX;
291
- }
292
-
293
- const shouldClose =
294
- (translation + velocity * gestureVelocityImpact) *
295
- getInvertedMultiplier(gestureDirection, direction === 'rtl') >
296
- distance / 2
297
- ? velocity !== 0 || translation !== 0
298
- : closing;
299
-
300
- animate({ closing: shouldClose, velocity });
301
-
302
- if (shouldClose) {
303
- // We call onClose with a delay to make sure that the animation has already started
304
- // This will make sure that the state update caused by this doesn't affect start of animation
305
- pendingGestureCallbackRef.current = setTimeout(() => {
306
- onClose();
307
-
308
- // Trigger an update after we dispatch the action to remove the screen
309
- // This will make sure that we check if the screen didn't get removed so we can cancel the animation
310
- forceUpdate();
311
- }, 32);
312
- }
313
-
314
- onGestureEnd?.();
315
- break;
316
- }
317
- }
318
- }
319
- );
320
-
321
247
  React.useLayoutEffect(() => {
322
248
  layoutAnim.width.setValue(layout.width);
323
249
  layoutAnim.height.setValue(layout.height);
@@ -350,17 +276,15 @@ function Card({
350
276
  }
351
277
 
352
278
  clearTimeout(pendingGestureCallbackRef.current);
279
+ clearTimeout(pendingOnCloseCallbackRef.current);
353
280
  };
354
-
355
- // We only want to clean up the animation on unmount
356
281
  }, []);
357
282
 
358
- const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
283
+ const timeoutRef = React.useRef<ReturnType<typeof setTimeout>>(undefined);
359
284
 
360
- React.useEffect(() => {
361
- if (preloaded) {
362
- return;
363
- }
285
+ const maybeAnimate = useLatestCallback(() => {
286
+ clearTimeout(pendingGestureCallbackRef.current);
287
+ clearTimeout(pendingOnCloseCallbackRef.current);
364
288
 
365
289
  if (!didInitiallyAnimate.current) {
366
290
  // Animate the card in on initial mount
@@ -368,9 +292,8 @@ function Card({
368
292
  // rending of the screen is done. This is especially important
369
293
  // in the new architecture
370
294
  // cf., https://github.com/react-navigation/react-navigation/issues/12401
371
- if (timeoutRef.current) {
372
- clearTimeout(timeoutRef.current);
373
- }
295
+ clearTimeout(timeoutRef.current);
296
+
374
297
  timeoutRef.current = setTimeout(() => {
375
298
  didInitiallyAnimate.current = true;
376
299
  animate({ closing });
@@ -410,6 +333,14 @@ function Card({
410
333
  animate({ closing });
411
334
  }
412
335
  }
336
+ });
337
+
338
+ React.useEffect(() => {
339
+ if (preloaded) {
340
+ return;
341
+ }
342
+
343
+ maybeAnimate();
413
344
 
414
345
  previousPropsRef.current = {
415
346
  opening,
@@ -428,6 +359,7 @@ function Card({
428
359
  layout,
429
360
  opening,
430
361
  preloaded,
362
+ maybeAnimate,
431
363
  ]);
432
364
 
433
365
  const interpolationProps = React.useMemo(
@@ -463,23 +395,95 @@ function Card({
463
395
  ]
464
396
  );
465
397
 
466
- const { containerStyle, cardStyle, overlayStyle, shadowStyle } =
398
+ const { containerStyle, cardStyle, overlayStyle, dimStyle, shadowStyle } =
467
399
  React.useMemo(
468
400
  () => styleInterpolator(interpolationProps),
469
401
  [styleInterpolator, interpolationProps]
470
402
  );
471
403
 
472
- const onGestureEvent = React.useMemo(
404
+ const onGestureActivate: PanGestureConfig['onActivate'] = useLatestCallback(
405
+ () => {
406
+ clearTimeout(pendingGestureCallbackRef.current);
407
+ clearTimeout(pendingOnCloseCallbackRef.current);
408
+ isSwiping.setValue(TRUE);
409
+ onGestureBegin?.();
410
+ }
411
+ );
412
+
413
+ const onGestureDeactivate: PanGestureConfig['onDeactivate'] =
414
+ useLatestCallback((event) => {
415
+ isSwiping.setValue(FALSE);
416
+
417
+ let distance;
418
+ let translation;
419
+ let velocity;
420
+
421
+ if (
422
+ gestureDirection === 'vertical' ||
423
+ gestureDirection === 'vertical-inverted'
424
+ ) {
425
+ distance = layout.height;
426
+ translation = event.translationY;
427
+ velocity = event.velocityY;
428
+ } else {
429
+ distance = layout.width;
430
+ translation = event.translationX;
431
+ velocity = event.velocityX;
432
+ }
433
+
434
+ gesture.setValue(translation);
435
+
436
+ if (event.canceled) {
437
+ animate({
438
+ closing,
439
+ velocity,
440
+ });
441
+
442
+ onGestureCanceled?.();
443
+
444
+ return;
445
+ }
446
+
447
+ const shouldClose =
448
+ (translation + velocity * gestureVelocityImpact) *
449
+ getInvertedMultiplier(gestureDirection, direction === 'rtl') >
450
+ distance / 2
451
+ ? velocity !== 0 || translation !== 0
452
+ : closing;
453
+
454
+ animate({ closing: shouldClose, velocity });
455
+
456
+ if (shouldClose) {
457
+ // We call onClose with a delay to make sure that the animation has already started
458
+ // This will make sure that the state update caused by this doesn't affect start of animation
459
+ pendingGestureCallbackRef.current = setTimeout(() => {
460
+ onClose();
461
+
462
+ // Check if the screen is still closing with a delay
463
+ // So state update from onClose has a chance to go through
464
+ // If route wasn't removed after onClose, re-open it
465
+ pendingOnCloseCallbackRef.current = setTimeout(() => {
466
+ maybeAnimate();
467
+ }, 32);
468
+ }, 16);
469
+ }
470
+
471
+ onGestureEnd?.();
472
+ });
473
+
474
+ const onGestureEvent: PanGestureConfig['onUpdate'] = React.useMemo(
473
475
  () =>
474
476
  gestureEnabled
475
477
  ? Animated.event(
476
478
  [
477
479
  {
478
- nativeEvent:
479
- gestureDirection === 'vertical' ||
480
- gestureDirection === 'vertical-inverted'
481
- ? { translationY: gesture }
482
- : { translationX: gesture },
480
+ nativeEvent: {
481
+ handlerData:
482
+ gestureDirection === 'vertical' ||
483
+ gestureDirection === 'vertical-inverted'
484
+ ? { translationY: gesture }
485
+ : { translationX: gesture },
486
+ },
483
487
  },
484
488
  ],
485
489
  { useNativeDriver }
@@ -488,7 +492,36 @@ function Card({
488
492
  [gesture, gestureDirection, gestureEnabled]
489
493
  );
490
494
 
491
- const { backgroundColor } = StyleSheet.flatten(contentStyle || {});
495
+ const panGestureConfig = React.useMemo(
496
+ (): PanGestureConfig => ({
497
+ enabled: layout.width !== 0 && gestureEnabled,
498
+ onActivate: onGestureActivate,
499
+ onDeactivate: onGestureDeactivate,
500
+ onUpdate: onGestureEvent,
501
+ disableReanimated: true,
502
+ useAnimated: true,
503
+ ...gestureActivationCriteria({
504
+ layout,
505
+ direction,
506
+ gestureDirection,
507
+ gestureResponseDistance,
508
+ }),
509
+ }),
510
+ [
511
+ direction,
512
+ gestureDirection,
513
+ gestureEnabled,
514
+ gestureResponseDistance,
515
+ layout,
516
+ onGestureActivate,
517
+ onGestureDeactivate,
518
+ onGestureEvent,
519
+ ]
520
+ );
521
+
522
+ const panGesture = usePanGesture(panGestureConfig);
523
+
524
+ const backgroundColor = StyleSheet.flatten(contentStyle)?.backgroundColor;
492
525
 
493
526
  const isTransparent =
494
527
  typeof backgroundColor === 'string'
@@ -519,20 +552,10 @@ function Card({
519
552
  <Animated.View
520
553
  style={[styles.container, containerStyle, customContainerStyle]}
521
554
  >
522
- <PanGestureHandler
523
- enabled={layout.width !== 0 && gestureEnabled}
524
- onGestureEvent={onGestureEvent}
525
- onHandlerStateChange={onGestureStateChange}
526
- {...gestureActivationCriteria({
527
- layout,
528
- direction,
529
- gestureDirection,
530
- gestureResponseDistance,
531
- })}
532
- >
555
+ <GestureDetector gesture={panGesture}>
533
556
  <Animated.View
534
557
  needsOffscreenAlphaCompositing={hasOpacityStyle(cardStyle)}
535
- style={[styles.container, cardStyle]}
558
+ style={[styles.card, cardStyle]}
536
559
  >
537
560
  {shadowEnabled && shadowStyle && !isTransparent ? (
538
561
  <Animated.View
@@ -557,8 +580,9 @@ function Card({
557
580
  >
558
581
  {children}
559
582
  </CardContent>
583
+ {dimStyle ? <Animated.View style={[styles.dim, dimStyle]} /> : null}
560
584
  </Animated.View>
561
- </PanGestureHandler>
585
+ </GestureDetector>
562
586
  </Animated.View>
563
587
  </CardAnimationContext.Provider>
564
588
  );
@@ -571,11 +595,22 @@ const styles = StyleSheet.create({
571
595
  flex: 1,
572
596
  pointerEvents: 'box-none',
573
597
  },
598
+ card: {
599
+ flex: 1,
600
+ // This is necessary for gestures to work
601
+ // Without this, gestures won't work if the child view is flattened
602
+ pointerEvents: 'auto',
603
+ },
574
604
  overlay: {
575
605
  flex: 1,
576
606
  backgroundColor: '#000',
577
607
  pointerEvents: 'none',
578
608
  },
609
+ dim: {
610
+ ...StyleSheet.absoluteFill,
611
+ backgroundColor: '#000',
612
+ pointerEvents: 'none',
613
+ },
579
614
  shadow: {
580
615
  position: 'absolute',
581
616
  pointerEvents: 'none',