118 lines
3.2 KiB
TypeScript
118 lines
3.2 KiB
TypeScript
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);
|
|
});
|
|
}
|
|
}
|