@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.
- package/lib/module/TransitionConfigs/CardStyleInterpolators.js +280 -84
- package/lib/module/TransitionConfigs/CardStyleInterpolators.js.map +1 -1
- package/lib/module/TransitionConfigs/HeaderStyleInterpolators.js +11 -44
- package/lib/module/TransitionConfigs/HeaderStyleInterpolators.js.map +1 -1
- package/lib/module/TransitionConfigs/TransitionEasings.js +71 -0
- package/lib/module/TransitionConfigs/TransitionEasings.js.map +1 -0
- package/lib/module/TransitionConfigs/TransitionPresets.js +50 -10
- package/lib/module/TransitionConfigs/TransitionPresets.js.map +1 -1
- package/lib/module/TransitionConfigs/TransitionSpecs.js +69 -34
- package/lib/module/TransitionConfigs/TransitionSpecs.js.map +1 -1
- package/lib/module/index.js +2 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/navigators/createStackNavigator.js +37 -33
- package/lib/module/navigators/createStackNavigator.js.map +1 -1
- package/lib/module/utils/GestureHandlerContext.js +5 -0
- package/lib/module/utils/GestureHandlerContext.js.map +1 -0
- package/lib/module/utils/gestureActivationCriteria.js +10 -8
- package/lib/module/utils/gestureActivationCriteria.js.map +1 -1
- package/lib/module/utils/useCardAnimation.js +1 -1
- package/lib/module/utils/useCardAnimation.js.map +1 -1
- package/lib/module/utils/useGestureHandler.js +9 -0
- package/lib/module/utils/useGestureHandler.js.map +1 -0
- package/lib/module/utils/useKeyboardManager.js +71 -26
- package/lib/module/utils/useKeyboardManager.js.map +1 -1
- package/lib/module/views/GestureHandler.js +15 -14
- package/lib/module/views/GestureHandler.js.map +1 -1
- package/lib/module/views/GestureHandlerNative.js +16 -10
- package/lib/module/views/GestureHandlerNative.js.map +1 -1
- package/lib/module/views/Header/Header.js +4 -2
- package/lib/module/views/Header/Header.js.map +1 -1
- package/lib/module/views/Header/HeaderContainer.js +60 -21
- package/lib/module/views/Header/HeaderContainer.js.map +1 -1
- package/lib/module/views/Header/HeaderSegment.js +6 -9
- package/lib/module/views/Header/HeaderSegment.js.map +1 -1
- package/lib/module/views/Stack/Card.js +170 -123
- package/lib/module/views/Stack/Card.js.map +1 -1
- package/lib/module/views/Stack/CardContainer.js +96 -98
- package/lib/module/views/Stack/CardContainer.js.map +1 -1
- package/lib/module/views/Stack/CardContent.js +10 -37
- package/lib/module/views/Stack/CardContent.js.map +1 -1
- package/lib/module/views/Stack/CardStack.js +211 -147
- package/lib/module/views/Stack/CardStack.js.map +1 -1
- package/lib/module/views/Stack/StackView.js +106 -57
- package/lib/module/views/Stack/StackView.js.map +1 -1
- package/lib/typescript/src/TransitionConfigs/CardStyleInterpolators.d.ts +20 -12
- package/lib/typescript/src/TransitionConfigs/CardStyleInterpolators.d.ts.map +1 -1
- package/lib/typescript/src/TransitionConfigs/HeaderStyleInterpolators.d.ts +4 -4
- package/lib/typescript/src/TransitionConfigs/HeaderStyleInterpolators.d.ts.map +1 -1
- package/lib/typescript/src/TransitionConfigs/TransitionEasings.d.ts +38 -0
- package/lib/typescript/src/TransitionConfigs/TransitionEasings.d.ts.map +1 -0
- package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts +15 -3
- package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts.map +1 -1
- package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts +44 -15
- package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +12 -12
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/navigators/createStackNavigator.d.ts +10 -15
- package/lib/typescript/src/navigators/createStackNavigator.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +120 -108
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/utils/CardAnimationContext.d.ts +1 -1
- package/lib/typescript/src/utils/CardAnimationContext.d.ts.map +1 -1
- package/lib/typescript/src/utils/GestureHandlerContext.d.ts +3 -0
- package/lib/typescript/src/utils/GestureHandlerContext.d.ts.map +1 -0
- package/lib/typescript/src/utils/gestureActivationCriteria.d.ts +4 -51
- package/lib/typescript/src/utils/gestureActivationCriteria.d.ts.map +1 -1
- package/lib/typescript/src/utils/getDistanceForDirection.d.ts +1 -1
- package/lib/typescript/src/utils/getDistanceForDirection.d.ts.map +1 -1
- package/lib/typescript/src/utils/getInvertedMultiplier.d.ts +1 -1
- package/lib/typescript/src/utils/getInvertedMultiplier.d.ts.map +1 -1
- package/lib/typescript/src/utils/getModalRoutesKeys.d.ts +1 -1
- package/lib/typescript/src/utils/getModalRoutesKeys.d.ts.map +1 -1
- package/lib/typescript/src/utils/getShadowStyle.d.ts +5 -5
- package/lib/typescript/src/utils/useCardAnimation.d.ts +1 -1
- package/lib/typescript/src/utils/useCardAnimation.d.ts.map +1 -1
- package/lib/typescript/src/utils/useGestureHandler.d.ts +2 -0
- package/lib/typescript/src/utils/useGestureHandler.d.ts.map +1 -0
- package/lib/typescript/src/utils/useKeyboardManager.d.ts +12 -2
- package/lib/typescript/src/utils/useKeyboardManager.d.ts.map +1 -1
- package/lib/typescript/src/views/GestureHandler.android.d.ts +1 -1
- package/lib/typescript/src/views/GestureHandler.android.d.ts.map +1 -1
- package/lib/typescript/src/views/GestureHandler.d.ts +7 -11
- package/lib/typescript/src/views/GestureHandler.d.ts.map +1 -1
- package/lib/typescript/src/views/GestureHandler.ios.d.ts +1 -1
- package/lib/typescript/src/views/GestureHandler.ios.d.ts.map +1 -1
- package/lib/typescript/src/views/GestureHandlerNative.d.ts +9 -4
- package/lib/typescript/src/views/GestureHandlerNative.d.ts.map +1 -1
- package/lib/typescript/src/views/Header/Header.d.ts +2 -2
- package/lib/typescript/src/views/Header/Header.d.ts.map +1 -1
- package/lib/typescript/src/views/Header/HeaderContainer.d.ts +9 -3
- package/lib/typescript/src/views/Header/HeaderContainer.d.ts.map +1 -1
- package/lib/typescript/src/views/Header/HeaderSegment.d.ts +6 -4
- package/lib/typescript/src/views/Header/HeaderSegment.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/Card.d.ts +14 -8
- package/lib/typescript/src/views/Stack/Card.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/CardContainer.d.ts +7 -7
- package/lib/typescript/src/views/Stack/CardContainer.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/CardContent.d.ts +1 -1
- package/lib/typescript/src/views/Stack/CardContent.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/CardStack.d.ts +10 -6
- package/lib/typescript/src/views/Stack/CardStack.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/StackView.d.ts +14 -17
- package/lib/typescript/src/views/Stack/StackView.d.ts.map +1 -1
- package/package.json +23 -24
- package/src/TransitionConfigs/CardStyleInterpolators.tsx +399 -100
- package/src/TransitionConfigs/HeaderStyleInterpolators.tsx +15 -46
- package/src/TransitionConfigs/TransitionEasings.tsx +90 -0
- package/src/TransitionConfigs/TransitionPresets.tsx +56 -10
- package/src/TransitionConfigs/TransitionSpecs.tsx +68 -34
- package/src/index.tsx +3 -2
- package/src/navigators/createStackNavigator.tsx +74 -91
- package/src/types.tsx +149 -123
- package/src/utils/GestureHandlerContext.tsx +5 -0
- package/src/utils/gestureActivationCriteria.tsx +14 -10
- package/src/utils/useCardAnimation.tsx +1 -1
- package/src/utils/useGestureHandler.tsx +9 -0
- package/src/utils/useKeyboardManager.tsx +96 -33
- package/src/views/GestureHandler.tsx +24 -16
- package/src/views/GestureHandlerNative.tsx +24 -13
- package/src/views/Header/Header.tsx +4 -2
- package/src/views/Header/HeaderContainer.tsx +79 -37
- package/src/views/Header/HeaderSegment.tsx +10 -11
- package/src/views/Stack/Card.tsx +241 -154
- package/src/views/Stack/CardContainer.tsx +111 -127
- package/src/views/Stack/CardContent.tsx +13 -51
- package/src/views/Stack/CardStack.tsx +478 -335
- package/src/views/Stack/StackView.tsx +181 -82
- package/lib/module/utils/GestureHandlerRefContext.js +0 -5
- package/lib/module/utils/GestureHandlerRefContext.js.map +0 -1
- package/lib/module/utils/findLastIndex.js +0 -11
- package/lib/module/utils/findLastIndex.js.map +0 -1
- package/lib/module/utils/useGestureHandlerRef.js +0 -12
- package/lib/module/utils/useGestureHandlerRef.js.map +0 -1
- package/lib/module/views/Stack/CardA11yWrapper.js +0 -43
- package/lib/module/views/Stack/CardA11yWrapper.js.map +0 -1
- package/lib/typescript/src/utils/GestureHandlerRefContext.d.ts +0 -3
- package/lib/typescript/src/utils/GestureHandlerRefContext.d.ts.map +0 -1
- package/lib/typescript/src/utils/findLastIndex.d.ts +0 -2
- package/lib/typescript/src/utils/findLastIndex.d.ts.map +0 -1
- package/lib/typescript/src/utils/useGestureHandlerRef.d.ts +0 -3
- package/lib/typescript/src/utils/useGestureHandlerRef.d.ts.map +0 -1
- package/lib/typescript/src/views/Stack/CardA11yWrapper.d.ts +0 -15
- package/lib/typescript/src/views/Stack/CardA11yWrapper.d.ts.map +0 -1
- package/src/utils/GestureHandlerRefContext.tsx +0 -5
- package/src/utils/findLastIndex.tsx +0 -9
- package/src/utils/useGestureHandlerRef.tsx +0 -15
- package/src/views/Stack/CardA11yWrapper.tsx +0 -67
package/src/views/Stack/Card.tsx
CHANGED
|
@@ -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
|
-
|
|
39
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
222
|
-
|
|
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
|
-
|
|
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
|
-
|
|
372
|
-
|
|
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
|
-
|
|
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
|
|
437
|
-
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
|
|
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
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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
|
|
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
|
-
<
|
|
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.
|
|
610
|
+
style={[styles.card, cardStyle]}
|
|
536
611
|
>
|
|
537
|
-
{shadowEnabled
|
|
612
|
+
{(shadowEnabled ?? shadowStyle != null) && !isTransparent ? (
|
|
538
613
|
<Animated.View
|
|
539
614
|
style={[
|
|
540
615
|
styles.shadow,
|
|
541
|
-
gestureDirection === 'horizontal'
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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
|
-
</
|
|
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:
|
|
590
|
-
height:
|
|
682
|
+
width: 0,
|
|
683
|
+
height: -3,
|
|
591
684
|
},
|
|
592
|
-
radius:
|
|
593
|
-
opacity: 0.
|
|
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,
|