@react-navigation/stack 8.0.0-alpha.6 → 8.0.0-alpha.60

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 (147) hide show
  1. package/lib/module/TransitionConfigs/CardStyleInterpolators.js +280 -84
  2. package/lib/module/TransitionConfigs/CardStyleInterpolators.js.map +1 -1
  3. package/lib/module/TransitionConfigs/HeaderStyleInterpolators.js +11 -44
  4. package/lib/module/TransitionConfigs/HeaderStyleInterpolators.js.map +1 -1
  5. package/lib/module/TransitionConfigs/TransitionEasings.js +71 -0
  6. package/lib/module/TransitionConfigs/TransitionEasings.js.map +1 -0
  7. package/lib/module/TransitionConfigs/TransitionPresets.js +50 -10
  8. package/lib/module/TransitionConfigs/TransitionPresets.js.map +1 -1
  9. package/lib/module/TransitionConfigs/TransitionSpecs.js +69 -34
  10. package/lib/module/TransitionConfigs/TransitionSpecs.js.map +1 -1
  11. package/lib/module/index.js +2 -2
  12. package/lib/module/index.js.map +1 -1
  13. package/lib/module/navigators/createStackNavigator.js +37 -33
  14. package/lib/module/navigators/createStackNavigator.js.map +1 -1
  15. package/lib/module/utils/GestureHandlerContext.js +5 -0
  16. package/lib/module/utils/GestureHandlerContext.js.map +1 -0
  17. package/lib/module/utils/gestureActivationCriteria.js +10 -8
  18. package/lib/module/utils/gestureActivationCriteria.js.map +1 -1
  19. package/lib/module/utils/useCardAnimation.js +1 -1
  20. package/lib/module/utils/useCardAnimation.js.map +1 -1
  21. package/lib/module/utils/useGestureHandler.js +9 -0
  22. package/lib/module/utils/useGestureHandler.js.map +1 -0
  23. package/lib/module/utils/useKeyboardManager.js +71 -26
  24. package/lib/module/utils/useKeyboardManager.js.map +1 -1
  25. package/lib/module/views/GestureHandler.js +15 -14
  26. package/lib/module/views/GestureHandler.js.map +1 -1
  27. package/lib/module/views/GestureHandlerNative.js +16 -10
  28. package/lib/module/views/GestureHandlerNative.js.map +1 -1
  29. package/lib/module/views/Header/Header.js +4 -2
  30. package/lib/module/views/Header/Header.js.map +1 -1
  31. package/lib/module/views/Header/HeaderContainer.js +60 -21
  32. package/lib/module/views/Header/HeaderContainer.js.map +1 -1
  33. package/lib/module/views/Header/HeaderSegment.js +6 -9
  34. package/lib/module/views/Header/HeaderSegment.js.map +1 -1
  35. package/lib/module/views/Stack/Card.js +170 -123
  36. package/lib/module/views/Stack/Card.js.map +1 -1
  37. package/lib/module/views/Stack/CardContainer.js +96 -98
  38. package/lib/module/views/Stack/CardContainer.js.map +1 -1
  39. package/lib/module/views/Stack/CardContent.js +10 -37
  40. package/lib/module/views/Stack/CardContent.js.map +1 -1
  41. package/lib/module/views/Stack/CardStack.js +211 -147
  42. package/lib/module/views/Stack/CardStack.js.map +1 -1
  43. package/lib/module/views/Stack/StackView.js +106 -57
  44. package/lib/module/views/Stack/StackView.js.map +1 -1
  45. package/lib/typescript/src/TransitionConfigs/CardStyleInterpolators.d.ts +20 -12
  46. package/lib/typescript/src/TransitionConfigs/CardStyleInterpolators.d.ts.map +1 -1
  47. package/lib/typescript/src/TransitionConfigs/HeaderStyleInterpolators.d.ts +4 -4
  48. package/lib/typescript/src/TransitionConfigs/HeaderStyleInterpolators.d.ts.map +1 -1
  49. package/lib/typescript/src/TransitionConfigs/TransitionEasings.d.ts +38 -0
  50. package/lib/typescript/src/TransitionConfigs/TransitionEasings.d.ts.map +1 -0
  51. package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts +15 -3
  52. package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts.map +1 -1
  53. package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts +44 -15
  54. package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts.map +1 -1
  55. package/lib/typescript/src/index.d.ts +12 -12
  56. package/lib/typescript/src/index.d.ts.map +1 -1
  57. package/lib/typescript/src/navigators/createStackNavigator.d.ts +10 -15
  58. package/lib/typescript/src/navigators/createStackNavigator.d.ts.map +1 -1
  59. package/lib/typescript/src/types.d.ts +120 -108
  60. package/lib/typescript/src/types.d.ts.map +1 -1
  61. package/lib/typescript/src/utils/CardAnimationContext.d.ts +1 -1
  62. package/lib/typescript/src/utils/CardAnimationContext.d.ts.map +1 -1
  63. package/lib/typescript/src/utils/GestureHandlerContext.d.ts +3 -0
  64. package/lib/typescript/src/utils/GestureHandlerContext.d.ts.map +1 -0
  65. package/lib/typescript/src/utils/gestureActivationCriteria.d.ts +4 -51
  66. package/lib/typescript/src/utils/gestureActivationCriteria.d.ts.map +1 -1
  67. package/lib/typescript/src/utils/getDistanceForDirection.d.ts +1 -1
  68. package/lib/typescript/src/utils/getDistanceForDirection.d.ts.map +1 -1
  69. package/lib/typescript/src/utils/getInvertedMultiplier.d.ts +1 -1
  70. package/lib/typescript/src/utils/getInvertedMultiplier.d.ts.map +1 -1
  71. package/lib/typescript/src/utils/getModalRoutesKeys.d.ts +1 -1
  72. package/lib/typescript/src/utils/getModalRoutesKeys.d.ts.map +1 -1
  73. package/lib/typescript/src/utils/getShadowStyle.d.ts +5 -5
  74. package/lib/typescript/src/utils/useCardAnimation.d.ts +1 -1
  75. package/lib/typescript/src/utils/useCardAnimation.d.ts.map +1 -1
  76. package/lib/typescript/src/utils/useGestureHandler.d.ts +2 -0
  77. package/lib/typescript/src/utils/useGestureHandler.d.ts.map +1 -0
  78. package/lib/typescript/src/utils/useKeyboardManager.d.ts +12 -2
  79. package/lib/typescript/src/utils/useKeyboardManager.d.ts.map +1 -1
  80. package/lib/typescript/src/views/GestureHandler.android.d.ts +1 -1
  81. package/lib/typescript/src/views/GestureHandler.android.d.ts.map +1 -1
  82. package/lib/typescript/src/views/GestureHandler.d.ts +7 -11
  83. package/lib/typescript/src/views/GestureHandler.d.ts.map +1 -1
  84. package/lib/typescript/src/views/GestureHandler.ios.d.ts +1 -1
  85. package/lib/typescript/src/views/GestureHandler.ios.d.ts.map +1 -1
  86. package/lib/typescript/src/views/GestureHandlerNative.d.ts +9 -4
  87. package/lib/typescript/src/views/GestureHandlerNative.d.ts.map +1 -1
  88. package/lib/typescript/src/views/Header/Header.d.ts +2 -2
  89. package/lib/typescript/src/views/Header/Header.d.ts.map +1 -1
  90. package/lib/typescript/src/views/Header/HeaderContainer.d.ts +9 -3
  91. package/lib/typescript/src/views/Header/HeaderContainer.d.ts.map +1 -1
  92. package/lib/typescript/src/views/Header/HeaderSegment.d.ts +6 -4
  93. package/lib/typescript/src/views/Header/HeaderSegment.d.ts.map +1 -1
  94. package/lib/typescript/src/views/Stack/Card.d.ts +14 -8
  95. package/lib/typescript/src/views/Stack/Card.d.ts.map +1 -1
  96. package/lib/typescript/src/views/Stack/CardContainer.d.ts +7 -7
  97. package/lib/typescript/src/views/Stack/CardContainer.d.ts.map +1 -1
  98. package/lib/typescript/src/views/Stack/CardContent.d.ts +1 -1
  99. package/lib/typescript/src/views/Stack/CardContent.d.ts.map +1 -1
  100. package/lib/typescript/src/views/Stack/CardStack.d.ts +10 -6
  101. package/lib/typescript/src/views/Stack/CardStack.d.ts.map +1 -1
  102. package/lib/typescript/src/views/Stack/StackView.d.ts +14 -17
  103. package/lib/typescript/src/views/Stack/StackView.d.ts.map +1 -1
  104. package/package.json +23 -24
  105. package/src/TransitionConfigs/CardStyleInterpolators.tsx +399 -100
  106. package/src/TransitionConfigs/HeaderStyleInterpolators.tsx +15 -46
  107. package/src/TransitionConfigs/TransitionEasings.tsx +90 -0
  108. package/src/TransitionConfigs/TransitionPresets.tsx +56 -10
  109. package/src/TransitionConfigs/TransitionSpecs.tsx +68 -34
  110. package/src/index.tsx +3 -2
  111. package/src/navigators/createStackNavigator.tsx +74 -91
  112. package/src/types.tsx +149 -123
  113. package/src/utils/GestureHandlerContext.tsx +5 -0
  114. package/src/utils/gestureActivationCriteria.tsx +14 -10
  115. package/src/utils/useCardAnimation.tsx +1 -1
  116. package/src/utils/useGestureHandler.tsx +9 -0
  117. package/src/utils/useKeyboardManager.tsx +96 -33
  118. package/src/views/GestureHandler.tsx +24 -16
  119. package/src/views/GestureHandlerNative.tsx +24 -13
  120. package/src/views/Header/Header.tsx +4 -2
  121. package/src/views/Header/HeaderContainer.tsx +79 -37
  122. package/src/views/Header/HeaderSegment.tsx +10 -11
  123. package/src/views/Stack/Card.tsx +241 -154
  124. package/src/views/Stack/CardContainer.tsx +111 -127
  125. package/src/views/Stack/CardContent.tsx +13 -51
  126. package/src/views/Stack/CardStack.tsx +478 -335
  127. package/src/views/Stack/StackView.tsx +181 -82
  128. package/lib/module/utils/GestureHandlerRefContext.js +0 -5
  129. package/lib/module/utils/GestureHandlerRefContext.js.map +0 -1
  130. package/lib/module/utils/findLastIndex.js +0 -11
  131. package/lib/module/utils/findLastIndex.js.map +0 -1
  132. package/lib/module/utils/useGestureHandlerRef.js +0 -12
  133. package/lib/module/utils/useGestureHandlerRef.js.map +0 -1
  134. package/lib/module/views/Stack/CardA11yWrapper.js +0 -43
  135. package/lib/module/views/Stack/CardA11yWrapper.js.map +0 -1
  136. package/lib/typescript/src/utils/GestureHandlerRefContext.d.ts +0 -3
  137. package/lib/typescript/src/utils/GestureHandlerRefContext.d.ts.map +0 -1
  138. package/lib/typescript/src/utils/findLastIndex.d.ts +0 -2
  139. package/lib/typescript/src/utils/findLastIndex.d.ts.map +0 -1
  140. package/lib/typescript/src/utils/useGestureHandlerRef.d.ts +0 -3
  141. package/lib/typescript/src/utils/useGestureHandlerRef.d.ts.map +0 -1
  142. package/lib/typescript/src/views/Stack/CardA11yWrapper.d.ts +0 -15
  143. package/lib/typescript/src/views/Stack/CardA11yWrapper.d.ts.map +0 -1
  144. package/src/utils/GestureHandlerRefContext.tsx +0 -5
  145. package/src/utils/findLastIndex.tsx +0 -9
  146. package/src/utils/useGestureHandlerRef.tsx +0 -15
  147. package/src/views/Stack/CardA11yWrapper.tsx +0 -67
