UNPKG

@tanstack/solid-router

Version:

Modern and scalable routing for Solid applications

363 lines (362 loc) 13.7 kB
import { CatchBoundary, ErrorComponent } from "./CatchBoundary.js"; import { useRouter } from "./useRouter.js"; import { nearestMatchContext } from "./matchContext.js"; import { SafeFragment } from "./SafeFragment.js"; import { CatchNotFound, getNotFound } 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 { Dynamic, createComponent, memo, mergeProps } from "solid-js/web"; import * as Solid from "solid-js"; import { isServer } from "@tanstack/router-core/isServer"; //#region src/Match.tsx var Match = (props) => { const router = useRouter(); const match = Solid.createMemo(() => { const id = props.matchId; if (!id) return void 0; return router.stores.activeMatchStoresById.get(id)?.state; }); const rawMatchState = Solid.createMemo(() => { const currentMatch = match(); if (!currentMatch) return null; const routeId = currentMatch.routeId; const parentRouteId = router.routesById[routeId]?.parentRoute?.id; return { matchId: currentMatch.id, routeId, ssr: currentMatch.ssr, _displayPending: currentMatch._displayPending, parentRouteId }; }); const nearestMatch = { matchId: () => rawMatchState()?.matchId, routeId: () => rawMatchState()?.routeId, match, hasPending: Solid.createMemo(() => { const currentRouteId = rawMatchState()?.routeId; return currentRouteId ? Boolean(router.stores.pendingRouteIds.state[currentRouteId]) : false; }) }; return createComponent(Solid.Show, { get when() { return rawMatchState(); }, children: (currentMatchState) => { const route = () => router.routesById[currentMatchState().routeId]; const resolvePendingComponent = () => route().options.pendingComponent ?? router.options.defaultPendingComponent; const routeErrorComponent = () => route().options.errorComponent ?? router.options.defaultErrorComponent; const routeOnCatch = () => route().options.onCatch ?? router.options.defaultOnCatch; const routeNotFoundComponent = () => route().isRoot ? route().options.notFoundComponent ?? router.options.notFoundRoute?.options.component : route().options.notFoundComponent; const resolvedNoSsr = currentMatchState().ssr === false || currentMatchState().ssr === "data-only"; const ResolvedSuspenseBoundary = () => Solid.Suspense; const ResolvedCatchBoundary = () => routeErrorComponent() ? CatchBoundary : SafeFragment; const ResolvedNotFoundBoundary = () => routeNotFoundComponent() ? CatchNotFound : SafeFragment; return createComponent(route().isRoot ? route().options.shellComponent ?? SafeFragment : SafeFragment, { get children() { return [createComponent(nearestMatchContext.Provider, { value: nearestMatch, get children() { return createComponent(Dynamic, { get component() { return ResolvedSuspenseBoundary(); }, get fallback() { return memo(() => !!((isServer ?? router.isServer) && resolvedNoSsr))() ? void 0 : createComponent(Dynamic, { get component() { return resolvePendingComponent(); } }); }, get children() { return createComponent(Dynamic, { get component() { return ResolvedCatchBoundary(); }, getResetKey: () => router.stores.loadedAt.state, get errorComponent() { return routeErrorComponent() || ErrorComponent; }, onCatch: (error) => { const notFoundError = getNotFound(error); if (notFoundError) { notFoundError.routeId ??= currentMatchState().routeId; throw notFoundError; } if (process.env.NODE_ENV !== "production") console.warn(`Warning: Error in route match: ${currentMatchState().routeId}`); routeOnCatch()?.(error); }, get children() { return createComponent(Dynamic, { get component() { return ResolvedNotFoundBoundary(); }, fallback: (error) => { const notFoundError = getNotFound(error) ?? error; notFoundError.routeId ??= currentMatchState().routeId; if (!routeNotFoundComponent() || notFoundError.routeId && notFoundError.routeId !== currentMatchState().routeId || !notFoundError.routeId && !route().isRoot) throw notFoundError; return createComponent(Dynamic, mergeProps({ get component() { return routeNotFoundComponent(); } }, notFoundError)); }, get children() { return createComponent(Solid.Switch, { get children() { return [createComponent(Solid.Match, { when: resolvedNoSsr, get children() { return createComponent(Solid.Show, { get when() { return !(isServer ?? router.isServer); }, get fallback() { return createComponent(Dynamic, { get component() { return resolvePendingComponent(); } }); }, get children() { return createComponent(MatchInner, {}); } }); } }), createComponent(Solid.Match, { when: !resolvedNoSsr, get children() { return createComponent(MatchInner, {}); } })]; } }); } }); } }); } }); } }), memo(() => memo(() => currentMatchState().parentRouteId === rootRouteId)() ? [createComponent(OnRendered, {}), memo(() => memo(() => !!(router.options.scrollRestoration && (isServer ?? router.isServer)))() ? createComponent(ScrollRestoration, {}) : null)] : null)]; } }); } }); }; function OnRendered() { const router = useRouter(); const location = Solid.createMemo(() => router.stores.resolvedLocation.state?.state.__TSR_key); Solid.createEffect(Solid.on([location], () => { router.emit({ type: "onRendered", ...getLocationChangeInfo(router.stores.location.state, router.stores.resolvedLocation.state) }); })); return null; } var MatchInner = () => { const router = useRouter(); const match = Solid.useContext(nearestMatchContext).match; const rawMatchState = Solid.createMemo(() => { const currentMatch = match(); if (!currentMatch) return null; const routeId = currentMatch.routeId; const remountDeps = (router.routesById[routeId].options.remountDeps ?? router.options.defaultRemountDeps)?.({ routeId, loaderDeps: currentMatch.loaderDeps, params: currentMatch._strictParams, search: currentMatch._strictSearch }); return { key: remountDeps ? JSON.stringify(remountDeps) : void 0, routeId, match: { id: currentMatch.id, status: currentMatch.status, error: currentMatch.error, _forcePending: currentMatch._forcePending ?? false, _displayPending: currentMatch._displayPending ?? false } }; }); return createComponent(Solid.Show, { get when() { return rawMatchState(); }, children: (currentMatchState) => { const route = () => router.routesById[currentMatchState().routeId]; const currentMatch = () => currentMatchState().match; const componentKey = () => currentMatchState().key ?? currentMatchState().match.id; const out = () => { const Comp = route().options.component ?? router.options.defaultComponent; if (Comp) return createComponent(Comp, {}); return createComponent(Outlet, {}); }; const keyedOut = () => createComponent(Solid.Show, { get when() { return componentKey(); }, keyed: true, children: (_key) => out() }); return createComponent(Solid.Switch, { get children() { return [ createComponent(Solid.Match, { get when() { return currentMatch()._displayPending; }, children: (_) => { const [displayPendingResult] = Solid.createResource(() => router.getMatch(currentMatch().id)?._nonReactive.displayPendingPromise); return memo(displayPendingResult); } }), createComponent(Solid.Match, { get when() { return currentMatch()._forcePending; }, children: (_) => { const [minPendingResult] = Solid.createResource(() => router.getMatch(currentMatch().id)?._nonReactive.minPendingPromise); return memo(minPendingResult); } }), createComponent(Solid.Match, { get when() { return currentMatch().status === "pending"; }, children: (_) => { const pendingMinMs = route().options.pendingMinMs ?? router.options.defaultPendingMinMs; if (pendingMinMs) { const routerMatch = router.getMatch(currentMatch().id); if (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 [loaderResult] = Solid.createResource(async () => { await new Promise((r) => setTimeout(r, 0)); return router.getMatch(currentMatch().id)?._nonReactive.loadPromise; }); const FallbackComponent = route().options.pendingComponent ?? router.options.defaultPendingComponent; return [FallbackComponent && pendingMinMs > 0 ? createComponent(Dynamic, { component: FallbackComponent }) : null, memo(loaderResult)]; } }), createComponent(Solid.Match, { get when() { return currentMatch().status === "notFound"; }, children: (_) => { if (!isNotFound(currentMatch().error)) { if (process.env.NODE_ENV !== "production") throw new Error("Invariant failed: Expected a notFound error"); invariant(); } return createComponent(Solid.Show, { get when() { return currentMatchState().routeId; }, keyed: true, children: (_routeId) => renderRouteNotFound(router, route(), currentMatch().error) }); } }), createComponent(Solid.Match, { get when() { return currentMatch().status === "redirected"; }, children: (_) => { if (!isRedirect(currentMatch().error)) { if (process.env.NODE_ENV !== "production") throw new Error("Invariant failed: Expected a redirect error"); invariant(); } const [loaderResult] = Solid.createResource(async () => { await new Promise((r) => setTimeout(r, 0)); return router.getMatch(currentMatch().id)?._nonReactive.loadPromise; }); return memo(loaderResult); } }), createComponent(Solid.Match, { get when() { return currentMatch().status === "error"; }, children: (_) => { if (isServer ?? router.isServer) return createComponent((route().options.errorComponent ?? router.options.defaultErrorComponent) || ErrorComponent, { get error() { return currentMatch().error; }, info: { componentStack: "" } }); throw currentMatch().error; } }), createComponent(Solid.Match, { get when() { return currentMatch().status === "success"; }, get children() { return keyedOut(); } }) ]; } }); } }); }; var Outlet = () => { const router = useRouter(); const nearestParentMatch = Solid.useContext(nearestMatchContext); const parentMatch = nearestParentMatch.match; const routeId = nearestParentMatch.routeId; const route = Solid.createMemo(() => routeId() ? router.routesById[routeId()] : void 0); const parentGlobalNotFound = Solid.createMemo(() => parentMatch()?.globalNotFound ?? false); const childMatchId = Solid.createMemo(() => { const currentRouteId = routeId(); return currentRouteId ? router.stores.childMatchIdByRouteId.state[currentRouteId] : void 0; }); const childMatchStatus = Solid.createMemo(() => { const id = childMatchId(); if (!id) return void 0; return router.stores.activeMatchStoresById.get(id)?.state.status; }); const shouldShowNotFound = () => childMatchStatus() !== "redirected" && parentGlobalNotFound(); return createComponent(Solid.Show, { get when() { return memo(() => !!!shouldShowNotFound())() && childMatchId(); }, get fallback() { return createComponent(Solid.Show, { get when() { return memo(() => !!shouldShowNotFound())() && route(); }, children: (resolvedRoute) => renderRouteNotFound(router, resolvedRoute(), void 0) }); }, children: (childMatchIdAccessor) => { const currentMatchId = Solid.createMemo(() => childMatchIdAccessor()); return createComponent(Solid.Show, { get when() { return routeId() === rootRouteId; }, get fallback() { return createComponent(Match, { get matchId() { return currentMatchId(); } }); }, get children() { return createComponent(Solid.Suspense, { get fallback() { return createComponent(Dynamic, { get component() { return router.options.defaultPendingComponent; } }); }, get children() { return createComponent(Match, { get matchId() { return currentMatchId(); } }); } }); } }); } }); }; //#endregion export { Match, Outlet }; //# sourceMappingURL=Match.js.map