react-native-screen-transitions 3.0.0-rc.3 → 3.0.0-rc.5
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 +22 -13
- package/lib/commonjs/blank-stack/components/screens.js.map +1 -1
- package/lib/commonjs/blank-stack/components/stack-view.js +42 -39
- package/lib/commonjs/blank-stack/components/stack-view.js.map +1 -1
- package/lib/commonjs/blank-stack/utils/with-stack-navigation/index.js +11 -10
- package/lib/commonjs/blank-stack/utils/with-stack-navigation/index.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 +81 -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 +36 -20
- 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 +22 -13
- package/lib/module/blank-stack/components/screens.js.map +1 -1
- package/lib/module/blank-stack/components/stack-view.js +42 -39
- package/lib/module/blank-stack/components/stack-view.js.map +1 -1
- package/lib/module/blank-stack/utils/with-stack-navigation/index.js +11 -10
- package/lib/module/blank-stack/utils/with-stack-navigation/index.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 +74 -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 +36 -21
- 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 +1 -3
- 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/blank-stack/utils/with-stack-navigation/index.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 +10 -7
- 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 +25 -19
- package/src/blank-stack/components/stack-view.tsx +57 -53
- package/src/blank-stack/types.ts +1 -24
- package/src/blank-stack/utils/with-stack-navigation/index.tsx +17 -3
- 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 +89 -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 +49 -48
- 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
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/** biome-ignore-all lint/style/noNonNullAssertion: <Lifecycles are rendered right under the gesture provider> */
|
|
2
|
+
import { useEffect, useLayoutEffect } from "react";
|
|
3
|
+
import { useDerivedValue } from "react-native-reanimated";
|
|
4
|
+
import type { NativeStackDescriptor } from "../../../native-stack/types";
|
|
5
|
+
import { useSharedValueState } from "../../hooks/reanimated/use-shared-value-state";
|
|
6
|
+
import useStableCallback from "../../hooks/use-stable-callback";
|
|
7
|
+
import { useGestureContext } from "../../providers/gestures.provider";
|
|
8
|
+
import { useKeys } from "../../providers/keys.provider";
|
|
9
|
+
import { AnimationStore } from "../../stores/animation.store";
|
|
10
|
+
import { TRUE } from "../../types/state.types";
|
|
11
|
+
import type { Any } from "../../types/utils.types";
|
|
12
|
+
import { startScreenTransition } from "../../utils/animation/start-screen-transition";
|
|
13
|
+
import { resetStoresForScreen } from "../../utils/reset-stores-for-screen";
|
|
14
|
+
|
|
15
|
+
export interface Props {
|
|
16
|
+
children: React.ReactNode;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Lifecycle controller built out for Native Stack implementation.
|
|
21
|
+
*/
|
|
22
|
+
export const NativeStackScreenLifecycleController = ({ children }: Props) => {
|
|
23
|
+
const { current } = useKeys<NativeStackDescriptor>();
|
|
24
|
+
const { ancestorContext } = useGestureContext()!;
|
|
25
|
+
|
|
26
|
+
const isAncestorDismissingViaGesture = useSharedValueState(
|
|
27
|
+
useDerivedValue(() => {
|
|
28
|
+
"worklet";
|
|
29
|
+
return (
|
|
30
|
+
ancestorContext?.gestureAnimationValues.isDismissing?.value ?? false
|
|
31
|
+
);
|
|
32
|
+
}),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const animations = AnimationStore.getAll(current.route.key);
|
|
36
|
+
|
|
37
|
+
const handleBeforeRemove = useStableCallback((e: Any) => {
|
|
38
|
+
const isEnabled = current.options.enableTransitions;
|
|
39
|
+
|
|
40
|
+
const isFirstScreen = current.navigation.getState().index === 0;
|
|
41
|
+
|
|
42
|
+
// If transitions are disabled, or an ancestor is dismissing via gesture, or this is the first screen of the stack, reset the stores
|
|
43
|
+
if (!isEnabled || isAncestorDismissingViaGesture || isFirstScreen) {
|
|
44
|
+
animations.closing.set(TRUE);
|
|
45
|
+
resetStoresForScreen(current);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
e.preventDefault();
|
|
50
|
+
const onAnimationFinish = (finished: boolean) => {
|
|
51
|
+
if (finished) {
|
|
52
|
+
current.navigation.dispatch(e.data.action);
|
|
53
|
+
|
|
54
|
+
// we'll ensure the dispatch is complete before resetting stores
|
|
55
|
+
requestAnimationFrame(() => {
|
|
56
|
+
resetStoresForScreen(current);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
startScreenTransition({
|
|
62
|
+
target: "close",
|
|
63
|
+
spec: current.options.transitionSpec,
|
|
64
|
+
onAnimationFinish,
|
|
65
|
+
animations,
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const handleInitialize = useStableCallback(() => {
|
|
70
|
+
startScreenTransition({
|
|
71
|
+
target: "open",
|
|
72
|
+
spec: current.options.transitionSpec,
|
|
73
|
+
animations,
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
const unsubscribe = current.navigation.addListener(
|
|
79
|
+
"beforeRemove",
|
|
80
|
+
handleBeforeRemove,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
return unsubscribe;
|
|
84
|
+
}, [current.navigation, handleBeforeRemove]);
|
|
85
|
+
|
|
86
|
+
useLayoutEffect(handleInitialize, []);
|
|
87
|
+
|
|
88
|
+
return children;
|
|
89
|
+
};
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type TransitionDescriptor,
|
|
11
11
|
useKeys,
|
|
12
12
|
} from "../../providers/keys.provider";
|
|
13
|
+
import { useStackAnimationValues } from "../../providers/routes.provider";
|
|
13
14
|
import { AnimationStore } from "../../stores/animation.store";
|
|
14
15
|
import { GestureStore, type GestureStoreMap } from "../../stores/gesture.store";
|
|
15
16
|
import type {
|
|
@@ -17,7 +18,7 @@ import type {
|
|
|
17
18
|
ScreenTransitionState,
|
|
18
19
|
} from "../../types/animation.types";
|
|
19
20
|
import type { ScreenTransitionConfig } from "../../types/core.types";
|
|
20
|
-
import
|
|
21
|
+
import { computeStackProgress } from "../../utils/animation/compute-stack-progress";
|
|
21
22
|
import { derivations } from "../../utils/animation/derivations";
|
|
22
23
|
import { createBounds } from "../../utils/bounds";
|
|
23
24
|
|
|
@@ -60,10 +61,7 @@ const unwrapInto = (s: BuiltState): ScreenTransitionState => {
|
|
|
60
61
|
out.gesture.normalizedY = s.gesture.normalizedY.value;
|
|
61
62
|
out.gesture.isDismissing = s.gesture.isDismissing.value;
|
|
62
63
|
out.gesture.isDragging = s.gesture.isDragging.value;
|
|
63
|
-
out.gesture.direction = s.gesture.direction.value
|
|
64
|
-
GestureDirection,
|
|
65
|
-
"bidirectional"
|
|
66
|
-
> | null;
|
|
64
|
+
out.gesture.direction = s.gesture.direction.value;
|
|
67
65
|
|
|
68
66
|
return out;
|
|
69
67
|
};
|
|
@@ -112,6 +110,9 @@ export function _useScreenAnimation() {
|
|
|
112
110
|
const nextAnimation = useBuildScreenTransitionState(nextDescriptor);
|
|
113
111
|
const prevAnimation = useBuildScreenTransitionState(previousDescriptor);
|
|
114
112
|
|
|
113
|
+
const currentRouteKey = currentDescriptor?.route?.key;
|
|
114
|
+
const stackAnimationValues = useStackAnimationValues(currentRouteKey);
|
|
115
|
+
|
|
115
116
|
const screenInterpolatorProps = useDerivedValue<
|
|
116
117
|
Omit<ScreenInterpolationProps, "bounds">
|
|
117
118
|
>(() => {
|
|
@@ -129,17 +130,21 @@ export function _useScreenAnimation() {
|
|
|
129
130
|
? unwrapInto(currentAnimation)
|
|
130
131
|
: DEFAULT_SCREEN_TRANSITION_STATE;
|
|
131
132
|
|
|
132
|
-
const helpers = derivations({
|
|
133
|
+
const { progress, ...helpers } = derivations({
|
|
133
134
|
current,
|
|
134
135
|
next,
|
|
135
136
|
});
|
|
136
137
|
|
|
138
|
+
const stackProgress = computeStackProgress(stackAnimationValues, progress);
|
|
139
|
+
|
|
137
140
|
return {
|
|
138
141
|
layouts: { screen: dimensions },
|
|
139
142
|
insets,
|
|
140
143
|
previous,
|
|
141
144
|
current,
|
|
142
145
|
next,
|
|
146
|
+
progress,
|
|
147
|
+
stackProgress,
|
|
143
148
|
...helpers,
|
|
144
149
|
};
|
|
145
150
|
});
|
|
@@ -40,12 +40,12 @@ import useStableCallbackValue from "../use-stable-callback-value";
|
|
|
40
40
|
|
|
41
41
|
interface BuildGesturesHookProps {
|
|
42
42
|
scrollConfig: SharedValue<ScrollConfig | null>;
|
|
43
|
-
|
|
43
|
+
ancestorContext?: GestureContextType | null;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export const useBuildGestures = ({
|
|
47
47
|
scrollConfig,
|
|
48
|
-
|
|
48
|
+
ancestorContext,
|
|
49
49
|
}: BuildGesturesHookProps): {
|
|
50
50
|
panGesture: GestureType;
|
|
51
51
|
nativeGesture: GestureType;
|
|
@@ -96,8 +96,8 @@ export const useBuildGestures = ({
|
|
|
96
96
|
|
|
97
97
|
const handleDismiss = useCallback(() => {
|
|
98
98
|
// If an ancestor navigator is already dismissing, skip this dismiss to
|
|
99
|
-
// avoid racing with the
|
|
100
|
-
if (
|
|
99
|
+
// avoid racing with the ancestor
|
|
100
|
+
if (ancestorContext?.gestureAnimationValues.isDismissing?.value) {
|
|
101
101
|
return;
|
|
102
102
|
}
|
|
103
103
|
|
|
@@ -116,7 +116,7 @@ export const useBuildGestures = ({
|
|
|
116
116
|
source: current.route.key,
|
|
117
117
|
target: state.key,
|
|
118
118
|
});
|
|
119
|
-
}, [current,
|
|
119
|
+
}, [current, ancestorContext]);
|
|
120
120
|
|
|
121
121
|
const onTouchesDown = useStableCallbackValue((e: GestureTouchEvent) => {
|
|
122
122
|
"worklet";
|
|
@@ -130,7 +130,7 @@ export const useBuildGestures = ({
|
|
|
130
130
|
"worklet";
|
|
131
131
|
|
|
132
132
|
// If an ancestor navigator is already dismissing via gesture, block new gestures here.
|
|
133
|
-
if (
|
|
133
|
+
if (ancestorContext?.gestureAnimationValues.isDismissing?.value) {
|
|
134
134
|
gestureOffsetState.value = GestureOffsetState.FAILED;
|
|
135
135
|
manager.fail();
|
|
136
136
|
return;
|
|
@@ -338,6 +338,11 @@ export const useBuildGestures = ({
|
|
|
338
338
|
.onEnd(onEnd)
|
|
339
339
|
.blocksExternalGesture(nativeGesture);
|
|
340
340
|
|
|
341
|
+
// Allow ancestors to block child native gestures
|
|
342
|
+
if (ancestorContext?.panGesture && nativeGesture) {
|
|
343
|
+
ancestorContext.panGesture.blocksExternalGesture(nativeGesture);
|
|
344
|
+
}
|
|
345
|
+
|
|
341
346
|
return {
|
|
342
347
|
panGesture,
|
|
343
348
|
nativeGesture,
|
|
@@ -351,5 +356,6 @@ export const useBuildGestures = ({
|
|
|
351
356
|
onUpdate,
|
|
352
357
|
onEnd,
|
|
353
358
|
gestureAnimationValues,
|
|
359
|
+
ancestorContext,
|
|
354
360
|
]);
|
|
355
361
|
};
|