@@ -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 = {
@@ -35,8 +32,16 @@ type Props = {
35
32
  interpolationIndex: number;
36
33
  opening: boolean;
37
34
  closing: boolean;
38
- next: Animated.AnimatedInterpolation<number> | undefined;
39
- current: Animated.AnimatedInterpolation<number>;
35
+ current: {
36
+ progress: Animated.AnimatedInterpolation<number>;
37
+ closing: Animated.Value;
38
+ };
39
+ next:
40
+ | {
41
+ progress: Animated.AnimatedInterpolation<number>;
42
+ closing: Animated.Value;
43
+ }
44
+ | undefined;
40
45
  gesture: Animated.Value;
41
46
  layout: Layout;
42
47
  insets: EdgeInsets;
@@ -55,10 +60,10 @@ type Props = {
55
60
  style: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
56
61
  }) => React.ReactNode)
57
62
  | undefined;
58
- overlayEnabled: boolean;
63
+ overlayEnabled: boolean | undefined;
59
64
  shadowEnabled: boolean | undefined;
60
65
  gestureEnabled: boolean;
61
- gestureResponseDistance?: number;
66
+ gestureResponseDistance?: number | undefined;
62
67
  gestureVelocityImpact: number | undefined;
63
68
  transitionSpec: {
64
69
  open: TransitionSpec;
@@ -66,8 +71,8 @@ type Props = {
66
71
  };
67
72
  preloaded: boolean;
68
73
  styleInterpolator: StackCardStyleInterpolator;
69
- containerStyle?: StyleProp<ViewStyle>;
70
- contentStyle?: StyleProp<ViewStyle>;
74
+ containerStyle?: StyleProp<ViewStyle> | undefined;
75
+ contentStyle?: StyleProp<ViewStyle> | undefined;
71
76
  };
