Separate out into files

This commit is contained in:
2026-07-22 11:37:46 +00:00
parent 6aed7606ad
commit 5a514906eb
11 changed files with 906 additions and 657 deletions

View File

@@ -0,0 +1,117 @@
import type { App } from "vue";
import {
isNavigationFailure,
loadRouteLocation,
routeLocationKey,
START_LOCATION,
type NavigationFailure,
type RouteLocationNormalizedLoaded,
type RouteLocationRaw,
type RouteLocationResolved,
type Router,
} from "vue-router";
import type { NativeTransaction, NativeViewEntry } from "../types";
import type { NativeHistoryLedger } from "./history-ledger";
export type NavigationResult = NavigationFailure | void | true;
export function isFailedNavigation(value: unknown) {
return Boolean(value && isNavigationFailure(value));
}
const nativeScopedRouteProperty = "__nativeVueRouterScopedRoute";
export class VueRouterBridge {
private removeAfterEach?: () => void;
private pendingPop?: (failure?: NavigationFailure | void) => void;
constructor(
readonly router: Router,
acceptRoute: (route: RouteLocationNormalizedLoaded) => void,
) {
this.removeAfterEach = router.afterEach((to, _from, failure) => {
if (!failure) acceptRoute(to);
this.pendingPop?.(failure);
this.pendingPop = undefined;
});
}
installRouteScope(app: App) {
// Vue Router's global `$route` always reads currentRoute. Preview trees
// provide their own routeLocationKey, so bridge that injection to the
// Options API too.
app.mixin({
inject: {
[nativeScopedRouteProperty]: { from: routeLocationKey },
},
computed: {
$route() {
return (
this as unknown as Record<
typeof nativeScopedRouteProperty,
RouteLocationNormalizedLoaded
>
)[nativeScopedRouteProperty];
},
},
});
}
async initialRoute() {
await this.router.isReady();
return this.router.currentRoute.value === START_LOCATION
? undefined
: this.router.currentRoute.value;
}
resolve(to: RouteLocationRaw) {
return this.router.resolve(to);
}
currentFullPath() {
return this.router.currentRoute.value.fullPath;
}
load(route: RouteLocationResolved) {
return loadRouteLocation(route);
}
async commit(
transaction: NativeTransaction,
target: NativeViewEntry,
history: NativeHistoryLedger,
): Promise<NavigationResult> {
if (transaction.kind === "pop" || transaction.kind === "dismiss") {
if (target.synthetic)
return await this.router.replace(target.route.fullPath);
return await this.navigateHistory(-1);
}
if (transaction.replace) {
const targetIndex = history.indexOf(target.key);
if (targetIndex >= 0 && targetIndex < history.length - 1)
return await this.navigateHistory(targetIndex - (history.length - 1));
}
return transaction.replace
? await this.router.replace(target.route.fullPath)
: await this.router.push(target.route.fullPath);
}
dispose() {
this.removeAfterEach?.();
}
private async navigateHistory(delta: number) {
return await new Promise<NavigationFailure | void>((resolve) => {
this.pendingPop = resolve;
this.router.go(delta);
globalThis.setTimeout(() => {
if (this.pendingPop === resolve) {
this.pendingPop = undefined;
resolve();
}
}, 1200);
});
}
}