react-native-screen-transitions 3.0.0-rc.3 → 3.0.0-rc.4
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/commonjs/blank-stack/components/screens.js +16 -11
- package/lib/commonjs/blank-stack/components/screens.js.map +1 -1
- package/lib/commonjs/blank-stack/components/stack-view.js +42 -36
- package/lib/commonjs/blank-stack/components/stack-view.js.map +1 -1
- package/lib/commonjs/native-stack/views/NativeStackView.native.js +110 -103
- package/lib/commonjs/native-stack/views/NativeStackView.native.js.map +1 -1
- package/lib/commonjs/shared/components/controllers/blank-stack-lifecycle.js +72 -0
- package/lib/commonjs/shared/components/controllers/blank-stack-lifecycle.js.map +1 -0
- package/lib/commonjs/shared/components/controllers/native-stack-lifecycle.js +79 -0
- package/lib/commonjs/shared/components/controllers/native-stack-lifecycle.js.map +1 -0
- package/lib/commonjs/shared/hooks/animation/use-screen-animation.js +11 -1
- package/lib/commonjs/shared/hooks/animation/use-screen-animation.js.map +1 -1
- package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js +11 -6
- package/lib/commonjs/shared/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/commonjs/shared/hooks/gestures/use-scroll-registry.js +7 -7
- package/lib/commonjs/shared/hooks/gestures/use-scroll-registry.js.map +1 -1
- package/lib/commonjs/shared/providers/gestures.provider.js +32 -5
- package/lib/commonjs/shared/providers/gestures.provider.js.map +1 -1
- package/lib/commonjs/shared/providers/register-bounds.provider.js +4 -3
- package/lib/commonjs/shared/providers/register-bounds.provider.js.map +1 -1
- package/lib/commonjs/shared/providers/routes.provider.js +48 -0
- package/lib/commonjs/shared/providers/routes.provider.js.map +1 -0
- package/lib/commonjs/shared/providers/screen-transition.provider.js.map +1 -1
- package/lib/commonjs/shared/types/state.types.js +9 -0
- package/lib/commonjs/shared/types/state.types.js.map +1 -0
- package/lib/commonjs/shared/utils/animation/compute-stack-progress.js +20 -0
- package/lib/commonjs/shared/utils/animation/compute-stack-progress.js.map +1 -0
- package/lib/commonjs/shared/utils/animation/derivations.js +1 -1
- package/lib/commonjs/shared/utils/animation/start-screen-transition.js +11 -11
- package/lib/commonjs/shared/utils/animation/start-screen-transition.js.map +1 -1
- package/lib/module/blank-stack/components/screens.js +16 -11
- package/lib/module/blank-stack/components/screens.js.map +1 -1
- package/lib/module/blank-stack/components/stack-view.js +42 -36
- package/lib/module/blank-stack/components/stack-view.js.map +1 -1
- package/lib/module/native-stack/views/NativeStackView.native.js +109 -102
- package/lib/module/native-stack/views/NativeStackView.native.js.map +1 -1
- package/lib/module/shared/components/controllers/blank-stack-lifecycle.js +66 -0
- package/lib/module/shared/components/controllers/blank-stack-lifecycle.js.map +1 -0
- package/lib/module/shared/components/controllers/native-stack-lifecycle.js +73 -0
- package/lib/module/shared/components/controllers/native-stack-lifecycle.js.map +1 -0
- package/lib/module/shared/hooks/animation/use-screen-animation.js +11 -1
- package/lib/module/shared/hooks/animation/use-screen-animation.js.map +1 -1
- package/lib/module/shared/hooks/gestures/use-build-gestures.js +11 -6
- package/lib/module/shared/hooks/gestures/use-build-gestures.js.map +1 -1
- package/lib/module/shared/hooks/gestures/use-scroll-registry.js +7 -7
- package/lib/module/shared/hooks/gestures/use-scroll-registry.js.map +1 -1
- package/lib/module/shared/providers/gestures.provider.js +31 -4
- package/lib/module/shared/providers/gestures.provider.js.map +1 -1
- package/lib/module/shared/providers/register-bounds.provider.js +4 -3
- package/lib/module/shared/providers/register-bounds.provider.js.map +1 -1
- package/lib/module/shared/providers/routes.provider.js +42 -0
- package/lib/module/shared/providers/routes.provider.js.map +1 -0
- package/lib/module/shared/providers/screen-transition.provider.js.map +1 -1
- package/lib/module/shared/types/state.types.js +5 -0
- package/lib/module/shared/types/state.types.js.map +1 -0
- package/lib/module/shared/utils/animation/compute-stack-progress.js +15 -0
- package/lib/module/shared/utils/animation/compute-stack-progress.js.map +1 -0
- package/lib/module/shared/utils/animation/derivations.js +1 -1
- package/lib/module/shared/utils/animation/start-screen-transition.js +11 -11
- package/lib/module/shared/utils/animation/start-screen-transition.js.map +1 -1
- package/lib/typescript/blank-stack/components/screens.d.ts.map +1 -1
- package/lib/typescript/blank-stack/components/stack-view.d.ts.map +1 -1
- package/lib/typescript/blank-stack/types.d.ts +1 -39
- package/lib/typescript/blank-stack/types.d.ts.map +1 -1
- package/lib/typescript/native-stack/views/NativeStackView.native.d.ts.map +1 -1
- package/lib/typescript/shared/components/controllers/blank-stack-lifecycle.d.ts +8 -0
- package/lib/typescript/shared/components/controllers/blank-stack-lifecycle.d.ts.map +1 -0
- package/lib/typescript/shared/components/controllers/native-stack-lifecycle.d.ts +8 -0
- package/lib/typescript/shared/components/controllers/native-stack-lifecycle.d.ts.map +1 -0
- package/lib/typescript/shared/hooks/animation/use-screen-animation.d.ts.map +1 -1
- package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts +2 -2
- package/lib/typescript/shared/hooks/gestures/use-build-gestures.d.ts.map +1 -1
- package/lib/typescript/shared/providers/gestures.provider.d.ts +8 -2
- package/lib/typescript/shared/providers/gestures.provider.d.ts.map +1 -1
- package/lib/typescript/shared/providers/register-bounds.provider.d.ts.map +1 -1
- package/lib/typescript/shared/providers/routes.provider.d.ts +19 -0
- package/lib/typescript/shared/providers/routes.provider.d.ts.map +1 -0
- package/lib/typescript/shared/providers/screen-transition.provider.d.ts +2 -2
- package/lib/typescript/shared/providers/screen-transition.provider.d.ts.map +1 -1
- package/lib/typescript/shared/types/animation.types.d.ts +12 -0
- package/lib/typescript/shared/types/animation.types.d.ts.map +1 -1
- package/lib/typescript/shared/types/state.types.d.ts +3 -0
- package/lib/typescript/shared/types/state.types.d.ts.map +1 -0
- package/lib/typescript/shared/utils/animation/compute-stack-progress.d.ts +3 -0
- package/lib/typescript/shared/utils/animation/compute-stack-progress.d.ts.map +1 -0
- package/lib/typescript/shared/utils/animation/start-screen-transition.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/blank-stack/components/screens.tsx +21 -15
- package/src/blank-stack/components/stack-view.tsx +55 -44
- package/src/blank-stack/types.ts +1 -24
- package/src/native-stack/views/NativeStackView.native.tsx +121 -112
- package/src/shared/__tests__/bounds.store.test.ts +14 -36
- package/src/shared/components/controllers/blank-stack-lifecycle.tsx +70 -0
- package/src/shared/components/controllers/native-stack-lifecycle.tsx +87 -0
- package/src/shared/hooks/animation/use-screen-animation.tsx +11 -6
- package/src/shared/hooks/gestures/use-build-gestures.tsx +12 -6
- package/src/shared/hooks/gestures/use-scroll-registry.tsx +7 -7
- package/src/shared/providers/gestures.provider.tsx +34 -5
- package/src/shared/providers/register-bounds.provider.tsx +4 -3
- package/src/shared/providers/routes.provider.tsx +54 -0
- package/src/shared/providers/screen-transition.provider.tsx +2 -2
- package/src/shared/types/animation.types.ts +13 -0
- package/src/shared/types/state.types.ts +2 -0
- package/src/shared/utils/animation/compute-stack-progress.ts +16 -0
- package/src/shared/utils/animation/derivations.ts +1 -1
- package/src/shared/utils/animation/start-screen-transition.ts +13 -10
- package/lib/commonjs/shared/components/controllers/screen-lifecycle.js +0 -142
- package/lib/commonjs/shared/components/controllers/screen-lifecycle.js.map +0 -1
- package/lib/commonjs/shared/hooks/gestures/use-parent-gesture-registry.js +0 -28
- package/lib/commonjs/shared/hooks/gestures/use-parent-gesture-registry.js.map +0 -1
- package/lib/module/shared/components/controllers/screen-lifecycle.js +0 -136
- package/lib/module/shared/components/controllers/screen-lifecycle.js.map +0 -1
- package/lib/module/shared/hooks/gestures/use-parent-gesture-registry.js +0 -23
- package/lib/module/shared/hooks/gestures/use-parent-gesture-registry.js.map +0 -1
- package/lib/typescript/shared/components/controllers/screen-lifecycle.d.ts +0 -12
- package/lib/typescript/shared/components/controllers/screen-lifecycle.d.ts.map +0 -1
- package/lib/typescript/shared/hooks/gestures/use-parent-gesture-registry.d.ts +0 -6
- package/lib/typescript/shared/hooks/gestures/use-parent-gesture-registry.d.ts.map +0 -1
- package/src/shared/components/controllers/screen-lifecycle.tsx +0 -154
- package/src/shared/hooks/gestures/use-parent-gesture-registry.tsx +0 -18
|
@@ -7,8 +7,9 @@ import * as React from "react";
|
|
|
7
7
|
import { StyleSheet } from "react-native";
|
|
8
8
|
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
|
9
9
|
import { ScreenContainer } from "react-native-screens";
|
|
10
|
-
import { BlankStackScreenLifecycleController } from "../../shared/components/controllers/
|
|
10
|
+
import { BlankStackScreenLifecycleController } from "../../shared/components/controllers/blank-stack-lifecycle";
|
|
11
11
|
import { FlagsProvider } from "../../shared/providers/flags.provider";
|
|
12
|
+
import { RoutesProvider } from "../../shared/providers/routes.provider";
|
|
12
13
|
import { ScreenTransitionProvider } from "../../shared/providers/screen-transition.provider";
|
|
13
14
|
import type { BlankStackDescriptor } from "../types";
|
|
14
15
|
import { withStackNavigationProvider } from "../utils/with-stack-navigation";
|
|
@@ -45,55 +46,65 @@ export const StackView = withStackNavigationProvider(
|
|
|
45
46
|
scenes,
|
|
46
47
|
shouldShowFloatOverlay,
|
|
47
48
|
}) => {
|
|
49
|
+
// Memoize route keys array for ScenesProvider
|
|
50
|
+
const routeKeys = React.useMemo(
|
|
51
|
+
() => routes.map((route) => route.key),
|
|
52
|
+
[routes],
|
|
53
|
+
);
|
|
54
|
+
|
|
48
55
|
return (
|
|
49
56
|
<FlagsProvider TRANSITIONS_ALWAYS_ON>
|
|
50
|
-
<
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
{
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
<RoutesProvider routeKeys={routeKeys}>
|
|
58
|
+
<GestureHandlerRootView>
|
|
59
|
+
<SafeAreaProviderCompat>
|
|
60
|
+
{shouldShowFloatOverlay ? <Overlay.Float /> : null}
|
|
61
|
+
<ScreenContainer style={styles.container}>
|
|
62
|
+
{scenes.map((scene, sceneIndex) => {
|
|
63
|
+
const descriptor = scene.descriptor;
|
|
64
|
+
const route = scene.route;
|
|
65
|
+
const isFocused = focusedIndex === sceneIndex;
|
|
66
|
+
const isBelowFocused = focusedIndex - 1 === sceneIndex;
|
|
59
67
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
const previousDescriptor =
|
|
69
|
+
scenes[sceneIndex - 1]?.descriptor ?? undefined;
|
|
70
|
+
const nextDescriptor =
|
|
71
|
+
scenes[sceneIndex + 1]?.descriptor ?? undefined;
|
|
64
72
|
|
|
65
|
-
|
|
73
|
+
const isPreloaded = descriptors[route.key] === undefined;
|
|
66
74
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
>
|
|
83
|
-
<ScreenTransitionProvider
|
|
84
|
-
previous={previousDescriptor}
|
|
85
|
-
current={descriptor}
|
|
86
|
-
next={nextDescriptor}
|
|
87
|
-
LifecycleController={BlankStackScreenLifecycleController}
|
|
75
|
+
// On Fabric, when screen is frozen, animated and reanimated values are not updated
|
|
76
|
+
// due to component being unmounted. To avoid this, we don't freeze the previous screen there
|
|
77
|
+
const shouldFreeze = isFabric()
|
|
78
|
+
? !isPreloaded && !isFocused && !isBelowFocused
|
|
79
|
+
: !isPreloaded && !isFocused;
|
|
80
|
+
return (
|
|
81
|
+
<Screen
|
|
82
|
+
key={route.key}
|
|
83
|
+
isPreloaded={isPreloaded}
|
|
84
|
+
index={sceneIndex}
|
|
85
|
+
activeScreensLimit={activeScreensLimit}
|
|
86
|
+
routeKey={route.key}
|
|
87
|
+
routesLength={routes.length}
|
|
88
|
+
shouldFreeze={shouldFreeze}
|
|
89
|
+
freezeOnBlur={descriptor.options.freezeOnBlur}
|
|
88
90
|
>
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
91
|
+
<ScreenTransitionProvider
|
|
92
|
+
previous={previousDescriptor}
|
|
93
|
+
current={descriptor}
|
|
94
|
+
next={nextDescriptor}
|
|
95
|
+
LifecycleController={
|
|
96
|
+
BlankStackScreenLifecycleController
|
|
97
|
+
}
|
|
98
|
+
>
|
|
99
|
+
<SceneView key={route.key} descriptor={descriptor} />
|
|
100
|
+
</ScreenTransitionProvider>
|
|
101
|
+
</Screen>
|
|
102
|
+
);
|
|
103
|
+
})}
|
|
104
|
+
</ScreenContainer>
|
|
105
|
+
</SafeAreaProviderCompat>
|
|
106
|
+
</GestureHandlerRootView>
|
|
107
|
+
</RoutesProvider>
|
|
97
108
|
</FlagsProvider>
|
|
98
109
|
);
|
|
99
110
|
},
|
package/src/blank-stack/types.ts
CHANGED
|
@@ -18,29 +18,7 @@ import type {
|
|
|
18
18
|
ScreenInterpolationProps,
|
|
19
19
|
} from "../shared/types/animation.types";
|
|
20
20
|
|
|
21
|
-
export type BlankStackNavigationEventMap = {
|
|
22
|
-
/**
|
|
23
|
-
* Event which fires when a transition animation starts.
|
|
24
|
-
*/
|
|
25
|
-
transitionStart: { data: { closing: boolean } };
|
|
26
|
-
/**
|
|
27
|
-
* Event which fires when a transition animation ends.
|
|
28
|
-
*/
|
|
29
|
-
transitionEnd: { data: { closing: boolean } };
|
|
30
|
-
/**
|
|
31
|
-
* Event which fires when a swipe back is canceled on iOS.
|
|
32
|
-
*/
|
|
33
|
-
gestureCancel: { data: undefined };
|
|
34
|
-
/**
|
|
35
|
-
* Event which fires when screen is in sheet presentation & it's detent changes.
|
|
36
|
-
*
|
|
37
|
-
* In payload it caries two fields:
|
|
38
|
-
*
|
|
39
|
-
* * `index` - current detent index in the `sheetAllowedDetents` array,
|
|
40
|
-
* * `stable` - on Android `false` value means that the user is dragging the sheet or it is settling; on iOS it is always `true`.
|
|
41
|
-
*/
|
|
42
|
-
sheetDetentChange: { data: { index: number; stable: boolean } };
|
|
43
|
-
};
|
|
21
|
+
export type BlankStackNavigationEventMap = {};
|
|
44
22
|
|
|
45
23
|
export type BlankStackNavigationProp<
|
|
46
24
|
ParamList extends ParamListBase,
|
|
@@ -132,7 +110,6 @@ export type BlankStackScreenTransitionConfig = ScreenTransitionConfig & {
|
|
|
132
110
|
* Whether to detach the previous screen from the view hierarchy to save memory.
|
|
133
111
|
* Set it to `false` if you need the previous screen to be seen through the active screen.
|
|
134
112
|
* Only applicable if `detachInactiveScreens` isn't set to `false`.
|
|
135
|
-
* Defaults to `false` for the last screen for modals, otherwise `true`.
|
|
136
113
|
*/
|
|
137
114
|
detachPreviousScreen?: boolean;
|
|
138
115
|
};
|
|
@@ -33,7 +33,8 @@ import {
|
|
|
33
33
|
ScreenStack,
|
|
34
34
|
ScreenStackItem,
|
|
35
35
|
} from "react-native-screens";
|
|
36
|
-
import { NativeStackScreenLifecycleController } from "../../shared/components/controllers/
|
|
36
|
+
import { NativeStackScreenLifecycleController } from "../../shared/components/controllers/native-stack-lifecycle";
|
|
37
|
+
import { RoutesProvider } from "../../shared/providers/routes.provider";
|
|
37
38
|
import { ScreenTransitionProvider } from "../../shared/providers/screen-transition.provider";
|
|
38
39
|
import type {
|
|
39
40
|
NativeStackDescriptor,
|
|
@@ -515,118 +516,126 @@ export function NativeStackView({
|
|
|
515
516
|
|
|
516
517
|
const routes = state.routes.concat(state.preloadedRoutes);
|
|
517
518
|
|
|
519
|
+
// Memoize route keys array for RoutesProvider
|
|
520
|
+
const routeKeys = React.useMemo(
|
|
521
|
+
() => routes.map((route) => route.key),
|
|
522
|
+
[routes],
|
|
523
|
+
);
|
|
524
|
+
|
|
518
525
|
return (
|
|
519
|
-
<
|
|
520
|
-
<
|
|
521
|
-
<
|
|
522
|
-
{
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
526
|
+
<RoutesProvider routeKeys={routeKeys}>
|
|
527
|
+
<GestureHandlerRootView>
|
|
528
|
+
<SafeAreaProviderCompat>
|
|
529
|
+
<ScreenStack style={styles.container}>
|
|
530
|
+
{routes.map((route, index) => {
|
|
531
|
+
const descriptor =
|
|
532
|
+
descriptors[route.key] ?? preloadedDescriptors[route.key];
|
|
533
|
+
const isFocused = state.index === index;
|
|
534
|
+
const isBelowFocused = state.index - 1 === index;
|
|
535
|
+
const previousKey = state.routes[index - 1]?.key;
|
|
536
|
+
const nextKey = state.routes[index + 1]?.key;
|
|
537
|
+
const previousDescriptor = previousKey
|
|
538
|
+
? descriptors[previousKey]
|
|
539
|
+
: undefined;
|
|
540
|
+
const nextDescriptor = nextKey ? descriptors[nextKey] : undefined;
|
|
541
|
+
|
|
542
|
+
const isModal = modalRouteKeys.includes(route.key);
|
|
543
|
+
|
|
544
|
+
const isPreloaded =
|
|
545
|
+
preloadedDescriptors[route.key] !== undefined &&
|
|
546
|
+
descriptors[route.key] === undefined;
|
|
547
|
+
|
|
548
|
+
// On Fabric, when screen is frozen, animated and reanimated values are not updated
|
|
549
|
+
// due to component being unmounted. To avoid this, we don't freeze the previous screen there
|
|
550
|
+
const shouldFreeze = isFabric()
|
|
551
|
+
? !isPreloaded && !isFocused && !isBelowFocused
|
|
552
|
+
: !isPreloaded && !isFocused;
|
|
553
|
+
|
|
554
|
+
return (
|
|
555
|
+
<SceneView
|
|
556
|
+
key={route.key}
|
|
557
|
+
index={index}
|
|
558
|
+
focused={isFocused}
|
|
559
|
+
shouldFreeze={shouldFreeze}
|
|
560
|
+
descriptor={descriptor}
|
|
561
|
+
previousDescriptor={previousDescriptor}
|
|
562
|
+
nextDescriptor={nextDescriptor}
|
|
563
|
+
isPresentationModal={isModal}
|
|
564
|
+
isPreloaded={isPreloaded}
|
|
565
|
+
onWillDisappear={() => {
|
|
566
|
+
navigation.emit({
|
|
567
|
+
type: "transitionStart",
|
|
568
|
+
data: { closing: true },
|
|
569
|
+
target: route.key,
|
|
570
|
+
});
|
|
571
|
+
}}
|
|
572
|
+
onWillAppear={() => {
|
|
573
|
+
navigation.emit({
|
|
574
|
+
type: "transitionStart",
|
|
575
|
+
data: { closing: false },
|
|
576
|
+
target: route.key,
|
|
577
|
+
});
|
|
578
|
+
}}
|
|
579
|
+
onAppear={() => {
|
|
580
|
+
navigation.emit({
|
|
581
|
+
type: "transitionEnd",
|
|
582
|
+
data: { closing: false },
|
|
583
|
+
target: route.key,
|
|
584
|
+
});
|
|
585
|
+
}}
|
|
586
|
+
onDisappear={() => {
|
|
587
|
+
navigation.emit({
|
|
588
|
+
type: "transitionEnd",
|
|
589
|
+
data: { closing: true },
|
|
590
|
+
target: route.key,
|
|
591
|
+
});
|
|
592
|
+
}}
|
|
593
|
+
onDismissed={(event) => {
|
|
594
|
+
navigation.dispatch({
|
|
595
|
+
...StackActions.pop(event.nativeEvent.dismissCount),
|
|
596
|
+
source: route.key,
|
|
597
|
+
target: state.key,
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
setNextDismissedKey(route.key);
|
|
601
|
+
}}
|
|
602
|
+
onHeaderBackButtonClicked={() => {
|
|
603
|
+
navigation.dispatch({
|
|
604
|
+
...StackActions.pop(),
|
|
605
|
+
source: route.key,
|
|
606
|
+
target: state.key,
|
|
607
|
+
});
|
|
608
|
+
}}
|
|
609
|
+
onNativeDismissCancelled={(event) => {
|
|
610
|
+
navigation.dispatch({
|
|
611
|
+
...StackActions.pop(event.nativeEvent.dismissCount),
|
|
612
|
+
source: route.key,
|
|
613
|
+
target: state.key,
|
|
614
|
+
});
|
|
615
|
+
}}
|
|
616
|
+
onGestureCancel={() => {
|
|
617
|
+
navigation.emit({
|
|
618
|
+
type: "gestureCancel",
|
|
619
|
+
target: route.key,
|
|
620
|
+
});
|
|
621
|
+
}}
|
|
622
|
+
onSheetDetentChanged={(event) => {
|
|
623
|
+
navigation.emit({
|
|
624
|
+
type: "sheetDetentChange",
|
|
625
|
+
target: route.key,
|
|
626
|
+
data: {
|
|
627
|
+
index: event.nativeEvent.index,
|
|
628
|
+
stable: event.nativeEvent.isStable,
|
|
629
|
+
},
|
|
630
|
+
});
|
|
631
|
+
}}
|
|
632
|
+
/>
|
|
633
|
+
);
|
|
634
|
+
})}
|
|
635
|
+
</ScreenStack>
|
|
636
|
+
</SafeAreaProviderCompat>
|
|
637
|
+
</GestureHandlerRootView>
|
|
638
|
+
</RoutesProvider>
|
|
630
639
|
);
|
|
631
640
|
}
|
|
632
641
|
|
|
@@ -45,12 +45,8 @@ describe("BoundStore.registerSnapshot", () => {
|
|
|
45
45
|
BoundStore.registerSnapshot("card", "screen-a", boundsA);
|
|
46
46
|
BoundStore.registerSnapshot("card", "screen-b", boundsB);
|
|
47
47
|
|
|
48
|
-
expect(BoundStore.getSnapshot("card", "screen-a")?.bounds).toEqual(
|
|
49
|
-
|
|
50
|
-
);
|
|
51
|
-
expect(BoundStore.getSnapshot("card", "screen-b")?.bounds).toEqual(
|
|
52
|
-
boundsB,
|
|
53
|
-
);
|
|
48
|
+
expect(BoundStore.getSnapshot("card", "screen-a")?.bounds).toEqual(boundsA);
|
|
49
|
+
expect(BoundStore.getSnapshot("card", "screen-b")?.bounds).toEqual(boundsB);
|
|
54
50
|
});
|
|
55
51
|
|
|
56
52
|
it("stores ancestorKeys correctly", () => {
|
|
@@ -192,13 +188,9 @@ describe("BoundStore.getSnapshot", () => {
|
|
|
192
188
|
const ancestorBounds = createBounds(200, 200, 50, 50);
|
|
193
189
|
|
|
194
190
|
// Register with ancestor that matches another screen's key
|
|
195
|
-
BoundStore.registerSnapshot(
|
|
196
|
-
"
|
|
197
|
-
|
|
198
|
-
ancestorBounds,
|
|
199
|
-
{},
|
|
200
|
-
["stack-a"],
|
|
201
|
-
);
|
|
191
|
+
BoundStore.registerSnapshot("card", "screen-a", ancestorBounds, {}, [
|
|
192
|
+
"stack-a",
|
|
193
|
+
]);
|
|
202
194
|
BoundStore.registerSnapshot("card", "stack-a", directBounds);
|
|
203
195
|
|
|
204
196
|
// Direct match should win
|
|
@@ -242,32 +234,23 @@ describe("BoundStore.getActiveLink", () => {
|
|
|
242
234
|
|
|
243
235
|
// Query from source screen = closing (going back)
|
|
244
236
|
const linkFromSource = BoundStore.getActiveLink("card", "screen-a");
|
|
245
|
-
expect(linkFromSource?.isClosing).toBe(true);
|
|
246
237
|
expect(linkFromSource?.source.screenKey).toBe("screen-a");
|
|
247
238
|
|
|
248
239
|
// Query from destination screen = opening
|
|
249
240
|
const linkFromDest = BoundStore.getActiveLink("card", "screen-b");
|
|
250
|
-
expect(linkFromDest?.isClosing).toBe(false);
|
|
251
241
|
expect(linkFromDest?.destination?.screenKey).toBe("screen-b");
|
|
252
242
|
});
|
|
253
243
|
|
|
254
244
|
it("ancestor matching works in link lookup", () => {
|
|
255
245
|
const ancestors = ["stack-a"];
|
|
256
246
|
|
|
257
|
-
BoundStore.setLinkSource(
|
|
258
|
-
"card",
|
|
259
|
-
"screen-a",
|
|
260
|
-
createBounds(),
|
|
261
|
-
{},
|
|
262
|
-
ancestors,
|
|
263
|
-
);
|
|
247
|
+
BoundStore.setLinkSource("card", "screen-a", createBounds(), {}, ancestors);
|
|
264
248
|
BoundStore.setLinkDestination("card", "screen-b", createBounds());
|
|
265
249
|
|
|
266
250
|
// Query by ancestor key (matches source)
|
|
267
251
|
const link = BoundStore.getActiveLink("card", "stack-a");
|
|
268
252
|
expect(link).not.toBeNull();
|
|
269
253
|
expect(link?.source.screenKey).toBe("screen-a");
|
|
270
|
-
expect(link?.isClosing).toBe(true); // Ancestor of source = closing
|
|
271
254
|
});
|
|
272
255
|
|
|
273
256
|
it("returns null when screenKey does not match any link", () => {
|
|
@@ -296,13 +279,11 @@ describe("Scenario: Simple push/pop navigation", () => {
|
|
|
296
279
|
|
|
297
280
|
// Verify link is complete - query from destination (opening)
|
|
298
281
|
const openingLink = BoundStore.getActiveLink("card", "screen-b");
|
|
299
|
-
expect(openingLink?.isClosing).toBe(false);
|
|
300
282
|
expect(openingLink?.source.bounds).toEqual(srcBounds);
|
|
301
283
|
expect(openingLink?.destination?.bounds).toEqual(dstBounds);
|
|
302
284
|
|
|
303
285
|
// 3. Query from source (closing - going back)
|
|
304
286
|
const closingLink = BoundStore.getActiveLink("card", "screen-a");
|
|
305
|
-
expect(closingLink?.isClosing).toBe(true);
|
|
306
287
|
expect(closingLink?.source.screenKey).toBe("screen-a");
|
|
307
288
|
expect(closingLink?.destination?.screenKey).toBe("screen-b");
|
|
308
289
|
});
|
|
@@ -355,19 +336,14 @@ describe("Scenario: Nested navigator with ancestor keys", () => {
|
|
|
355
336
|
|
|
356
337
|
it("getActiveLink respects ancestor chain", () => {
|
|
357
338
|
// Navigation from Stack A to detail screen
|
|
358
|
-
BoundStore.setLinkSource(
|
|
359
|
-
"
|
|
360
|
-
|
|
361
|
-
createBounds(10, 10),
|
|
362
|
-
{},
|
|
363
|
-
["stack-a"],
|
|
364
|
-
);
|
|
339
|
+
BoundStore.setLinkSource("profile", "a1", createBounds(10, 10), {}, [
|
|
340
|
+
"stack-a",
|
|
341
|
+
]);
|
|
365
342
|
BoundStore.setLinkDestination("profile", "detail", createBounds(0, 0));
|
|
366
343
|
|
|
367
344
|
// Query by ancestor should find the link
|
|
368
345
|
const link = BoundStore.getActiveLink("profile", "stack-a");
|
|
369
346
|
expect(link?.source.screenKey).toBe("a1");
|
|
370
|
-
expect(link?.isClosing).toBe(true); // Ancestor matches source = closing
|
|
371
347
|
});
|
|
372
348
|
});
|
|
373
349
|
|
|
@@ -388,12 +364,10 @@ describe("Scenario: Rapid navigation A → B → C → pop → pop", () => {
|
|
|
388
364
|
|
|
389
365
|
// Query from C (destination of B→C) = opening
|
|
390
366
|
const fromC = BoundStore.getActiveLink("card", "screen-c");
|
|
391
|
-
expect(fromC?.isClosing).toBe(false);
|
|
392
367
|
expect(fromC?.destination?.screenKey).toBe("screen-c");
|
|
393
368
|
|
|
394
369
|
// Query from B - B is source of B→C link, so isClosing=true
|
|
395
370
|
const fromB = BoundStore.getActiveLink("card", "screen-b");
|
|
396
|
-
expect(fromB?.isClosing).toBe(true);
|
|
397
371
|
expect(fromB?.source.screenKey).toBe("screen-b");
|
|
398
372
|
});
|
|
399
373
|
});
|
|
@@ -401,7 +375,11 @@ describe("Scenario: Rapid navigation A → B → C → pop → pop", () => {
|
|
|
401
375
|
describe("Scenario: Global bounds (fullscreen target)", () => {
|
|
402
376
|
it("getActiveLink with no screenKey returns most recent for fullscreen", () => {
|
|
403
377
|
// Source exists, destination will be fullscreen (no specific screenKey needed)
|
|
404
|
-
BoundStore.setLinkSource(
|
|
378
|
+
BoundStore.setLinkSource(
|
|
379
|
+
"image",
|
|
380
|
+
"gallery",
|
|
381
|
+
createBounds(50, 50, 100, 100),
|
|
382
|
+
);
|
|
405
383
|
BoundStore.setLinkDestination(
|
|
406
384
|
"image",
|
|
407
385
|
"fullscreen-viewer",
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { useLayoutEffect } from "react";
|
|
2
|
+
import { useAnimatedReaction } from "react-native-reanimated";
|
|
3
|
+
import type { BlankStackDescriptor } from "../../../blank-stack/types";
|
|
4
|
+
import { useStackNavigationContext } from "../../../blank-stack/utils/with-stack-navigation";
|
|
5
|
+
import useStableCallback from "../../hooks/use-stable-callback";
|
|
6
|
+
import { useKeys } from "../../providers/keys.provider";
|
|
7
|
+
import { AnimationStore } from "../../stores/animation.store";
|
|
8
|
+
import { startScreenTransition } from "../../utils/animation/start-screen-transition";
|
|
9
|
+
import { resetStoresForScreen } from "../../utils/reset-stores-for-screen";
|
|
10
|
+
|
|
11
|
+
export interface Props {
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Lifecycle controller built out for Blank Stack implementation.
|
|
17
|
+
*/
|
|
18
|
+
export const BlankStackScreenLifecycleController = ({ children }: Props) => {
|
|
19
|
+
const { current } = useKeys<BlankStackDescriptor>();
|
|
20
|
+
const { handleCloseRoute, closingRouteKeysShared } =
|
|
21
|
+
useStackNavigationContext();
|
|
22
|
+
|
|
23
|
+
const animations = AnimationStore.getAll(current.route.key);
|
|
24
|
+
|
|
25
|
+
const handleInitialize = useStableCallback(() => {
|
|
26
|
+
startScreenTransition({
|
|
27
|
+
target: "open",
|
|
28
|
+
spec: current.options.transitionSpec,
|
|
29
|
+
animations,
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const handleCleanup = useStableCallback(() => {
|
|
34
|
+
resetStoresForScreen(current);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const handleCloseEnd = useStableCallback((finished: boolean) => {
|
|
38
|
+
if (!finished) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
handleCloseRoute({ route: current.route });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
useAnimatedReaction(
|
|
45
|
+
() => ({
|
|
46
|
+
keys: closingRouteKeysShared.value,
|
|
47
|
+
}),
|
|
48
|
+
({ keys }) => {
|
|
49
|
+
if (!keys.includes(current.route.key)) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
startScreenTransition({
|
|
54
|
+
target: "close",
|
|
55
|
+
spec: current.options.transitionSpec,
|
|
56
|
+
animations,
|
|
57
|
+
onAnimationFinish: handleCloseEnd,
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
useLayoutEffect(() => {
|
|
63
|
+
handleInitialize();
|
|
64
|
+
return () => {
|
|
65
|
+
handleCleanup();
|
|
66
|
+
};
|
|
67
|
+
}, [handleInitialize, handleCleanup]);
|
|
68
|
+
|
|
69
|
+
return children;
|
|
70
|
+
};
|