72
77
 
73
78
  const GESTURE_VELOCITY_IMPACT = 0.3;
@@ -83,7 +88,11 @@ const hasOpacityStyle = (
83
88
  if (style) {
84
89
  const flattenedStyle = StyleSheet.flatten(style);
85
90
 
86
- return 'opacity' in flattenedStyle && flattenedStyle.opacity != null;
91
+ return (
92
+ flattenedStyle != null &&
93
+ 'opacity' in flattenedStyle &&
94
+ flattenedStyle.opacity != null
95
+ );
87
96
  }
88
97
 
89
98
  return false;
@@ -120,7 +129,7 @@ const defaultOverlay = ({
120
129
  }) => (style ? <Animated.View style={[styles.overlay, style]} /> : null);
121
130
 
122
131
  function Card({
123
- shadowEnabled = false,
132
+ shadowEnabled,
124
133
  gestureEnabled = true,
125
134
  gestureVelocityImpact = GESTURE_VELOCITY_IMPACT,
126
135
  overlay = defaultOverlay,
@@ -151,16 +160,18 @@ function Card({
151
160
  containerStyle: customContainerStyle,
152
161
  contentStyle,
153
162
  }: Props) {
154
- const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
155
-
156
163
  const didInitiallyAnimate = React.useRef(false);
164
+ const isClosingValueLockedRef = React.useRef(false);
157
165
  const lastToValueRef = React.useRef<number | undefined>(undefined);
166
+ const isAnimatingRef = React.useRef(false);
167
+ const animationIdRef = React.useRef(0);
158
168
 
159
169
  const animationHandleRef = React.useRef<number | undefined>(undefined);
160
170
  const pendingGestureCallbackRef =
161
171
  React.useRef<ReturnType<typeof setTimeout>>(undefined);
162
-
163
- const [isClosing] = React.useState(() => new Animated.Value(FALSE));
172
+ const pendingOnCloseCallbackRef =
173
+ React.useRef<ReturnType<typeof setTimeout>>(undefined);
174
+ const timeoutRef = React.useRef<ReturnType<typeof setTimeout>>(undefined);
164
175
 
165
176
  const [inverted] = React.useState(
166
177
  () =>
@@ -184,6 +195,8 @@ function Card({
184
195
  closing: boolean;
185
196
  velocity?: number;
186
197
  }) => {
198
+ const id = ++animationIdRef.current;
199
+
187
200
  const toValue = getAnimateToValue({
188
201
  closing: isClosingParam,
189
202
  layout,
@@ -193,7 +206,13 @@ function Card({
193
206
  });
194
207
 
195
208
  lastToValueRef.current = toValue;
196
- isClosing.setValue(isClosingParam ? TRUE : FALSE);
209
+
210
+ // Switching between open and close mid-gesture and animation can cause a visible jump.
211
+ // So we lock the closing value until the animation is finished.
212
+ if (!isClosingValueLockedRef.current) {
213
+ isClosingValueLockedRef.current = true;
214
+ current.closing.setValue(isClosingParam ? TRUE : FALSE);
215
+ }
197
216
 
198
217
  const spec = isClosingParam ? transitionSpec.close : transitionSpec.open;
199
218
  const animation =
@@ -211,6 +230,9 @@ function Card({
211
230
  });
212
231
 
213
232
  const onFinish = () => {
233
+ current.closing.setValue(isClosingParam ? TRUE : FALSE);
234
+ isClosingValueLockedRef.current = false;
235
+
214
236
  if (isClosingParam) {
215
237
  onClose();
216
238
  } else {
@@ -218,14 +240,14 @@ function Card({
218
240
  }
219
241
 
220
242
  animationHandleRef.current = requestAnimationFrame(() => {
221
- if (didInitiallyAnimate.current) {
222
- // Make sure to re-open screen if it wasn't removed
223
- forceUpdate();
224
- }
243
+ // Make sure to re-open screen if it wasn't removed
244
+ maybeAnimate();
225
245
  });
226
246
  };
227
247
 
228
248
  if (animated) {
249
+ isAnimatingRef.current = true;
250
+
229
251
  animation(gesture, {
230
252
  ...spec.config,
231
253
  velocity,
@@ -233,6 +255,12 @@ function Card({
233
255
  useNativeDriver,
234
256
  isInteraction: false,
235
257
  }).start(({ finished }) => {
258
+ if (id !== animationIdRef.current) {
259
+ return;
260
+ }
261
+
262
+ isAnimatingRef.current = false;
263
+
236
264
  clearTimeout(pendingGestureCallbackRef.current);
237
265
 
238
266
  if (finished) {
@@ -240,84 +268,13 @@ function Card({
240
268
  }
241
269
  });
242
270
  } else {
271
+ isAnimatingRef.current = false;
272
+ gesture.setValue(toValue);
243
273
  onFinish();
244
274
  }
245
275
  }
246
276
  );
247
277
 
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
278
  React.useLayoutEffect(() => {
322
279
  layoutAnim.width.setValue(layout.width);
323
280
  layoutAnim.height.setValue(layout.height);
@@ -344,23 +301,34 @@ function Card({
344
301
  } | null>(null);
345
302
 
346
303
  React.useEffect(() => {
304
+ const animationIdRefForCleanup = animationIdRef;
305
+
347
306
  return () => {
307
+ // Increment the animation ID when the card unmounts.
308
+ // This makes pending callbacks return because their IDs no longer match.
309
+ animationIdRefForCleanup.current++;
310
+ gesture.stopAnimation();
311
+
312
+ // In development, StrictMode re-runs effects without unmounting,
313
+ // e.g. when the card moves to a new position after rearranging.
314
+ // The stopped animation didn't reach its value, so reset the state,
315
+ // to make sure that `maybeAnimate` restarts it when effects re-run.
316
+ isAnimatingRef.current = false;
317
+ lastToValueRef.current = undefined;
318
+
348
319
  if (animationHandleRef.current) {
349
320
  cancelAnimationFrame(animationHandleRef.current);
350
321
  }
351
322
 
352
323
  clearTimeout(pendingGestureCallbackRef.current);
324
+ clearTimeout(pendingOnCloseCallbackRef.current);
325
+ clearTimeout(timeoutRef.current);
353
326
  };
327
+ }, [gesture]);
354
328
 
355
- // We only want to clean up the animation on unmount
356
- }, []);
357
-
358
- const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
359
-
360
- React.useEffect(() => {
361
- if (preloaded) {
362
- return;
363
- }
329
+ const maybeAnimate = useLatestCallback(() => {
330
+ clearTimeout(pendingGestureCallbackRef.current);
331
+ clearTimeout(pendingOnCloseCallbackRef.current);
364
332
 
365
333
  if (!didInitiallyAnimate.current) {
366
334
  // Animate the card in on initial mount
@@ -368,9 +336,8 @@ function Card({
368
336
  // rending of the screen is done. This is especially important
369
337
  // in the new architecture
370
338
  // cf., https://github.com/react-navigation/react-navigation/issues/12401
371
- if (timeoutRef.current) {
372
- clearTimeout(timeoutRef.current);
373
- }
339
+ clearTimeout(timeoutRef.current);
340
+
374
341
  timeoutRef.current = setTimeout(() => {
375
342
  didInitiallyAnimate.current = true;
376
343
  animate({ closing });
@@ -395,7 +362,10 @@ function Card({
395
362
  // When route was closed due to a gesture, the animation would've happened already
396
363
  // It's still important to trigger the animation so that `onClose` is called
397
364
  // If `onClose` is not called, cleanup step won't be performed for gestures
398
- animate({ closing });
365
+ // We also check if animation was already running so we don't restart it unnecessarily
366
+ if (!isAnimatingRef.current || lastToValueRef.current !== toValue) {
367
+ animate({ closing });
368
+ }
399
369
  } else if (
400
370
  typeof previousOpening === 'boolean' &&
401
371
  opening &&
@@ -410,6 +380,14 @@ function Card({
410
380
  animate({ closing });
411
381
  }
412
382
  }
383
+ });
384
+
385
+ React.useEffect(() => {
386
+ if (preloaded) {
387
+ return;
388
+ }
389
+
390
+ maybeAnimate();
413
391
 
414
392
  previousPropsRef.current = {
415
393
  opening,
@@ -428,14 +406,14 @@ function Card({
428
406
  layout,
429
407
  opening,
430
408
  preloaded,
409
+ maybeAnimate,
431
410
  ]);
432
411
 
433
412
  const interpolationProps = React.useMemo(
434
413
  () => ({
435
414
  index: interpolationIndex,
436
- current: { progress: current },
437
- next: next && { progress: next },
438
- closing: isClosing,
415
+ current,
416
+ next,
439
417
  swiping: isSwiping,
440
418
  inverted,
441
419
  layouts: {
@@ -452,7 +430,6 @@ function Card({
452
430
  interpolationIndex,
453
431
  current,
454
432
  next,
455
- isClosing,
456
433
  isSwiping,
457
434
  inverted,
458
435
  layout,
@@ -463,23 +440,102 @@ function Card({
463
440
  ]
464
441
  );
465
442
 
466
- const { containerStyle, cardStyle, overlayStyle, shadowStyle } =
443
+ const { containerStyle, cardStyle, overlayStyle, dimStyle, shadowStyle } =
467
444
  React.useMemo(
468
445
  () => styleInterpolator(interpolationProps),
469
446
  [styleInterpolator, interpolationProps]
470
447
  );
471
448
 
472
- const onGestureEvent = React.useMemo(
449
+ const onGestureActivate: PanGestureConfig['onActivate'] = useLatestCallback(
450
+ () => {
451
+ clearTimeout(pendingGestureCallbackRef.current);
452
+ clearTimeout(pendingOnCloseCallbackRef.current);
453
+
454
+ isSwiping.setValue(TRUE);
455
+
456
+ if (!isClosingValueLockedRef.current) {
457
+ isClosingValueLockedRef.current = true;
458
+ current.closing.setValue(TRUE);
459
+ }
460
+
461
+ onGestureBegin?.();
462
+ }
463
+ );
464
+
465
+ const onGestureDeactivate: PanGestureConfig['onDeactivate'] =
466
+ useLatestCallback((event) => {
467
+ isSwiping.setValue(FALSE);
468
+
469
+ let distance;
470
+ let translation;
471
+ let velocity;
472
+
473
+ if (
474
+ gestureDirection === 'vertical' ||
475
+ gestureDirection === 'vertical-inverted'
476
+ ) {
477
+ distance = layout.height;
478
+ translation = event.translationY;
479
+ velocity = event.velocityY;
480
+ } else {
481
+ distance = layout.width;
482
+ translation = event.translationX;
483
+ velocity = event.velocityX;
484
+ }
485
+
486
+ gesture.setValue(translation);
487
+
488
+ if (event.canceled) {
489
+ animate({
490
+ closing,
491
+ velocity,
492
+ });
493
+
494
+ onGestureCanceled?.();
495
+
496
+ return;
497
+ }
498
+
499
+ const shouldClose =
500
+ (translation + velocity * gestureVelocityImpact) *
501
+ getInvertedMultiplier(gestureDirection, direction === 'rtl') >
502
+ distance / 2
503
+ ? velocity !== 0 || translation !== 0
504
+ : closing;
505
+
506
+ animate({ closing: shouldClose, velocity });
507
+
508
+ if (shouldClose) {
509
+ // We call onClose with a delay to make sure that the animation has already started
510
+ // This will make sure that the state update caused by this doesn't affect start of animation
511
+ pendingGestureCallbackRef.current = setTimeout(() => {
512
+ onClose();
513
+
514
+ // Check if the screen is still closing with a delay
515
+ // So state update from onClose has a chance to go through
516
+ // If route wasn't removed after onClose, re-open it
517
+ pendingOnCloseCallbackRef.current = setTimeout(() => {
518
+ maybeAnimate();
519
+ }, 32);
520
+ }, 16);
521
+ }
522
+
523
+ onGestureEnd?.();
524
+ });
525
+
526
+ const onGestureEvent: PanGestureConfig['onUpdate'] = React.useMemo(
473
527
  () =>
474
528
  gestureEnabled
475
529
  ? Animated.event(
476
530
  [
477
531
  {
478
- nativeEvent:
479
- gestureDirection === 'vertical' ||
480
- gestureDirection === 'vertical-inverted'
481
- ? { translationY: gesture }
482
- : { translationX: gesture },
532
+ nativeEvent: {
533
+ handlerData:
534
+ gestureDirection === 'vertical' ||
535
+ gestureDirection === 'vertical-inverted'
536
+ ? { translationY: gesture }
537
+ : { translationX: gesture },
538
+ },
483
539
  },
484
540
  ],
485
541
  { useNativeDriver }
@@ -488,7 +544,36 @@ function Card({
488
544
  [gesture, gestureDirection, gestureEnabled]
489
545
  );
490
546
 
491
- const { backgroundColor } = StyleSheet.flatten(contentStyle || {});
547
+ const panGestureConfig = React.useMemo(
548
+ (): PanGestureConfig => ({
549
+ enabled: layout.width !== 0 && gestureEnabled,
550
+ onActivate: onGestureActivate,
551
+ onDeactivate: onGestureDeactivate,
552
+ onUpdate: onGestureEvent,
553
+ disableReanimated: true,
554
+ useAnimated: true,
555
+ ...gestureActivationCriteria({
556
+ layout,
557
+ direction,
558
+ gestureDirection,
559
+ gestureResponseDistance,
560
+ }),
561
+ }),
562
+ [
563
+ direction,
564
+ gestureDirection,
565
+ gestureEnabled,
566
+ gestureResponseDistance,
567
+ layout,
568
+ onGestureActivate,
569
+ onGestureDeactivate,
570
+ onGestureEvent,
571
+ ]
572
+ );
573
+
574
+ const panGesture = usePanGesture(panGestureConfig);
575
+
576
+ const backgroundColor = StyleSheet.flatten(contentStyle)?.backgroundColor;
492
577
 
493
578
  const isTransparent =
494
579
  typeof backgroundColor === 'string'
@@ -505,13 +590,13 @@ function Card({
505
590
  // Animated needs the animated value to be used somewhere, otherwise things don't update properly.
506
591
  // If we disable animations and hide header, it could end up making the value unused.
507
592
  // So we have this dummy style that will always be used regardless of what else changed.
508
- opacity: current,
593
+ opacity: current.progress,
509
594
  }}
510
595
  // Make sure that this view isn't removed. If this view is removed, our style with animated value won't apply
511
596
  collapsable={false}
512
597
  />
513
598
  ) : null}
514
- {overlayEnabled ? (
599
+ {(overlayEnabled ?? overlayStyle != null) ? (
515
600
  <View style={[StyleSheet.absoluteFill, { pointerEvents: 'box-none' }]}>
516
601
  {overlay({ style: overlayStyle })}
517
602
  </View>
@@ -519,32 +604,21 @@ function Card({
519
604
  <Animated.View
520
605
  style={[styles.container, containerStyle, customContainerStyle]}
521
606
  >
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
- >
607
+ <GestureDetector gesture={panGesture}>
533
608
  <Animated.View
534
609
  needsOffscreenAlphaCompositing={hasOpacityStyle(cardStyle)}
535
- style={[styles.container, cardStyle]}
610
+ style={[styles.card, cardStyle]}
536
611
  >
537
- {shadowEnabled && shadowStyle && !isTransparent ? (
612
+ {(shadowEnabled ?? shadowStyle != null) && !isTransparent ? (
538
613
  <Animated.View
539
614
  style={[
540
615
  styles.shadow,
541
- gestureDirection === 'horizontal'
542
- ? [styles.shadowHorizontal, styles.shadowStart]
543
- : gestureDirection === 'horizontal-inverted'
544
- ? [styles.shadowHorizontal, styles.shadowEnd]
545
- : gestureDirection === 'vertical'
546
- ? [styles.shadowVertical, styles.shadowTop]
547
- : [styles.shadowVertical, styles.shadowBottom],
616
+ gestureDirection === 'horizontal' ||
617
+ gestureDirection === 'horizontal-inverted'
618
+ ? styles.shadowHorizontal
619
+ : gestureDirection === 'vertical'
620
+ ? [styles.shadowVertical, styles.shadowTop]
621
+ : [styles.shadowVertical, styles.shadowBottom],
548
622
  { backgroundColor },
549
623
  shadowStyle,
550
624
  ]}
@@ -557,8 +631,11 @@ function Card({
557
631
  >
558
632
  {children}
559
633
  </CardContent>
634
+ {dimStyle != null ? (
635
+ <Animated.View style={[styles.dim, dimStyle]} />
636
+ ) : null}
560
637
  </Animated.View>
561
- </PanGestureHandler>
638
+ </GestureDetector>
562
639
  </Animated.View>
563
640
  </CardAnimationContext.Provider>
564
641
  );
@@ -571,34 +648,44 @@ const styles = StyleSheet.create({
571
648
  flex: 1,
572
649
  pointerEvents: 'box-none',
573
650
  },
651
+ card: {
652
+ flex: 1,
653
+ // The flip animation adds `backfaceVisibility`
654
+ // On Android, it crashes when the style property is removed
655
+ // So we always add the property with the default
656
+ backfaceVisibility: 'visible',
657
+ // This is necessary for gestures to work
658
+ // Without this, gestures won't work if the child view is flattened
659
+ pointerEvents: 'auto',
660
+ },
574
661
  overlay: {
575
662
  flex: 1,
576
663
  backgroundColor: '#000',
577
664
  pointerEvents: 'none',
578
665
  },
666
+ dim: {
667
+ ...StyleSheet.absoluteFill,
668
+ backgroundColor: '#000',
669
+ pointerEvents: 'none',
670
+ },
579
671
  shadow: {
580
672
  position: 'absolute',
581
673
  pointerEvents: 'none',
582
674
  },
583
675
  shadowHorizontal: {
584
676
  top: 0,
677
+ start: 0,
678
+ end: 0,
585
679
  bottom: 0,
586
- width: 3,
587
680
  ...getShadowStyle({
588
681
  offset: {
589
- width: -1,
590
- height: 1,
682
+ width: 0,
683
+ height: -3,
591
684
  },
592
- radius: 5,
593
- opacity: 0.3,
685
+ radius: 12,
686
+ opacity: 0.04,
594
687
  }),
595
688
  },
596
- shadowStart: {
597
- start: 0,
598
- },
599
- shadowEnd: {
600
- end: 0,
601
- },
602
689
  shadowVertical: {
603
690
  start: 0,
604
691
  end: 0,