@react-navigation/stack 8.0.0-alpha.51 → 8.0.0-alpha.53
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 +241 -83
- 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/navigators/createStackNavigator.js +25 -16
- package/lib/module/navigators/createStackNavigator.js.map +1 -1
- package/lib/module/views/Header/Header.js +2 -0
- package/lib/module/views/Header/Header.js.map +1 -1
- package/lib/module/views/Header/HeaderContainer.js +8 -3
- package/lib/module/views/Header/HeaderContainer.js.map +1 -1
- package/lib/module/views/Header/HeaderSegment.js +3 -6
- package/lib/module/views/Header/HeaderSegment.js.map +1 -1
- package/lib/module/views/Stack/Card.js +57 -32
- package/lib/module/views/Stack/Card.js.map +1 -1
- package/lib/module/views/Stack/CardContainer.js +17 -2
- package/lib/module/views/Stack/CardContainer.js.map +1 -1
- package/lib/module/views/Stack/CardStack.js +24 -13
- package/lib/module/views/Stack/CardStack.js.map +1 -1
- package/lib/typescript/src/TransitionConfigs/CardStyleInterpolators.d.ts +19 -11
- package/lib/typescript/src/TransitionConfigs/CardStyleInterpolators.d.ts.map +1 -1
- package/lib/typescript/src/TransitionConfigs/HeaderStyleInterpolators.d.ts +3 -3
- 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 +14 -2
- package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts.map +1 -1
- package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts +43 -14
- package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts.map +1 -1
- package/lib/typescript/src/navigators/createStackNavigator.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +44 -28
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/views/Header/Header.d.ts +1 -1
- package/lib/typescript/src/views/Header/Header.d.ts.map +1 -1
- package/lib/typescript/src/views/Header/HeaderContainer.d.ts.map +1 -1
- package/lib/typescript/src/views/Header/HeaderSegment.d.ts +1 -0
- package/lib/typescript/src/views/Header/HeaderSegment.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/Card.d.ts +9 -3
- package/lib/typescript/src/views/Stack/Card.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/CardContainer.d.ts +3 -1
- package/lib/typescript/src/views/Stack/CardContainer.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/CardStack.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/TransitionConfigs/CardStyleInterpolators.tsx +351 -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/navigators/createStackNavigator.tsx +33 -24
- package/src/types.tsx +46 -29
- package/src/views/Header/Header.tsx +2 -0
- package/src/views/Header/HeaderContainer.tsx +10 -4
- package/src/views/Header/HeaderSegment.tsx +4 -4
- package/src/views/Stack/Card.tsx +82 -37
- package/src/views/Stack/CardContainer.tsx +17 -2
- package/src/views/Stack/CardStack.tsx +69 -14
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
NavigationProvider,
|
|
5
5
|
type Route,
|
|
6
6
|
useLinkBuilder,
|
|
7
|
+
useLocale,
|
|
7
8
|
} from '@react-navigation/native';
|
|
8
9
|
import * as React from 'react';
|
|
9
10
|
import {
|
|
@@ -17,10 +18,10 @@ import {
|
|
|
17
18
|
import {
|
|
18
19
|
forNoAnimation,
|
|
19
20
|
forSlideLeft,
|
|
20
|
-
forSlideRight,
|
|
21
21
|
forSlideUp,
|
|
22
22
|
} from '../../TransitionConfigs/HeaderStyleInterpolators';
|
|
23
23
|
import type { Scene, StackHeaderMode, StackHeaderProps } from '../../types';
|
|
24
|
+
import { getInvertedMultiplier } from '../../utils/getInvertedMultiplier';
|
|
24
25
|
import { Header } from './Header';
|
|
25
26
|
|
|
26
27
|
export type HeaderHeight = {
|
|
@@ -53,6 +54,7 @@ export function HeaderContainer({
|
|
|
53
54
|
const focusedRoute = getFocusedRoute();
|
|
54
55
|
const parentHeaderBack = React.use(HeaderBackContext);
|
|
55
56
|
const { buildHref } = useLinkBuilder();
|
|
57
|
+
const { direction } = useLocale();
|
|
56
58
|
|
|
57
59
|
return (
|
|
58
60
|
<View style={[styles.container, style]}>
|
|
@@ -131,6 +133,12 @@ export function HeaderContainer({
|
|
|
131
133
|
const props: StackHeaderProps = {
|
|
132
134
|
back: headerBack,
|
|
133
135
|
progress: scene.progress,
|
|
136
|
+
inverted: getInvertedMultiplier(
|
|
137
|
+
isHeaderStatic && nextHeaderlessGestureDirection
|
|
138
|
+
? nextHeaderlessGestureDirection
|
|
139
|
+
: scene.descriptor.options.gestureDirection,
|
|
140
|
+
direction === 'rtl'
|
|
141
|
+
),
|
|
134
142
|
options: scene.descriptor.options,
|
|
135
143
|
route: scene.descriptor.route,
|
|
136
144
|
navigation: scene.descriptor.navigation,
|
|
@@ -140,9 +148,7 @@ export function HeaderContainer({
|
|
|
140
148
|
? nextHeaderlessGestureDirection === 'vertical' ||
|
|
141
149
|
nextHeaderlessGestureDirection === 'vertical-inverted'
|
|
142
150
|
? forSlideUp
|
|
143
|
-
:
|
|
144
|
-
? forSlideRight
|
|
145
|
-
: forSlideLeft
|
|
151
|
+
: forSlideLeft
|
|
146
152
|
: headerStyleInterpolator
|
|
147
153
|
: forNoAnimation,
|
|
148
154
|
};
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
HeaderTitle,
|
|
7
7
|
useFrameSize,
|
|
8
8
|
} from '@react-navigation/elements';
|
|
9
|
-
import { useLocale } from '@react-navigation/native';
|
|
10
9
|
import * as React from 'react';
|
|
11
10
|
import { StyleSheet, type ViewStyle } from 'react-native';
|
|
12
11
|
|
|
@@ -23,15 +22,16 @@ type Props = Omit<StackHeaderOptions, 'headerStatusBarHeight'> & {
|
|
|
23
22
|
onGoBack?: (() => void) | undefined;
|
|
24
23
|
backHref?: string | undefined;
|
|
25
24
|
progress: SceneProgress;
|
|
25
|
+
inverted: 1 | -1;
|
|
26
26
|
styleInterpolator: StackHeaderStyleInterpolator;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
export function HeaderSegment(props: Props) {
|
|
30
|
-
const { direction } = useLocale();
|
|
31
30
|
const layout = useFrameSize((frame) => frame, true);
|
|
32
31
|
|
|
33
32
|
const {
|
|
34
33
|
progress,
|
|
34
|
+
inverted,
|
|
35
35
|
modal,
|
|
36
36
|
onGoBack,
|
|
37
37
|
backHref,
|
|
@@ -75,7 +75,7 @@ export function HeaderSegment(props: Props) {
|
|
|
75
75
|
styleInterpolator({
|
|
76
76
|
current: { progress: progress.current },
|
|
77
77
|
next: progress.next && { progress: progress.next },
|
|
78
|
-
|
|
78
|
+
inverted,
|
|
79
79
|
layouts: {
|
|
80
80
|
header: {
|
|
81
81
|
height: headerHeight,
|
|
@@ -84,7 +84,7 @@ export function HeaderSegment(props: Props) {
|
|
|
84
84
|
screen: layout,
|
|
85
85
|
},
|
|
86
86
|
}),
|
|
87
|
-
[styleInterpolator, progress,
|
|
87
|
+
[styleInterpolator, progress, inverted, headerHeight, layout]
|
|
88
88
|
);
|
|
89
89
|
|
|
90
90
|
const headerLeft: StackHeaderOptions['headerLeft'] = left
|
package/src/views/Stack/Card.tsx
CHANGED
|
@@ -32,8 +32,16 @@ type Props = {
|
|
|
32
32
|
interpolationIndex: number;
|
|
33
33
|
opening: boolean;
|
|
34
34
|
closing: boolean;
|
|
35
|
-
|
|
36
|
-
|
|
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;
|
|
37
45
|
gesture: Animated.Value;
|
|
38
46
|
layout: Layout;
|
|
39
47
|
insets: EdgeInsets;
|
|
@@ -52,7 +60,7 @@ type Props = {
|
|
|
52
60
|
style: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
|
53
61
|
}) => React.ReactNode)
|
|
54
62
|
| undefined;
|
|
55
|
-
overlayEnabled: boolean;
|
|
63
|
+
overlayEnabled: boolean | undefined;
|
|
56
64
|
shadowEnabled: boolean | undefined;
|
|
57
65
|
gestureEnabled: boolean;
|
|
58
66
|
gestureResponseDistance?: number | undefined;
|
|
@@ -121,7 +129,7 @@ const defaultOverlay = ({
|
|
|
121
129
|
}) => (style ? <Animated.View style={[styles.overlay, style]} /> : null);
|
|
122
130
|
|
|
123
131
|
function Card({
|
|
124
|
-
shadowEnabled
|
|
132
|
+
shadowEnabled,
|
|
125
133
|
gestureEnabled = true,
|
|
126
134
|
gestureVelocityImpact = GESTURE_VELOCITY_IMPACT,
|
|
127
135
|
overlay = defaultOverlay,
|
|
@@ -153,15 +161,17 @@ function Card({
|
|
|
153
161
|
contentStyle,
|
|
154
162
|
}: Props) {
|
|
155
163
|
const didInitiallyAnimate = React.useRef(false);
|
|
164
|
+
const isClosingValueLockedRef = React.useRef(false);
|
|
156
165
|
const lastToValueRef = React.useRef<number | undefined>(undefined);
|
|
166
|
+
const isAnimatingRef = React.useRef(false);
|
|
167
|
+
const animationIdRef = React.useRef(0);
|
|
157
168
|
|
|
158
169
|
const animationHandleRef = React.useRef<number | undefined>(undefined);
|
|
159
170
|
const pendingGestureCallbackRef =
|
|
160
171
|
React.useRef<ReturnType<typeof setTimeout>>(undefined);
|
|
161
172
|
const pendingOnCloseCallbackRef =
|
|
162
173
|
React.useRef<ReturnType<typeof setTimeout>>(undefined);
|
|
163
|
-
|
|
164
|
-
const [isClosing] = React.useState(() => new Animated.Value(FALSE));
|
|
174
|
+
const timeoutRef = React.useRef<ReturnType<typeof setTimeout>>(undefined);
|
|
165
175
|
|
|
166
176
|
const [inverted] = React.useState(
|
|
167
177
|
() =>
|
|
@@ -185,6 +195,8 @@ function Card({
|
|
|
185
195
|
closing: boolean;
|
|
186
196
|
velocity?: number;
|
|
187
197
|
}) => {
|
|
198
|
+
const id = ++animationIdRef.current;
|
|
199
|
+
|
|
188
200
|
const toValue = getAnimateToValue({
|
|
189
201
|
closing: isClosingParam,
|
|
190
202
|
layout,
|
|
@@ -194,7 +206,13 @@ function Card({
|
|
|
194
206
|
});
|
|
195
207
|
|
|
196
208
|
lastToValueRef.current = toValue;
|
|
197
|
-
|
|
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
|
+
}
|
|
198
216
|
|
|
199
217
|
const spec = isClosingParam ? transitionSpec.close : transitionSpec.open;
|
|
200
218
|
const animation =
|
|
@@ -212,6 +230,9 @@ function Card({
|
|
|
212
230
|
});
|
|
213
231
|
|
|
214
232
|
const onFinish = () => {
|
|
233
|
+
current.closing.setValue(isClosingParam ? TRUE : FALSE);
|
|
234
|
+
isClosingValueLockedRef.current = false;
|
|
235
|
+
|
|
215
236
|
if (isClosingParam) {
|
|
216
237
|
onClose();
|
|
217
238
|
} else {
|
|
@@ -225,6 +246,8 @@ function Card({
|
|
|
225
246
|
};
|
|
226
247
|
|
|
227
248
|
if (animated) {
|
|
249
|
+
isAnimatingRef.current = true;
|
|
250
|
+
|
|
228
251
|
animation(gesture, {
|
|
229
252
|
...spec.config,
|
|
230
253
|
velocity,
|
|
@@ -232,6 +255,12 @@ function Card({
|
|
|
232
255
|
useNativeDriver,
|
|
233
256
|
isInteraction: false,
|
|
234
257
|
}).start(({ finished }) => {
|
|
258
|
+
if (id !== animationIdRef.current) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
isAnimatingRef.current = false;
|
|
263
|
+
|
|
235
264
|
clearTimeout(pendingGestureCallbackRef.current);
|
|
236
265
|
|
|
237
266
|
if (finished) {
|
|
@@ -239,6 +268,8 @@ function Card({
|
|
|
239
268
|
}
|
|
240
269
|
});
|
|
241
270
|
} else {
|
|
271
|
+
isAnimatingRef.current = false;
|
|
272
|
+
gesture.setValue(toValue);
|
|
242
273
|
onFinish();
|
|
243
274
|
}
|
|
244
275
|
}
|
|
@@ -270,17 +301,23 @@ function Card({
|
|
|
270
301
|
} | null>(null);
|
|
271
302
|
|
|
272
303
|
React.useEffect(() => {
|
|
304
|
+
const animationIdRefForCleanup = animationIdRef;
|
|
305
|
+
|
|
273
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
|
+
|
|
274
312
|
if (animationHandleRef.current) {
|
|
275
313
|
cancelAnimationFrame(animationHandleRef.current);
|
|
276
314
|
}
|
|
277
315
|
|
|
278
316
|
clearTimeout(pendingGestureCallbackRef.current);
|
|
279
317
|
clearTimeout(pendingOnCloseCallbackRef.current);
|
|
318
|
+
clearTimeout(timeoutRef.current);
|
|
280
319
|
};
|
|
281
|
-
}, []);
|
|
282
|
-
|
|
283
|
-
const timeoutRef = React.useRef<ReturnType<typeof setTimeout>>(undefined);
|
|
320
|
+
}, [gesture]);
|
|
284
321
|
|
|
285
322
|
const maybeAnimate = useLatestCallback(() => {
|
|
286
323
|
clearTimeout(pendingGestureCallbackRef.current);
|
|
@@ -318,7 +355,10 @@ function Card({
|
|
|
318
355
|
// When route was closed due to a gesture, the animation would've happened already
|
|
319
356
|
// It's still important to trigger the animation so that `onClose` is called
|
|
320
357
|
// If `onClose` is not called, cleanup step won't be performed for gestures
|
|
321
|
-
|
|
358
|
+
// We also check if animation was already running so we don't restart it unnecessarily
|
|
359
|
+
if (!isAnimatingRef.current || lastToValueRef.current !== toValue) {
|
|
360
|
+
animate({ closing });
|
|
361
|
+
}
|
|
322
362
|
} else if (
|
|
323
363
|
typeof previousOpening === 'boolean' &&
|
|
324
364
|
opening &&
|
|
@@ -365,9 +405,8 @@ function Card({
|
|
|
365
405
|
const interpolationProps = React.useMemo(
|
|
366
406
|
() => ({
|
|
367
407
|
index: interpolationIndex,
|
|
368
|
-
current
|
|
369
|
-
next
|
|
370
|
-
closing: isClosing,
|
|
408
|
+
current,
|
|
409
|
+
next,
|
|
371
410
|
swiping: isSwiping,
|
|
372
411
|
inverted,
|
|
373
412
|
layouts: {
|
|
@@ -384,7 +423,6 @@ function Card({
|
|
|
384
423
|
interpolationIndex,
|
|
385
424
|
current,
|
|
386
425
|
next,
|
|
387
|
-
isClosing,
|
|
388
426
|
isSwiping,
|
|
389
427
|
inverted,
|
|
390
428
|
layout,
|
|
@@ -405,7 +443,14 @@ function Card({
|
|
|
405
443
|
() => {
|
|
406
444
|
clearTimeout(pendingGestureCallbackRef.current);
|
|
407
445
|
clearTimeout(pendingOnCloseCallbackRef.current);
|
|
446
|
+
|
|
408
447
|
isSwiping.setValue(TRUE);
|
|
448
|
+
|
|
449
|
+
if (!isClosingValueLockedRef.current) {
|
|
450
|
+
isClosingValueLockedRef.current = true;
|
|
451
|
+
current.closing.setValue(TRUE);
|
|
452
|
+
}
|
|
453
|
+
|
|
409
454
|
onGestureBegin?.();
|
|
410
455
|
}
|
|
411
456
|
);
|
|
@@ -538,13 +583,13 @@ function Card({
|
|
|
538
583
|
// Animated needs the animated value to be used somewhere, otherwise things don't update properly.
|
|
539
584
|
// If we disable animations and hide header, it could end up making the value unused.
|
|
540
585
|
// So we have this dummy style that will always be used regardless of what else changed.
|
|
541
|
-
opacity: current,
|
|
586
|
+
opacity: current.progress,
|
|
542
587
|
}}
|
|
543
588
|
// Make sure that this view isn't removed. If this view is removed, our style with animated value won't apply
|
|
544
589
|
collapsable={false}
|
|
545
590
|
/>
|
|
546
591
|
) : null}
|
|
547
|
-
{overlayEnabled ? (
|
|
592
|
+
{(overlayEnabled ?? overlayStyle != null) ? (
|
|
548
593
|
<View style={[StyleSheet.absoluteFill, { pointerEvents: 'box-none' }]}>
|
|
549
594
|
{overlay({ style: overlayStyle })}
|
|
550
595
|
</View>
|
|
@@ -557,17 +602,16 @@ function Card({
|
|
|
557
602
|
needsOffscreenAlphaCompositing={hasOpacityStyle(cardStyle)}
|
|
558
603
|
style={[styles.card, cardStyle]}
|
|
559
604
|
>
|
|
560
|
-
{shadowEnabled
|
|
605
|
+
{(shadowEnabled ?? shadowStyle != null) && !isTransparent ? (
|
|
561
606
|
<Animated.View
|
|
562
607
|
style={[
|
|
563
608
|
styles.shadow,
|
|
564
|
-
gestureDirection === 'horizontal'
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
: [styles.shadowVertical, styles.shadowBottom],
|
|
609
|
+
gestureDirection === 'horizontal' ||
|
|
610
|
+
gestureDirection === 'horizontal-inverted'
|
|
611
|
+
? styles.shadowHorizontal
|
|
612
|
+
: gestureDirection === 'vertical'
|
|
613
|
+
? [styles.shadowVertical, styles.shadowTop]
|
|
614
|
+
: [styles.shadowVertical, styles.shadowBottom],
|
|
571
615
|
{ backgroundColor },
|
|
572
616
|
shadowStyle,
|
|
573
617
|
]}
|
|
@@ -580,7 +624,9 @@ function Card({
|
|
|
580
624
|
>
|
|
581
625
|
{children}
|
|
582
626
|
</CardContent>
|
|
583
|
-
{dimStyle
|
|
627
|
+
{dimStyle != null ? (
|
|
628
|
+
<Animated.View style={[styles.dim, dimStyle]} />
|
|
629
|
+
) : null}
|
|
584
630
|
</Animated.View>
|
|
585
631
|
</GestureDetector>
|
|
586
632
|
</Animated.View>
|
|
@@ -597,6 +643,10 @@ const styles = StyleSheet.create({
|
|
|
597
643
|
},
|
|
598
644
|
card: {
|
|
599
645
|
flex: 1,
|
|
646
|
+
// The flip animation adds `backfaceVisibility`
|
|
647
|
+
// On Android, it crashes when the style property is removed
|
|
648
|
+
// So we always add the property with the default
|
|
649
|
+
backfaceVisibility: 'visible',
|
|
600
650
|
// This is necessary for gestures to work
|
|
601
651
|
// Without this, gestures won't work if the child view is flattened
|
|
602
652
|
pointerEvents: 'auto',
|
|
@@ -617,23 +667,18 @@ const styles = StyleSheet.create({
|
|
|
617
667
|
},
|
|
618
668
|
shadowHorizontal: {
|
|
619
669
|
top: 0,
|
|
670
|
+
start: 0,
|
|
671
|
+
end: 0,
|
|
620
672
|
bottom: 0,
|
|
621
|
-
width: 3,
|
|
622
673
|
...getShadowStyle({
|
|
623
674
|
offset: {
|
|
624
|
-
width:
|
|
625
|
-
height:
|
|
675
|
+
width: 0,
|
|
676
|
+
height: -3,
|
|
626
677
|
},
|
|
627
|
-
radius:
|
|
628
|
-
opacity: 0.
|
|
678
|
+
radius: 12,
|
|
679
|
+
opacity: 0.04,
|
|
629
680
|
}),
|
|
630
681
|
},
|
|
631
|
-
shadowStart: {
|
|
632
|
-
start: 0,
|
|
633
|
-
},
|
|
634
|
-
shadowEnd: {
|
|
635
|
-
end: 0,
|
|
636
|
-
},
|
|
637
682
|
shadowVertical: {
|
|
638
683
|
start: 0,
|
|
639
684
|
end: 0,
|
|
@@ -29,6 +29,8 @@ type Props = {
|
|
|
29
29
|
focused: boolean;
|
|
30
30
|
opening: boolean;
|
|
31
31
|
closing: boolean;
|
|
32
|
+
closingAnimated: Animated.Value;
|
|
33
|
+
nextClosingAnimated: Animated.Value | undefined;
|
|
32
34
|
modal: boolean;
|
|
33
35
|
layout: Layout;
|
|
34
36
|
gesture: Animated.Value;
|
|
@@ -66,6 +68,8 @@ function CardContainerInner({
|
|
|
66
68
|
active,
|
|
67
69
|
opening,
|
|
68
70
|
closing,
|
|
71
|
+
closingAnimated,
|
|
72
|
+
nextClosingAnimated,
|
|
69
73
|
gesture,
|
|
70
74
|
focused,
|
|
71
75
|
modal,
|
|
@@ -207,6 +211,17 @@ function CardContainerInner({
|
|
|
207
211
|
|
|
208
212
|
const animated = animation !== 'none';
|
|
209
213
|
|
|
214
|
+
const { current, next } = React.useMemo(
|
|
215
|
+
() => ({
|
|
216
|
+
current: { progress: scene.progress.current, closing: closingAnimated },
|
|
217
|
+
next:
|
|
218
|
+
scene.progress.next && nextClosingAnimated
|
|
219
|
+
? { progress: scene.progress.next, closing: nextClosingAnimated }
|
|
220
|
+
: undefined,
|
|
221
|
+
}),
|
|
222
|
+
[closingAnimated, nextClosingAnimated, scene.progress]
|
|
223
|
+
);
|
|
224
|
+
|
|
210
225
|
return (
|
|
211
226
|
<Card
|
|
212
227
|
animated={animated}
|
|
@@ -216,8 +231,8 @@ function CardContainerInner({
|
|
|
216
231
|
insets={insets}
|
|
217
232
|
direction={direction}
|
|
218
233
|
gesture={gesture}
|
|
219
|
-
current={
|
|
220
|
-
next={
|
|
234
|
+
current={current}
|
|
235
|
+
next={next}
|
|
221
236
|
opening={opening}
|
|
222
237
|
closing={closing}
|
|
223
238
|
onOpen={handleOpen}
|
|
@@ -20,15 +20,24 @@ import {
|
|
|
20
20
|
import type { EdgeInsets } from 'react-native-safe-area-context';
|
|
21
21
|
|
|
22
22
|
import {
|
|
23
|
+
forBottomSheetAndroid,
|
|
24
|
+
forCrossDissolveIOS,
|
|
25
|
+
forDialogAndroid,
|
|
26
|
+
forFadeFromBottomAndroid,
|
|
27
|
+
forFadeFromRightAndroid,
|
|
28
|
+
forFlipIOS,
|
|
23
29
|
forModalPresentationIOS,
|
|
24
30
|
forNoAnimation as forNoAnimationCard,
|
|
31
|
+
forRevealFromBottomAndroid,
|
|
32
|
+
forScaleFromCenterAndroid,
|
|
33
|
+
forVerticalIOS,
|
|
25
34
|
} from '../../TransitionConfigs/CardStyleInterpolators';
|
|
26
35
|
import {
|
|
27
|
-
BottomSheetAndroid,
|
|
28
36
|
DefaultTransition,
|
|
29
37
|
FadeFromBottomAndroid,
|
|
30
38
|
FadeFromRightAndroid,
|
|
31
39
|
ModalFadeTransition,
|
|
40
|
+
ModalFlipIOS,
|
|
32
41
|
ModalSlideFromBottomIOS,
|
|
33
42
|
ModalTransition,
|
|
34
43
|
RevealFromBottomAndroid,
|
|
@@ -102,17 +111,33 @@ const NAMED_TRANSITIONS_PRESETS = {
|
|
|
102
111
|
fade: ModalFadeTransition,
|
|
103
112
|
fade_from_bottom: FadeFromBottomAndroid,
|
|
104
113
|
fade_from_right: FadeFromRightAndroid,
|
|
114
|
+
flip: ModalFlipIOS,
|
|
115
|
+
ios_from_left: SlideFromLeftIOS,
|
|
116
|
+
ios_from_right: SlideFromRightIOS,
|
|
105
117
|
none: DefaultTransition,
|
|
106
118
|
reveal_from_bottom: RevealFromBottomAndroid,
|
|
107
119
|
scale_from_center: ScaleFromCenterAndroid,
|
|
108
120
|
slide_from_left: SlideFromLeftIOS,
|
|
109
121
|
slide_from_right: SlideFromRightIOS,
|
|
110
|
-
slide_from_bottom:
|
|
111
|
-
ios: ModalSlideFromBottomIOS,
|
|
112
|
-
default: BottomSheetAndroid,
|
|
113
|
-
}),
|
|
122
|
+
slide_from_bottom: ModalSlideFromBottomIOS,
|
|
114
123
|
} as const satisfies Record<StackAnimationName, TransitionPreset>;
|
|
115
124
|
|
|
125
|
+
const GESTURE_DISABLED_INTERPOLATORS = [
|
|
126
|
+
forCrossDissolveIOS,
|
|
127
|
+
forDialogAndroid,
|
|
128
|
+
forFadeFromBottomAndroid,
|
|
129
|
+
forFadeFromRightAndroid,
|
|
130
|
+
forFlipIOS,
|
|
131
|
+
forRevealFromBottomAndroid,
|
|
132
|
+
forScaleFromCenterAndroid,
|
|
133
|
+
forVerticalIOS,
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
const SCREEN_HEADER_INTERPOLATORS = [
|
|
137
|
+
...GESTURE_DISABLED_INTERPOLATORS,
|
|
138
|
+
forBottomSheetAndroid,
|
|
139
|
+
];
|
|
140
|
+
|
|
116
141
|
const HIDDEN_HEADER_HEIGHT = { measured: false, value: 0 };
|
|
117
142
|
|
|
118
143
|
const getInterpolationIndex = (scenes: Scene[], index: number) => {
|
|
@@ -146,11 +171,26 @@ const getIsModalPresentation = (
|
|
|
146
171
|
) => {
|
|
147
172
|
return (
|
|
148
173
|
cardStyleInterpolator === forModalPresentationIOS ||
|
|
149
|
-
|
|
150
|
-
cardStyleInterpolator.name === 'forModalPresentationIOS'
|
|
174
|
+
cardStyleInterpolator.name === forModalPresentationIOS.name
|
|
151
175
|
);
|
|
152
176
|
};
|
|
153
177
|
|
|
178
|
+
const getIsGestureEnabledByDefault = (
|
|
179
|
+
cardStyleInterpolator: StackCardStyleInterpolator
|
|
180
|
+
) =>
|
|
181
|
+
!GESTURE_DISABLED_INTERPOLATORS.some(
|
|
182
|
+
(interpolator) =>
|
|
183
|
+
cardStyleInterpolator === interpolator ||
|
|
184
|
+
cardStyleInterpolator.name === interpolator.name
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const getIsScreenHeader = (cardStyleInterpolator: StackCardStyleInterpolator) =>
|
|
188
|
+
SCREEN_HEADER_INTERPOLATORS.some(
|
|
189
|
+
(interpolator) =>
|
|
190
|
+
cardStyleInterpolator === interpolator ||
|
|
191
|
+
cardStyleInterpolator.name === interpolator.name
|
|
192
|
+
);
|
|
193
|
+
|
|
154
194
|
const getIsInModalPresentation = (
|
|
155
195
|
scene: Scene,
|
|
156
196
|
interpolationIndex: number,
|
|
@@ -323,7 +363,6 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
323
363
|
}
|
|
324
364
|
|
|
325
365
|
const allRoutes = getAllRoutes(props.routes, props.state);
|
|
326
|
-
|
|
327
366
|
const gestures = allRoutes.reduce<GestureValues>((acc, curr) => {
|
|
328
367
|
const descriptor = props.descriptors[curr.key];
|
|
329
368
|
const { animation } = descriptor?.options || {};
|
|
@@ -354,6 +393,13 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
354
393
|
const nextRoute = isInactive ? undefined : self[index + 1];
|
|
355
394
|
|
|
356
395
|
const oldScene = state.scenes[index];
|
|
396
|
+
const previousScene =
|
|
397
|
+
oldScene?.route.key === route.key
|
|
398
|
+
? oldScene
|
|
399
|
+
: state.scenes.find((scene) => scene.route.key === route.key);
|
|
400
|
+
const closing =
|
|
401
|
+
previousScene?.closing ??
|
|
402
|
+
new Animated.Value(props.closingRouteKeys.includes(route.key) ? 1 : 0);
|
|
357
403
|
|
|
358
404
|
const currentGesture = gestures[route.key];
|
|
359
405
|
|
|
@@ -420,16 +466,17 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
420
466
|
: DefaultTransition;
|
|
421
467
|
|
|
422
468
|
const {
|
|
423
|
-
gestureEnabled = Platform.OS === 'ios' && isAnimationEnabled,
|
|
424
469
|
gestureDirection = transitionPreset.gestureDirection,
|
|
425
470
|
transitionSpec = transitionPreset.transitionSpec,
|
|
426
471
|
cardStyleInterpolator = isAnimationEnabled
|
|
427
472
|
? transitionPreset.cardStyleInterpolator
|
|
428
473
|
: forNoAnimationCard,
|
|
474
|
+
gestureEnabled = Platform.OS === 'ios' &&
|
|
475
|
+
isAnimationEnabled &&
|
|
476
|
+
getIsGestureEnabledByDefault(cardStyleInterpolator),
|
|
429
477
|
headerStyleInterpolator = transitionPreset.headerStyleInterpolator,
|
|
430
|
-
cardOverlayEnabled
|
|
431
|
-
|
|
432
|
-
getIsModalPresentation(cardStyleInterpolator),
|
|
478
|
+
cardOverlayEnabled,
|
|
479
|
+
cardShadowEnabled,
|
|
433
480
|
} = optionsForTransitionConfig;
|
|
434
481
|
|
|
435
482
|
const headerMode: StackHeaderMode =
|
|
@@ -439,7 +486,8 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
439
486
|
optionsForTransitionConfig.presentation === 'transparentModal' ||
|
|
440
487
|
nextOptions?.presentation === 'modal' ||
|
|
441
488
|
nextOptions?.presentation === 'transparentModal' ||
|
|
442
|
-
getIsModalPresentation(cardStyleInterpolator)
|
|
489
|
+
getIsModalPresentation(cardStyleInterpolator) ||
|
|
490
|
+
getIsScreenHeader(cardStyleInterpolator)
|
|
443
491
|
) &&
|
|
444
492
|
Platform.OS === 'ios' &&
|
|
445
493
|
descriptor.options.header === undefined
|
|
@@ -456,6 +504,7 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
456
504
|
...descriptor.options,
|
|
457
505
|
animation,
|
|
458
506
|
cardOverlayEnabled,
|
|
507
|
+
cardShadowEnabled,
|
|
459
508
|
cardStyleInterpolator,
|
|
460
509
|
gestureDirection,
|
|
461
510
|
gestureEnabled,
|
|
@@ -489,6 +538,7 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
489
538
|
)
|
|
490
539
|
: undefined,
|
|
491
540
|
},
|
|
541
|
+
closing,
|
|
492
542
|
__memo: [
|
|
493
543
|
state.layout,
|
|
494
544
|
descriptor,
|
|
@@ -810,10 +860,13 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
810
860
|
// Preloaded screens should stay mounted, but remain hidden until focused
|
|
811
861
|
(!isPreloaded && index >= routes.length - 2)));
|
|
812
862
|
|
|
813
|
-
const activityMode =
|
|
863
|
+
const activityMode =
|
|
864
|
+
// Keep focused and focusing screens interactive
|
|
814
865
|
focused || isFocusing
|
|
815
866
|
? 'normal'
|
|
816
867
|
: inactiveBehavior === 'none' ||
|
|
868
|
+
// Keep effects running until the closing animation finishes
|
|
869
|
+
isRemoving ||
|
|
817
870
|
// Unpause preloaded or retained screens so updates are visible
|
|
818
871
|
// Handle retained explicitly as isInactive won't be updated until animation end
|
|
819
872
|
isInactive ||
|
|
@@ -870,6 +923,8 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
870
923
|
focused={focused}
|
|
871
924
|
opening={openingRouteKeys.includes(route.key)}
|
|
872
925
|
closing={closingRouteKeys.includes(route.key)}
|
|
926
|
+
closingAnimated={scene.closing}
|
|
927
|
+
nextClosingAnimated={nextScene?.closing}
|
|
873
928
|
layout={layout}
|
|
874
929
|
gesture={gesture}
|
|
875
930
|
scene={scene}
|