UNPKG

@tanstack/solid-router

Version:

Modern and scalable routing for Solid applications

195 lines • 8.67 kB
import * as Solid from 'solid-js'; import { getLocationChangeInfo, trimPathRight } from '@tanstack/router-core'; import { isServer } from '@tanstack/router-core/isServer'; import { useRouter } from './useRouter'; import { describeInitial, describeNavigation, takeRequest } from './observe'; /** The history change was the arrival's own canonicalization, not a request. */ const ARRIVAL = Symbol(); /** * `offerPending`'s publish: a match still `pending` at or above the * not-found boundary. The matches below a not-found never load, and stay * `pending` in the publish that lands; an offer whose pending match is * below one already shows the destination, the not-found. */ function isPendingOffer(matches) { for (const match of matches) { if (match.status === 'pending') return true; if (match._notFound) return false; } return false; } function getResolvedLocation(router) { const resolvedLocation = router.stores.resolvedLocation.get(); if (resolvedLocation?.href === router.latestLocation.href && resolvedLocation.state.__TSR_key === router.latestLocation.state.__TSR_key) { return resolvedLocation; } return; } export function Transitioner() { const router = useRouter(); const acks = []; let committed; const isCommitted = (expected) => !!committed && committed.length === expected.length && expected.every((match, index) => committed[index] === match); // Solid's observe tier attributes what the user waited on to the // navigation that caused it: wrap the write whose landing is the // destination showing — here the match publish, after router-core awaited // the loaders — and describe the request, which predates it. The first // history change since the last publish is that request: when the user // asked, and the interaction they asked in, gone from the stack by the time // the publish runs. A push or replace while it is pending (a redirect, or // another navigation) sends it elsewhere, as `@solidjs/router` folds one: // a hop. `ARRIVAL` when the change was the arrival being canonicalized // (below): the initial declaration already covers it. let request; let canonicalizing = false; // The location the initial declaration names, canonical: what a navigation // is from until the first publish resolves one (a redirect while it loads). let arrival; // Ack when the commit's transition settles (the atomic swap), not when the // flush parks it; superseded or rolled-back commits resolve false. router.startTransition = (fn, expectedMatches) => { if (isServer ?? router.isServer) { fn(); return Promise.resolve(true); } return new Promise((resolve) => { const ack = [expectedMatches, resolve]; acks.push(ack); let publish = fn; // The pending offer (`offerPending`) is not the destination: published // undeclared, the request kept for the publish that lands. Every other // publish answers the request. if (Solid.OBSERVE !== undefined && !isPendingOffer(expectedMatches)) { const answered = request; request = undefined; const refs = answered === undefined || answered === ARRIVAL ? undefined : describeNavigation(router, answered, router.stores.resolvedLocation.get() ?? arrival, answered.hops); if (refs !== undefined) { const observe = Solid.OBSERVE; publish = refs.reduceRight((inner, ref) => () => observe.attribution.withOrigin(ref, inner), fn); } } Solid.runWithOwner(null, publish); try { Solid.flush(); } catch { // Solid auto-flushes when this is called from a reactive context. } // A commit that changed nothing produces no settlement to observe. if (acks.includes(ack) && isCommitted(expectedMatches)) { acks.splice(acks.indexOf(ack), 1); resolve(true); } }); }; // No server early-return here or below: Solid 2 derives hydration keys // from the reactive owner tree, so the server must register the same slots // as the client. The callbacks themselves never run on the server. Solid.createEffect(() => router.stores.matches.get(), (current) => { committed = current; if (acks.length) { for (const [expected, resolve] of acks.splice(0)) { resolve(isCommitted(expected)); } } }); Solid.onSettled(() => { const unsub = router.history.subscribe(({ location, action }) => { if (Solid.OBSERVE !== undefined) { // A push or replace was requested through `commitLocation`, maybe // before an `await` on the blockers; anything else is the browser // moving, requested now. const write = action.type === 'PUSH' || action.type === 'REPLACE'; const noted = write ? takeRequest(router) : undefined; if (canonicalizing) request ?? (request = ARRIVAL); else if (write && request !== undefined && request !== ARRIVAL) { request.hops.push({ location: router.parseLocation(request.location), at: noted?.at ?? performance.now(), }); request.location = location; } else { // A new request: the browser moving (back, forward) supersedes a // pending one rather than redirecting it, as in `@solidjs/router`. request = { ...(noted ?? { at: performance.now(), interaction: Solid.OBSERVE.attribution.currentOrigin(), }), location, hops: [], }; } } queueMicrotask(() => router.load().catch(console.error)); }); // The route the document arrived on is declared around establishing it, // canonicalization included, as `@solidjs/router` does: the record // names the canonical location, and the commit is the arrival's. const establish = () => { // The URL may have changed synchronously between render and settlement. router.updateLatestLocation(); const nextLocation = router.buildLocation({ to: router.latestLocation.pathname, search: true, params: true, hash: true, state: true, _includeValidateSearch: true, }); if (trimPathRight(router.latestLocation.publicHref) !== trimPathRight(nextLocation.publicHref)) { arrival = nextLocation; // The history notifies synchronously inside the commit (no blocker // to await), so the subscriber above sees the flag. canonicalizing = true; try { router.commitLocation({ ...nextLocation, replace: true, ignoreBlocker: true, }); } finally { canonicalizing = false; } return; } arrival = router.latestLocation; if (!getResolvedLocation(router) && !router._tx) { queueMicrotask(() => router.load().catch(console.error)); } }; const observe = Solid.OBSERVE; if (observe !== undefined) { observe.attribution.withOrigin(describeInitial(router, () => arrival ?? router.latestLocation), establish); } else establish(); return unsub; }); return null; } export function Rendered() { const router = useRouter(); Solid.onSettled(() => { const resolvedLocation = getResolvedLocation(router); if (resolvedLocation) { router.emit({ type: 'onRendered', ...getLocationChangeInfo(resolvedLocation, resolvedLocation), }); } }); return null; } //# sourceMappingURL=Transitioner.jsx.map