| 1 | import type { NavigationState } from "@react-navigation/native"; |
| 2 | |
| 3 | export type AdaptiveNavigationAction = "push" | "replace" | "set-params"; |
| 4 | |
| 5 | const BASE_THREAD_ROUTE_PATTERN = /^\/threads\/[^/]+\/[^/]+\/?$/; |
| 6 | |
| 7 | export function isBaseThreadRoute(pathname: string): boolean { |
| 8 | return BASE_THREAD_ROUTE_PATTERN.test(pathname); |
| 9 | } |
| 10 | |
| 11 | /** |
| 12 | * A persistent sidebar selects a peer destination in place. A compact list |
| 13 | * drills into a new destination so the native back stack remains available. |
| 14 | * From Home the selection pushes (never replaces) so Home stays beneath the |
| 15 | * thread — collapsing back to a compact width keeps a sane back stack. |
| 16 | */ |
| 17 | export function resolveThreadSelectionNavigationAction(input: { |
| 18 | readonly usesSplitView: boolean; |
| 19 | readonly pathname: string; |
| 20 | }): AdaptiveNavigationAction { |
| 21 | if (!input.usesSplitView || input.pathname === "/") { |
| 22 | return "push"; |
| 23 | } |
| 24 | |
| 25 | return isBaseThreadRoute(input.pathname) ? "set-params" : "replace"; |
| 26 | } |
| 27 | |
| 28 | /** Dismiss sheets and select their underlying workspace destination in one stack update. */ |
| 29 | export function resolveThreadSelectionOverlayState(input: { |
| 30 | readonly state: NavigationState | undefined; |
| 31 | readonly workspaceRouteKey: string | undefined; |
| 32 | readonly action: AdaptiveNavigationAction; |
| 33 | readonly params: ReactNavigation.RootParamList["Thread"]; |
| 34 | }) { |
| 35 | if (input.state === undefined) return null; |
| 36 | const workspaceIndex = input.state.routes.findIndex( |
| 37 | (route) => route.key === input.workspaceRouteKey, |
| 38 | ); |
| 39 | if (workspaceIndex < 0 || workspaceIndex >= input.state.index) return null; |
| 40 | |
| 41 | const workspaceRoute = input.state.routes[workspaceIndex]; |
| 42 | const routes = input.state.routes.slice(0, workspaceIndex + (input.action === "push" ? 1 : 0)); |
| 43 | return { |
| 44 | ...input.state, |
| 45 | index: routes.length, |
| 46 | routes: [ |
| 47 | ...routes, |
| 48 | input.action === "set-params" && workspaceRoute?.name === "Thread" |
| 49 | ? { ...workspaceRoute, params: { ...workspaceRoute.params, ...input.params } } |
| 50 | : { name: "Thread", params: input.params }, |
| 51 | ], |
| 52 | }; |
| 53 | } |
| 54 | |
| 55 | /** |
| 56 | * On regular-width layouts, the file browser and preview occupy one workspace |
| 57 | * destination. Replacing the browser route keeps a single back step to chat. |
| 58 | * Compact layouts retain the browser as the previous stack screen. |
| 59 | */ |
| 60 | export function resolveFileSelectionNavigationAction(input: { |
| 61 | readonly hasPersistentFileInspector: boolean; |
| 62 | }): AdaptiveNavigationAction { |
| 63 | return input.hasPersistentFileInspector ? "replace" : "push"; |
| 64 | } |