UNPKG

@tanstack/vue-router

Version:

Modern and scalable routing for Vue applications

256 lines (255 loc) 11 kB
import { CatchBoundary, ErrorComponent } from "./CatchBoundary.js"; import { useRouter } from "./useRouter.js"; import { matchContext, pendingMatchContext, routeIdContext } from "./matchContext.js"; import { ClientOnly } from "./ClientOnly.js"; import { CatchNotFound } from "./not-found.js"; import { renderRouteNotFound } from "./renderRouteNotFound.js"; import { ScrollRestoration } from "./scroll-restoration.js"; import { createControlledPromise, getLocationChangeInfo, invariant, isNotFound, isRedirect, rootRouteId } from "@tanstack/router-core"; import * as Vue from "vue"; import { isServer } from "@tanstack/router-core/isServer"; import { useStore } from "@tanstack/vue-store"; //#region src/Match.tsx var Match = Vue.defineComponent({ name: "Match", props: { matchId: { type: String, required: true } }, setup(props) { const router = useRouter(); const routeId = router.stores.matchStores.get(props.matchId)?.routeId; if (!routeId) { if (process.env.NODE_ENV !== "production") throw new Error(`Invariant failed: Could not find routeId for matchId "${props.matchId}". Please file an issue!`); invariant(); } const isChildOfRoot = router.routesById[routeId]?.parentRoute?.id === rootRouteId; const activeMatch = useStore(router.stores.getRouteMatchStore(routeId), (value) => value); const isPendingMatchRef = useStore(router.stores.pendingRouteIds, (pendingRouteIds) => Boolean(pendingRouteIds[routeId]), { equal: Object.is }); const loadedAt = useStore(router.stores.loadedAt, (value) => value); const matchData = Vue.computed(() => { const match = activeMatch.value; if (!match) return null; return { matchId: match.id, routeId, loadedAt: loadedAt.value, ssr: match.ssr, _displayPending: match._displayPending }; }); const route = Vue.computed(() => matchData.value ? router.routesById[matchData.value.routeId] : null); const PendingComponent = Vue.computed(() => route.value?.options?.pendingComponent ?? router?.options?.defaultPendingComponent); const pendingElement = Vue.computed(() => PendingComponent.value ? Vue.h(PendingComponent.value) : void 0); const routeErrorComponent = Vue.computed(() => route.value?.options?.errorComponent ?? router?.options?.defaultErrorComponent); const routeOnCatch = Vue.computed(() => route.value?.options?.onCatch ?? router?.options?.defaultOnCatch); const routeNotFoundComponent = Vue.computed(() => route.value?.isRoot ? route.value?.options?.notFoundComponent ?? router?.options?.notFoundRoute?.options?.component : route.value?.options?.notFoundComponent); const hasShellComponent = Vue.computed(() => { if (!route.value?.isRoot) return false; return !!route.value.options.shellComponent; }); const ShellComponent = Vue.computed(() => hasShellComponent.value ? route.value.options.shellComponent : null); Vue.provide(routeIdContext, routeId); const matchIdRef = Vue.computed(() => activeMatch.value?.id ?? props.matchId); Vue.provide(matchContext, matchIdRef); Vue.provide(pendingMatchContext, isPendingMatchRef); return () => { const actualMatchId = matchData.value?.matchId ?? props.matchId; const shouldClientOnly = matchData.value?.ssr === false || matchData.value?.ssr === "data-only" || !!matchData.value?._displayPending; const renderMatchContent = () => { const matchInner = Vue.h(MatchInner, { matchId: actualMatchId }); let content = shouldClientOnly ? Vue.h(ClientOnly, { fallback: pendingElement.value }, { default: () => matchInner }) : matchInner; if (routeNotFoundComponent.value) content = Vue.h(CatchNotFound, { fallback: (error) => { error.routeId ??= matchData.value?.routeId; if (!routeNotFoundComponent.value || error.routeId && error.routeId !== matchData.value?.routeId || !error.routeId && route.value && !route.value.isRoot) throw error; return Vue.h(routeNotFoundComponent.value, error); }, children: content }); if (routeErrorComponent.value) content = CatchBoundary({ getResetKey: () => matchData.value?.loadedAt ?? 0, errorComponent: routeErrorComponent.value || ErrorComponent, onCatch: (error) => { if (isNotFound(error)) { error.routeId ??= matchData.value?.routeId; throw error; } if (process.env.NODE_ENV !== "production") console.warn(`Warning: Error in route match: ${actualMatchId}`); routeOnCatch.value?.(error); }, children: content }); const withScrollRestoration = [content, isChildOfRoot ? Vue.h(Vue.Fragment, null, [Vue.h(OnRendered), router.options.scrollRestoration && (isServer ?? router.isServer) ? Vue.h(ScrollRestoration) : null]) : null].filter(Boolean); if (withScrollRestoration.length === 1) return withScrollRestoration[0]; return Vue.h(Vue.Fragment, null, withScrollRestoration); }; if (!hasShellComponent.value) return renderMatchContent(); return Vue.h(ShellComponent.value, null, { default: () => renderMatchContent() }); }; } }); var OnRendered = Vue.defineComponent({ name: "OnRendered", setup() { const router = useRouter(); const location = useStore(router.stores.resolvedLocation, (resolvedLocation) => resolvedLocation?.state.__TSR_key); let prevHref; Vue.watch(location, () => { if (location.value) { const currentHref = router.latestLocation.href; if (prevHref === void 0 || prevHref !== currentHref) { router.emit({ type: "onRendered", ...getLocationChangeInfo(router.stores.location.get(), router.stores.resolvedLocation.get()) }); prevHref = currentHref; } } }, { immediate: true }); return () => null; } }); var MatchInner = Vue.defineComponent({ name: "MatchInner", props: { matchId: { type: String, required: true } }, setup(props) { const router = useRouter(); const routeId = Vue.inject(routeIdContext); const activeMatch = useStore(router.stores.getRouteMatchStore(routeId), (value) => value); const combinedState = Vue.computed(() => { const match = activeMatch.value; if (!match) return null; const matchRouteId = match.routeId; const remountFn = router.routesById[matchRouteId].options.remountDeps ?? router.options.defaultRemountDeps; let remountKey; if (remountFn) { const remountDeps = remountFn({ routeId: matchRouteId, loaderDeps: match.loaderDeps, params: match._strictParams, search: match._strictSearch }); remountKey = remountDeps ? JSON.stringify(remountDeps) : void 0; } return { routeId: matchRouteId, match: { id: match.id, status: match.status, error: match.error, ssr: match.ssr, _forcePending: match._forcePending, _displayPending: match._displayPending, _nonReactive: match._nonReactive }, remountKey }; }); const route = Vue.computed(() => { if (!combinedState.value) return null; return router.routesById[combinedState.value.routeId]; }); const match = Vue.computed(() => combinedState.value?.match); const remountKey = Vue.computed(() => combinedState.value?.remountKey); const getMatchPromise = (match, key) => { return router.getMatch(match.id)?._nonReactive[key] ?? match._nonReactive[key]; }; return () => { if (!combinedState.value || !match.value || !route.value) return null; if (match.value._displayPending) { const PendingComponent = route.value.options.pendingComponent ?? router.options.defaultPendingComponent; return PendingComponent ? Vue.h(PendingComponent) : null; } if (match.value._forcePending) { const PendingComponent = route.value.options.pendingComponent ?? router.options.defaultPendingComponent; return PendingComponent ? Vue.h(PendingComponent) : null; } if (match.value.status === "notFound") { if (!isNotFound(match.value.error)) { if (process.env.NODE_ENV !== "production") throw new Error("Invariant failed: Expected a notFound error"); invariant(); } return renderRouteNotFound(router, route.value, match.value.error); } if (match.value.status === "redirected") { if (!isRedirect(match.value.error)) { if (process.env.NODE_ENV !== "production") throw new Error("Invariant failed: Expected a redirect error"); invariant(); } throw getMatchPromise(match.value, "loadPromise"); } if (match.value.status === "error") { const RouteErrorComponent = route.value.options.errorComponent ?? router.options.defaultErrorComponent; if (RouteErrorComponent) return Vue.h(RouteErrorComponent, { error: match.value.error, reset: () => { router.invalidate(); }, info: { componentStack: "" } }); throw match.value.error; } if (match.value.status === "pending") { const pendingMinMs = route.value.options.pendingMinMs ?? router.options.defaultPendingMinMs; const routerMatch = router.getMatch(match.value.id); if (pendingMinMs && routerMatch && !routerMatch._nonReactive.minPendingPromise) { if (!(isServer ?? router.isServer)) { const minPendingPromise = createControlledPromise(); routerMatch._nonReactive.minPendingPromise = minPendingPromise; setTimeout(() => { minPendingPromise.resolve(); routerMatch._nonReactive.minPendingPromise = void 0; }, pendingMinMs); } } const PendingComponent = route.value.options.pendingComponent ?? router.options.defaultPendingComponent; if (PendingComponent) return Vue.h(PendingComponent); return null; } const Comp = route.value.options.component ?? router.options.defaultComponent; const key = remountKey.value; if (Comp) return Vue.h(Comp, key !== void 0 ? { key } : void 0); return Vue.h(Outlet, key !== void 0 ? { key } : void 0); }; } }); var Outlet = Vue.defineComponent({ name: "Outlet", setup() { const router = useRouter(); const parentRouteId = Vue.inject(routeIdContext); if (!parentRouteId) return () => null; const parentMatch = useStore(router.stores.getRouteMatchStore(parentRouteId), (v) => v); const route = Vue.computed(() => parentMatch.value ? router.routesById[parentMatch.value.routeId] : void 0); const parentGlobalNotFound = Vue.computed(() => parentMatch.value?.globalNotFound ?? false); const childMatchIdMap = useStore(router.stores.childMatchIdByRouteId, (v) => v); const childMatchData = Vue.computed(() => { const childId = childMatchIdMap.value[parentRouteId]; if (!childId) return null; const child = router.stores.matchStores.get(childId)?.get(); if (!child) return null; return { id: child.id, paramsKey: child.routeId + JSON.stringify(child._strictParams) }; }); return () => { if (parentGlobalNotFound.value) { if (!route.value) return null; return renderRouteNotFound(router, route.value, void 0); } if (!childMatchData.value) return null; return Vue.h(Match, { matchId: childMatchData.value.id, key: childMatchData.value.paramsKey }); }; } }); //#endregion export { Match, Outlet }; //# sourceMappingURL=Match.js.map