Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e535d57
refactor(bounds): centralize boundary coordination
eds2002 Aug 10, 2026
50be574
fix(bounds): stabilize nested boundary lifecycle
eds2002 Aug 10, 2026
b382697
fix(bounds): stabilize nested portal ownership
eds2002 Aug 10, 2026
ef66896
fix(bounds): classify nested handoff topology
eds2002 Aug 10, 2026
5346826
chore: examples
eds2002 Aug 10, 2026
5e9ecc2
fix(bounds): refresh endpoints before animation
eds2002 Aug 11, 2026
6185c8e
fix(bounds): guard teardown pair cleanup
eds2002 Aug 11, 2026
c7f4c45
fix(activity): settle using visual progress
eds2002 Aug 11, 2026
f6a5ca1
perf(animation): pause inactive interpolator updates
eds2002 Aug 11, 2026
c1d9c56
feat(examples): add repeated bounds stacking flow
eds2002 Aug 11, 2026
8a45b0b
docs(changelog): add v3.13 release notes
eds2002 Aug 11, 2026
76b884f
fix(gestures): preserve locked snap transitions
eds2002 Aug 12, 2026
117084c
docs(changelog): document locked snap fixes
eds2002 Aug 12, 2026
97c99a0
chore(release): v3.13.0-beta.0
eds2002 Aug 12, 2026
493bcd1
refactor(topology): centralize screen relationships
eds2002 Aug 13, 2026
ee7d97a
perf(activity): freeze hidden screens
eds2002 Aug 14, 2026
63e76a8
perf(animation): derive stack progress lazily
eds2002 Aug 15, 2026
0eebe0e
docs(changelog): finalize v3.13 notes
eds2002 Aug 15, 2026
b86e7f2
perf(core): simplify retained screen coordination
eds2002 Aug 15, 2026
25d257b
fix(bounds): keep unselected boundaries in screen
eds2002 Aug 16, 2026
1fbf0b7
chore: yeah
eds2002 Aug 16, 2026
849976a
fix(overlay): hand off overlapping transition drivers
eds2002 Aug 17, 2026
2cfc843
chore(release): v3.13.0-beta.1
eds2002 Aug 17, 2026
36dd8bc
docs: require teleport 1.2.0 as minimal supported version (#149)
kirillzyusko Aug 25, 2026
315b730
fix(boundary): portal target content through handoff host
eds2002 Aug 27, 2026
7863659
fix(boundary): retain handoffs across pair changes
eds2002 Aug 27, 2026
6c4b092
Merge branch 'main' into fix/global-bounds
eds2002 Aug 27, 2026
d76fbf7
chore(release): v3.13.0
eds2002 Aug 27, 2026
04357ad
fix(release): keep bun lockfile version in sync
eds2002 Aug 27, 2026
3cb60b0
fix(docs): align React versions for prerendering
eds2002 Aug 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .release-it.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"before:git:release": "node ../../.github/scripts/release-notes.mjs ${version} --check",
"after:bump": [
"node ../../.github/scripts/sync-bun-lock-version.mjs",
"git add ../../bun.lock",
"bun run --cwd ../.. build",
"npm pack --dry-run"
]
Expand Down
55 changes: 55 additions & 0 deletions apps/e2e/.maestro/inactive-hide-thaw.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
appId: "com.eds2002.e2e"
name: "Inactive hide thaw restores gestures"
tags:
- maestro
- regression
---
- launchApp:
clearState: true
- openLink: "e2e:///blank-stack/inactive-behavior"
- assertVisible:
id: "blank-inactive-behavior-hide"
- tapOn:
id: "blank-inactive-behavior-hide"
- assertVisible:
id: "inactive-behavior-hide-push-next"

# Control: the same screen gesture works before it has ever been frozen.
- swipe:
start: "2%, 50%"
end: "99%, 50%"
duration: 250
- waitForAnimationToEnd
- assertVisible:
id: "blank-inactive-behavior-hide"
- tapOn:
id: "blank-inactive-behavior-hide"
- assertVisible:
id: "inactive-behavior-hide-push-next"

# Push far enough for the original route to become inactive, frozen, and hidden.
- tapOn:
id: "inactive-behavior-hide-push-next"
- waitForAnimationToEnd
- tapOn:
id: "inactive-behavior-hide-push-next"
- waitForAnimationToEnd

# Pop both newer routes so the original route thaws and becomes active again.
- tapOn:
id: "inactive-behavior-hide-back"
- waitForAnimationToEnd
- tapOn:
id: "inactive-behavior-hide-back"
- waitForAnimationToEnd

# The gesture detector on the previously hidden route must reattach.
- swipe:
start: "2%, 50%"
end: "99%, 50%"
duration: 250
- waitForAnimationToEnd
- assertVisible:
id: "blank-inactive-behavior-hide"
- assertNotVisible:
id: "inactive-behavior-hide-push-next"
4 changes: 4 additions & 0 deletions apps/e2e/app/[stackType]/bounds/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ export default function BoundsLayout() {
return (
<StackNavigator screenOptions={navigatorScreenOptions}>
<StackNavigator.Screen name="index" />
<StackNavigator.Screen name="example-1" options={{ ...IOSSlide() }} />
<StackNavigator.Screen name="example-2" options={{ ...IOSSlide() }} />
<StackNavigator.Screen name="example-3" options={{ ...IOSSlide() }} />
<StackNavigator.Screen name="style-id" options={{ ...IOSSlide() }} />
<StackNavigator.Screen name="zoom" options={{ ...IOSSlide() }} />
<StackNavigator.Screen name="sync" options={{ ...IOSSlide() }} />
Expand All @@ -23,6 +26,7 @@ export default function BoundsLayout() {
name="handoff-multiflow"
options={{ ...IOSSlide() }}
/>
<StackNavigator.Screen name="stacking" options={{ ...IOSSlide() }} />
</StackNavigator>
);
}
30 changes: 30 additions & 0 deletions apps/e2e/app/[stackType]/bounds/example-1/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Transition from "react-native-screen-transitions";
import { useResolvedStackType } from "@/components/stack-examples/stack-routing";
import { BlankStack } from "@/layouts/blank-stack";
import { Stack } from "@/layouts/stack";
import { nestedBoundaryZoomInterpolator } from "../nested-boundary-example";

export default function NestedSourceExampleLayout() {
const stackType = useResolvedStackType();
const StackNavigator = stackType === "native-stack" ? Stack : BlankStack;
const screenOptions =
stackType === "native-stack" ? { enableTransitions: true } : undefined;

return (
<StackNavigator screenOptions={screenOptions}>
<StackNavigator.Screen name="nested" />
<StackNavigator.Screen
name="destination"
options={{
gestureEnabled: true,
gestureDirection: ["bidirectional", "pinch-in"],
gestureProgressMode: "freeform",
gestureReleaseVelocityScale: 1.6,
navigationMaskEnabled: true,
screenStyleInterpolator: nestedBoundaryZoomInterpolator,
transitionSpec: Transition.Specs.Zoom,
}}
/>
</StackNavigator>
);
}
5 changes: 5 additions & 0 deletions apps/e2e/app/[stackType]/bounds/example-1/destination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { NestedBoundaryDestination } from "../nested-boundary-example";

export default function ParentDestinationScreen() {
return <NestedBoundaryDestination title="Parent destination" />;
}
16 changes: 16 additions & 0 deletions apps/e2e/app/[stackType]/bounds/example-1/nested/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useResolvedStackType } from "@/components/stack-examples/stack-routing";
import { BlankStack } from "@/layouts/blank-stack";
import { Stack } from "@/layouts/stack";

export default function NestedSourceLevelOneLayout() {
const stackType = useResolvedStackType();
const StackNavigator = stackType === "native-stack" ? Stack : BlankStack;
const screenOptions =
stackType === "native-stack" ? { enableTransitions: true } : undefined;

return (
<StackNavigator screenOptions={screenOptions}>
<StackNavigator.Screen name="nested" />
</StackNavigator>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useResolvedStackType } from "@/components/stack-examples/stack-routing";
import { BlankStack } from "@/layouts/blank-stack";
import { Stack } from "@/layouts/stack";

export default function NestedSourceLevelTwoLayout() {
const stackType = useResolvedStackType();
const StackNavigator = stackType === "native-stack" ? Stack : BlankStack;
const screenOptions =
stackType === "native-stack" ? { enableTransitions: true } : undefined;

return (
<StackNavigator screenOptions={screenOptions}>
<StackNavigator.Screen name="nested" />
</StackNavigator>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useResolvedStackType } from "@/components/stack-examples/stack-routing";
import { BlankStack } from "@/layouts/blank-stack";
import { Stack } from "@/layouts/stack";

export default function NestedSourceLevelThreeLayout() {
const stackType = useResolvedStackType();
const StackNavigator = stackType === "native-stack" ? Stack : BlankStack;
const screenOptions =
stackType === "native-stack" ? { enableTransitions: true } : undefined;

return (
<StackNavigator screenOptions={screenOptions}>
<StackNavigator.Screen name="source" />
</StackNavigator>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { NestedBoundarySource } from "../../../../nested-boundary-example";

export default function DeeplyNestedSourceScreen() {
return (
<NestedBoundarySource
title="Deeply nested source"
destination="bounds/example-1/destination"
/>
);
}
30 changes: 30 additions & 0 deletions apps/e2e/app/[stackType]/bounds/example-2/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Transition from "react-native-screen-transitions";
import { useResolvedStackType } from "@/components/stack-examples/stack-routing";
import { BlankStack } from "@/layouts/blank-stack";
import { Stack } from "@/layouts/stack";
import { nestedBoundaryZoomInterpolator } from "../nested-boundary-example";

export default function NestedDestinationExampleLayout() {
const stackType = useResolvedStackType();
const StackNavigator = stackType === "native-stack" ? Stack : BlankStack;
const screenOptions =
stackType === "native-stack" ? { enableTransitions: true } : undefined;

return (
<StackNavigator screenOptions={screenOptions}>
<StackNavigator.Screen name="source" />
<StackNavigator.Screen
name="nested"
options={{
gestureEnabled: true,
gestureDirection: ["bidirectional", "pinch-in"],
gestureProgressMode: "freeform",
gestureReleaseVelocityScale: 1.6,
navigationMaskEnabled: true,
screenStyleInterpolator: nestedBoundaryZoomInterpolator,
transitionSpec: Transition.Specs.Zoom,
}}
/>
</StackNavigator>
);
}
16 changes: 16 additions & 0 deletions apps/e2e/app/[stackType]/bounds/example-2/nested/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useResolvedStackType } from "@/components/stack-examples/stack-routing";
import { BlankStack } from "@/layouts/blank-stack";
import { Stack } from "@/layouts/stack";

export default function NestedDestinationLevelOneLayout() {
const stackType = useResolvedStackType();
const StackNavigator = stackType === "native-stack" ? Stack : BlankStack;
const screenOptions =
stackType === "native-stack" ? { enableTransitions: true } : undefined;

return (
<StackNavigator screenOptions={screenOptions}>
<StackNavigator.Screen name="nested" />
</StackNavigator>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useResolvedStackType } from "@/components/stack-examples/stack-routing";
import { BlankStack } from "@/layouts/blank-stack";
import { Stack } from "@/layouts/stack";

export default function NestedDestinationLevelTwoLayout() {
const stackType = useResolvedStackType();
const StackNavigator = stackType === "native-stack" ? Stack : BlankStack;
const screenOptions =
stackType === "native-stack" ? { enableTransitions: true } : undefined;

return (
<StackNavigator screenOptions={screenOptions}>
<StackNavigator.Screen name="nested" />
</StackNavigator>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useResolvedStackType } from "@/components/stack-examples/stack-routing";
import { BlankStack } from "@/layouts/blank-stack";
import { Stack } from "@/layouts/stack";

export default function NestedDestinationLevelThreeLayout() {
const stackType = useResolvedStackType();
const StackNavigator = stackType === "native-stack" ? Stack : BlankStack;
const screenOptions =
stackType === "native-stack" ? { enableTransitions: true } : undefined;

return (
<StackNavigator screenOptions={screenOptions}>
<StackNavigator.Screen name="destination" />
</StackNavigator>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { NestedBoundaryDestination } from "../../../../nested-boundary-example";

export default function DeeplyNestedDestinationScreen() {
return <NestedBoundaryDestination title="Deeply nested destination" />;
}
10 changes: 10 additions & 0 deletions apps/e2e/app/[stackType]/bounds/example-2/source.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { NestedBoundarySource } from "../nested-boundary-example";

export default function ParentSourceScreen() {
return (
<NestedBoundarySource
title="Parent source"
destination="bounds/example-2/nested/nested/nested/destination"
/>
);
}
30 changes: 30 additions & 0 deletions apps/e2e/app/[stackType]/bounds/example-3/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Transition from "react-native-screen-transitions";
import { useResolvedStackType } from "@/components/stack-examples/stack-routing";
import { BlankStack } from "@/layouts/blank-stack";
import { Stack } from "@/layouts/stack";
import { nestedBoundaryZoomInterpolator } from "../nested-boundary-example";

export default function NeighboringNestedExampleLayout() {
const stackType = useResolvedStackType();
const StackNavigator = stackType === "native-stack" ? Stack : BlankStack;
const screenOptions =
stackType === "native-stack" ? { enableTransitions: true } : undefined;

return (
<StackNavigator screenOptions={screenOptions}>
<StackNavigator.Screen name="nested-1" />
<StackNavigator.Screen
name="nested-2"
options={{
gestureEnabled: true,
gestureDirection: ["bidirectional", "pinch-in"],
gestureProgressMode: "freeform",
gestureReleaseVelocityScale: 1.6,
navigationMaskEnabled: true,
screenStyleInterpolator: nestedBoundaryZoomInterpolator,
transitionSpec: Transition.Specs.Zoom,
}}
/>
</StackNavigator>
);
}
16 changes: 16 additions & 0 deletions apps/e2e/app/[stackType]/bounds/example-3/nested-1/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useResolvedStackType } from "@/components/stack-examples/stack-routing";
import { BlankStack } from "@/layouts/blank-stack";
import { Stack } from "@/layouts/stack";

export default function NeighboringNestedSourceLayout() {
const stackType = useResolvedStackType();
const StackNavigator = stackType === "native-stack" ? Stack : BlankStack;
const screenOptions =
stackType === "native-stack" ? { enableTransitions: true } : undefined;

return (
<StackNavigator screenOptions={screenOptions}>
<StackNavigator.Screen name="source" />
</StackNavigator>
);
}
10 changes: 10 additions & 0 deletions apps/e2e/app/[stackType]/bounds/example-3/nested-1/source.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { NestedBoundarySource } from "../../nested-boundary-example";

export default function NeighboringNestedSourceScreen() {
return (
<NestedBoundarySource
title="Neighboring nested source"
destination="bounds/example-3/nested-2/destination"
/>
);
}
16 changes: 16 additions & 0 deletions apps/e2e/app/[stackType]/bounds/example-3/nested-2/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useResolvedStackType } from "@/components/stack-examples/stack-routing";
import { BlankStack } from "@/layouts/blank-stack";
import { Stack } from "@/layouts/stack";

export default function NeighboringNestedDestinationLayout() {
const stackType = useResolvedStackType();
const StackNavigator = stackType === "native-stack" ? Stack : BlankStack;
const screenOptions =
stackType === "native-stack" ? { enableTransitions: true } : undefined;

return (
<StackNavigator screenOptions={screenOptions}>
<StackNavigator.Screen name="destination" />
</StackNavigator>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { NestedBoundaryDestination } from "../../nested-boundary-example";

export default function NeighboringNestedDestinationScreen() {
return <NestedBoundaryDestination title="Neighboring nested destination" />;
}
24 changes: 24 additions & 0 deletions apps/e2e/app/[stackType]/bounds/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,24 @@ import {
import { ListScreen } from "@/components/ui";

const BOUNDS_EXAMPLES = [
{
id: "example-1/nested/nested/nested/source",
title: "Deeply nested source → parent destination",
description:
"Runtime group, bound target, and clipping controls across three nested stacks",
},
{
id: "example-2/source",
title: "Parent source → deeply nested destination",
description:
"Runtime group, bound target, and clipping controls into three nested stacks",
},
{
id: "example-3/nested-1/source",
title: "Nested source → neighboring nested destination",
description:
"Runtime group, bound target, and clipping controls across sibling nested stacks",
},
{
id: "style-id",
title: ".reveal()",
Expand Down Expand Up @@ -35,6 +53,12 @@ const BOUNDS_EXAMPLES = [
title: "Handoff multiflow",
description: "One payload handed across a multi-screen push and pop flow",
},
{
id: "stacking",
title: "Repeated route stacking",
description:
"Alternating route pushes with independently selectable card and button sources",
},
];

export default function BoundsHubIndex() {
Expand Down
Loading
Loading