UNPKG

@tanstack/vue-router

Version:

Modern and scalable routing for Vue applications

247 lines (246 loc) 10.6 kB
import { CatchBoundary, ErrorComponent } from "./CatchBoundary.js"; import { useRouter } from "./useRouter.js"; import { useRouterState } from "./useRouterState.js"; import { matchContext } 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, isNotFound, isRedirect, rootRouteId } from "@tanstack/router-core"; import * as Vue from "vue"; import warning from "tiny-warning"; import { isServer } from "@tanstack/router-core/isServer"; import invariant from "tiny-invariant"; //#region src/Match.tsx var Match = Vue.defineComponent({ name: "Match", props: { matchId: { type: String, required: true } }, setup(props) { const router = useRouter(); let lastKnownRouteId = null; const matchData = useRouterState({ select: (s) => { let match = s.matches.find((d) => d.id === props.matchId); let matchIndex = match ? s.matches.findIndex((d) => d.id === props.matchId) : -1; if (match) lastKnownRouteId = match.routeId; else if (lastKnownRouteId) { match = s.matches.find((d) => d.routeId === lastKnownRouteId); matchIndex = match ? s.matches.findIndex((d) => d.routeId === lastKnownRouteId) : -1; } if (!match) return null; const routeId = match.routeId; const parentRouteId = matchIndex > 0 ? s.matches[matchIndex - 1]?.routeId : null; return { matchId: match.id, routeId, parentRouteId, loadedAt: s.loadedAt, ssr: match.ssr, _displayPending: match._displayPending }; } }); invariant(matchData.value, `Could not find routeId for matchId "${props.matchId}". Please file an issue!`); 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); const matchIdRef = Vue.ref(matchData.value?.matchId ?? props.matchId); Vue.watch([() => props.matchId, () => matchData.value?.matchId], ([propsMatchId, dataMatchId]) => { matchIdRef.value = dataMatchId ?? propsMatchId; }, { immediate: true }); Vue.provide(matchContext, matchIdRef); 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) => { 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)) throw error; warning(false, `Error in route match: ${actualMatchId}`); routeOnCatch.value?.(error); }, children: content }); const withScrollRestoration = [content, matchData.value?.parentRouteId === rootRouteId && router.options.scrollRestoration ? Vue.h(Vue.Fragment, null, [Vue.h(OnRendered), Vue.h(ScrollRestoration)]) : 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 = useRouterState({ select: (s) => { return s.resolvedLocation?.state.key; } }); Vue.watchEffect(() => { if (location.value) router.emit({ type: "onRendered", ...getLocationChangeInfo(router.state) }); }); return () => null; } }); var MatchInner = Vue.defineComponent({ name: "MatchInner", props: { matchId: { type: String, required: true } }, setup(props) { const router = useRouter(); let lastKnownRouteId = null; const combinedState = useRouterState({ select: (s) => { let match = s.matches.find((d) => d.id === props.matchId); if (match) lastKnownRouteId = match.routeId; else if (lastKnownRouteId) { const sameRouteMatch = s.matches.find((d) => d.routeId === lastKnownRouteId); if (sameRouteMatch) match = sameRouteMatch; } if (!match) return null; const routeId = match.routeId; const remountFn = router.routesById[routeId].options.remountDeps ?? router.options.defaultRemountDeps; let remountKey; if (remountFn) { const remountDeps = remountFn({ routeId, loaderDeps: match.loaderDeps, params: match._strictParams, search: match._strictSearch }); remountKey = remountDeps ? JSON.stringify(remountDeps) : void 0; } return { routeId, match: { id: match.id, status: match.status, error: match.error, ssr: match.ssr, _forcePending: match._forcePending, _displayPending: match._displayPending }, 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); 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") { invariant(isNotFound(match.value.error), "Expected a notFound error"); return renderRouteNotFound(router, route.value, match.value.error); } if (match.value.status === "redirected") { invariant(isRedirect(match.value.error), "Expected a redirect error"); throw router.getMatch(match.value.id)?._nonReactive.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 matchId = Vue.inject(matchContext); const safeMatchId = Vue.computed(() => matchId?.value || ""); const routeId = useRouterState({ select: (s) => s.matches.find((d) => d.id === safeMatchId.value)?.routeId }); const route = Vue.computed(() => router.routesById[routeId.value]); const parentGlobalNotFound = useRouterState({ select: (s) => { const parentMatch = s.matches.find((d) => d.id === safeMatchId.value); if (!parentMatch) return false; return parentMatch.globalNotFound; } }); const childMatchData = useRouterState({ select: (s) => { const matches = s.matches; const child = matches[matches.findIndex((d) => d.id === safeMatchId.value) + 1]; if (!child) return null; return { id: child.id, paramsKey: child.routeId + JSON.stringify(child._strictParams) }; } }); return () => { if (parentGlobalNotFound.value) return renderRouteNotFound(router, route.value, void 0); if (!childMatchData.value) return null; const nextMatch = Vue.h(Match, { matchId: childMatchData.value.id, key: childMatchData.value.paramsKey }); if (safeMatchId.value === rootRouteId) return Vue.h(Vue.Suspense, { fallback: router.options.defaultPendingComponent ? Vue.h(router.options.defaultPendingComponent) : null }, { default: () => nextMatch }); return nextMatch; }; } }); //#endregion export { Match, Outlet }; //# sourceMappingURL=Match.js.map