@tanstack/solid-router
Version:
Modern and scalable routing for Solid applications
332 lines (331 loc) • 11.7 kB
JavaScript
import { CatchBoundary, ErrorComponent } from "./CatchBoundary.js";
import { useRouter } from "./useRouter.js";
import { useRouterState } from "./useRouterState.js";
import { matchContext } from "./matchContext.js";
import { SafeFragment } from "./SafeFragment.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 { Dynamic, createComponent, memo, mergeProps } from "solid-js/web";
import * as Solid from "solid-js";
import warning from "tiny-warning";
import { isServer } from "@tanstack/router-core/isServer";
import invariant from "tiny-invariant";
//#region src/Match.tsx
var Match = (props) => {
const router = useRouter();
const matchState = useRouterState({ select: (s) => {
const match = s.matches.find((d) => d.id === props.matchId);
if (!match) return null;
return {
routeId: match.routeId,
ssr: match.ssr,
_displayPending: match._displayPending
};
} });
if (!matchState()) return null;
const route = () => router.routesById[matchState().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 = matchState().ssr === false || matchState().ssr === "data-only";
const ResolvedSuspenseBoundary = () => Solid.Suspense;
const ResolvedCatchBoundary = () => routeErrorComponent() ? CatchBoundary : SafeFragment;
const ResolvedNotFoundBoundary = () => routeNotFoundComponent() ? CatchNotFound : SafeFragment;
const resetKey = useRouterState({ select: (s) => s.loadedAt });
const parentRouteId = useRouterState({ select: (s) => {
const index = s.matches.findIndex((d) => d.id === props.matchId);
return s.matches[index - 1]?.routeId;
} });
return createComponent(route().isRoot ? route().options.shellComponent ?? SafeFragment : SafeFragment, { get children() {
return [createComponent(matchContext.Provider, {
value: () => props.matchId,
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: () => resetKey(),
get errorComponent() {
return routeErrorComponent() || ErrorComponent;
},
onCatch: (error) => {
if (isNotFound(error)) throw error;
warning(false, `Error in route match: ${matchState().routeId}`);
routeOnCatch()?.(error);
},
get children() {
return createComponent(Dynamic, {
get component() {
return ResolvedNotFoundBoundary();
},
fallback: (error) => {
if (!routeNotFoundComponent() || error.routeId && error.routeId !== matchState().routeId || !error.routeId && !route().isRoot) throw error;
return createComponent(Dynamic, mergeProps({ get component() {
return routeNotFoundComponent();
} }, error));
},
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, { get matchId() {
return props.matchId;
} });
}
});
}
}), createComponent(Solid.Match, {
when: !resolvedNoSsr,
get children() {
return createComponent(MatchInner, { get matchId() {
return props.matchId;
} });
}
})];
} });
}
});
}
});
}
});
}
}), memo(() => memo(() => parentRouteId() === rootRouteId)() ? [createComponent(OnRendered, {}), createComponent(ScrollRestoration, {})] : null)];
} });
};
function OnRendered() {
const router = useRouter();
const location = useRouterState({ select: (s) => {
return s.resolvedLocation?.state.__TSR_key;
} });
Solid.createEffect(Solid.on([location], () => {
router.emit({
type: "onRendered",
...getLocationChangeInfo(router.state)
});
}));
return null;
}
var MatchInner = (props) => {
const router = useRouter();
const matchState = useRouterState({ select: (s) => {
const match = s.matches.find((d) => d.id === props.matchId);
if (!match) return null;
const routeId = match.routeId;
const remountDeps = (router.routesById[routeId].options.remountDeps ?? router.options.defaultRemountDeps)?.({
routeId,
loaderDeps: match.loaderDeps,
params: match._strictParams,
search: match._strictSearch
});
return {
key: remountDeps ? JSON.stringify(remountDeps) : void 0,
routeId,
match: {
id: match.id,
status: match.status,
error: match.error,
_forcePending: match._forcePending,
_displayPending: match._displayPending
}
};
} });
if (!matchState()) return null;
const route = () => router.routesById[matchState().routeId];
const match = () => matchState().match;
const componentKey = () => matchState().key ?? matchState().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 match()._displayPending;
},
children: (_) => {
const [displayPendingResult] = Solid.createResource(() => router.getMatch(match().id)?._nonReactive.displayPendingPromise);
return memo(displayPendingResult);
}
}),
createComponent(Solid.Match, {
get when() {
return match()._forcePending;
},
children: (_) => {
const [minPendingResult] = Solid.createResource(() => router.getMatch(match().id)?._nonReactive.minPendingPromise);
return memo(minPendingResult);
}
}),
createComponent(Solid.Match, {
get when() {
return match().status === "pending";
},
children: (_) => {
const pendingMinMs = route().options.pendingMinMs ?? router.options.defaultPendingMinMs;
if (pendingMinMs) {
const routerMatch = router.getMatch(match().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(match().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 match().status === "notFound";
},
children: (_) => {
invariant(isNotFound(match().error), "Expected a notFound error");
return createComponent(Solid.Show, {
get when() {
return matchState().routeId;
},
keyed: true,
children: (_routeId) => renderRouteNotFound(router, route(), match().error)
});
}
}),
createComponent(Solid.Match, {
get when() {
return match().status === "redirected";
},
children: (_) => {
invariant(isRedirect(match().error), "Expected a redirect error");
const [loaderResult] = Solid.createResource(async () => {
await new Promise((r) => setTimeout(r, 0));
return router.getMatch(match().id)?._nonReactive.loadPromise;
});
return memo(loaderResult);
}
}),
createComponent(Solid.Match, {
get when() {
return match().status === "error";
},
children: (_) => {
throw match().error;
}
}),
createComponent(Solid.Match, {
get when() {
return match().status === "success";
},
get children() {
return keyedOut();
}
})
];
} });
};
var Outlet = () => {
const router = useRouter();
const matchId = Solid.useContext(matchContext);
const routeId = useRouterState({ select: (s) => s.matches.find((d) => d.id === matchId())?.routeId });
const route = () => router.routesById[routeId()];
const parentGlobalNotFound = useRouterState({ select: (s) => {
const parentMatch = s.matches.find((d) => d.id === matchId());
if (!parentMatch) return false;
return parentMatch.globalNotFound;
} });
const childMatchId = useRouterState({ select: (s) => {
const matches = s.matches;
return matches[matches.findIndex((d) => d.id === matchId()) + 1]?.id;
} });
const childMatchStatus = useRouterState({ select: (s) => {
const matches = s.matches;
return matches[matches.findIndex((d) => d.id === matchId()) + 1]?.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 shouldShowNotFound();
},
get children() {
return renderRouteNotFound(router, route(), void 0);
}
});
},
children: (matchIdAccessor) => {
const currentMatchId = Solid.createMemo(() => matchIdAccessor());
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