@solidjs/router 2.0.0-next.36 → 2.0.0-next.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,3 @@
1
- import type { RouterContext } from "../types.js";
2
1
  import type { RouterHistory } from "./history.js";
3
2
  /**
4
3
  * Explicit scroll restoration for back/forward navigation. The browser's
@@ -13,7 +12,7 @@ import type { RouterHistory } from "./history.js";
13
12
  * so restoration survives reloads, which `scrollRestoration = "manual"`
14
13
  * otherwise disables.
15
14
  *
16
- * Restoration is a single scroll once routing settles — the same strategy
15
+ * Restoration is a single scroll once the traversal settles — the same strategy
17
16
  * SvelteKit, TanStack Router and React Router use. Settling after the
18
17
  * transition commits is what makes the offset reachable; chasing a still-
19
18
  * growing document afterwards (a ResizeObserver re-asserting the offset as
@@ -23,13 +22,38 @@ import type { RouterHistory } from "./history.js";
23
22
  * layout changes can feed it back into itself. Content that commits after the
24
23
  * transition settles — an image without reserved space, a boundary below the
25
24
  * fold — keeps whatever offset the document can hold.
25
+ *
26
+ * A server-rendered document load is the exception: the browser restores it
27
+ * natively once the document — streamed boundaries included — has loaded,
28
+ * which a restore at the first client flush cannot match. So an entry is
29
+ * handed back to the browser (`auto`) whenever its document may unload
30
+ * (pagehide), and a hydrating router arriving on a handed-back entry leaves
31
+ * the restore to the browser: it does not scroll, and it takes the entry back
32
+ * (`manual`) only after load or at the first client navigation, whichever
33
+ * comes first. A programmatic scroll during the load cancels the native
34
+ * restore in Firefox and WebKit, and WebKit restores just after the load
35
+ * event, so a `manual` write before then — inside a load listener included —
36
+ * suppresses it; the write waits a task. Handed-back depths are tracked here
37
+ * rather than read from `history.scrollRestoration`, which Firefox reports as
38
+ * the mode before pagehide after a reload. An entry this router pushed and
39
+ * never handed back stays `manual` and gets no native restore, so the router
40
+ * restores it as on a client-rendered load.
26
41
  */
27
- export declare function createScrollRestoration(): {
28
- /** When the adapter notifies a traversal: mark the target for restoration. */
42
+ export declare function createScrollRestoration(hydrating?: boolean): {
43
+ /**
44
+ * When the adapter notifies a traversal: mark the target and restore once
45
+ * the transition carrying it settles. The adapter notifies right before
46
+ * the location write, in the same tick, so the settle is that write's —
47
+ * a same-URL traversal included, which no location key would see.
48
+ */
29
49
  onPop(): void;
50
+ /** The initial page settled: restore a reload/back_forward arrival. */
51
+ settled: () => void;
52
+ /** Before a client navigation writes history: the entry it creates copies the current mode. */
53
+ beforeWrite: () => void;
30
54
  /** After a push: forward entries died, and this depth may be reused. */
31
55
  onPush(): void;
32
- create(router: RouterContext): void;
56
+ create(): void;
33
57
  };
34
58
  export type ScrollRestoration = ReturnType<typeof createScrollRestoration>;
35
59
  /**
@@ -1,6 +1,7 @@
1
- import { createEffect, onCleanup } from "solid-js";
1
+ import { onCleanup, onSettled, runWithOwner } from "solid-js";
2
2
  import { bindEvent, saveCurrentDepth } from "./history.js";
3
3
  const STORAGE_KEY = "solid-router:scroll";
4
+ const AUTO_KEY = "solid-router:scroll-auto";
4
5
  /**
5
6
  * Explicit scroll restoration for back/forward navigation. The browser's
6
7
  * native same-document heuristic is unreliable for suspense-driven rendering:
@@ -14,7 +15,7 @@ const STORAGE_KEY = "solid-router:scroll";
14
15
  * so restoration survives reloads, which `scrollRestoration = "manual"`
15
16
  * otherwise disables.
16
17
  *
17
- * Restoration is a single scroll once routing settles — the same strategy
18
+ * Restoration is a single scroll once the traversal settles — the same strategy
18
19
  * SvelteKit, TanStack Router and React Router use. Settling after the
19
20
  * transition commits is what makes the offset reachable; chasing a still-
20
21
  * growing document afterwards (a ResizeObserver re-asserting the offset as
@@ -24,18 +25,53 @@ const STORAGE_KEY = "solid-router:scroll";
24
25
  * layout changes can feed it back into itself. Content that commits after the
25
26
  * transition settles — an image without reserved space, a boundary below the
26
27
  * fold — keeps whatever offset the document can hold.
28
+ *
29
+ * A server-rendered document load is the exception: the browser restores it
30
+ * natively once the document — streamed boundaries included — has loaded,
31
+ * which a restore at the first client flush cannot match. So an entry is
32
+ * handed back to the browser (`auto`) whenever its document may unload
33
+ * (pagehide), and a hydrating router arriving on a handed-back entry leaves
34
+ * the restore to the browser: it does not scroll, and it takes the entry back
35
+ * (`manual`) only after load or at the first client navigation, whichever
36
+ * comes first. A programmatic scroll during the load cancels the native
37
+ * restore in Firefox and WebKit, and WebKit restores just after the load
38
+ * event, so a `manual` write before then — inside a load listener included —
39
+ * suppresses it; the write waits a task. Handed-back depths are tracked here
40
+ * rather than read from `history.scrollRestoration`, which Firefox reports as
41
+ * the mode before pagehide after a reload. An entry this router pushed and
42
+ * never handed back stays `manual` and gets no native restore, so the router
43
+ * restores it as on a client-rendered load.
27
44
  */
28
- export function createScrollRestoration() {
29
- window.history.scrollRestoration = "manual";
45
+ export function createScrollRestoration(hydrating) {
46
+ const h = window.history;
30
47
  // the current entry needs its depth stamp for captures to have a key, even
31
48
  // if something replaced history.state after the adapter stamped it
32
49
  saveCurrentDepth();
50
+ const depth = () => window.history.state && window.history.state._depth;
33
51
  let positions = {};
52
+ let handedBack = {};
34
53
  try {
35
54
  positions = JSON.parse(sessionStorage.getItem(STORAGE_KEY)) || {};
55
+ handedBack = JSON.parse(sessionStorage.getItem(AUTO_KEY)) || {};
36
56
  }
37
57
  catch { }
38
- const depth = () => window.history.state && window.history.state._depth;
58
+ const manual = () => {
59
+ h.scrollRestoration = "manual";
60
+ const d = depth();
61
+ if (d != null)
62
+ delete handedBack[d];
63
+ };
64
+ const d = depth();
65
+ let deferred = !!hydrating && (h.scrollRestoration === "auto" || (d != null && !!handedBack[d]));
66
+ if (!deferred)
67
+ manual();
68
+ const claim = () => {
69
+ if (!deferred)
70
+ return;
71
+ deferred = false;
72
+ manual();
73
+ };
74
+ let timer;
39
75
  let programmatic = false;
40
76
  let pending;
41
77
  const unbind = [
@@ -48,11 +84,22 @@ export function createScrollRestoration() {
48
84
  pending = undefined;
49
85
  }),
50
86
  bindEvent(window, "pagehide", () => {
87
+ h.scrollRestoration = "auto";
88
+ const d = depth();
89
+ if (d != null)
90
+ handedBack[d] = 1;
51
91
  try {
52
92
  sessionStorage.setItem(STORAGE_KEY, JSON.stringify(positions));
93
+ sessionStorage.setItem(AUTO_KEY, JSON.stringify(handedBack));
53
94
  }
54
95
  catch { }
55
- })
96
+ }),
97
+ bindEvent(window, "pageshow", e => {
98
+ if (e.persisted)
99
+ manual();
100
+ }),
101
+ bindEvent(window, "load", () => deferred && (timer = setTimeout(claim))),
102
+ () => clearTimeout(timer)
56
103
  ];
57
104
  const restore = () => {
58
105
  if (pending == null)
@@ -68,43 +115,40 @@ export function createScrollRestoration() {
68
115
  programmatic = false;
69
116
  };
70
117
  return {
71
- /** When the adapter notifies a traversal: mark the target for restoration. */
118
+ /**
119
+ * When the adapter notifies a traversal: mark the target and restore once
120
+ * the transition carrying it settles. The adapter notifies right before
121
+ * the location write, in the same tick, so the settle is that write's —
122
+ * a same-URL traversal included, which no location key would see.
123
+ */
72
124
  onPop() {
125
+ claim();
73
126
  pending = depth();
127
+ runWithOwner(null, () => onSettled(restore));
74
128
  },
129
+ /** The initial page settled: restore a reload/back_forward arrival. */
130
+ settled: () => restore(),
131
+ /** Before a client navigation writes history: the entry it creates copies the current mode. */
132
+ beforeWrite: claim,
75
133
  /** After a push: forward entries died, and this depth may be reused. */
76
134
  onPush() {
77
135
  const d = depth();
78
- if (d != null)
136
+ if (d != null) {
79
137
  for (const k in positions)
80
138
  +k >= d && delete positions[k];
139
+ for (const k in handedBack)
140
+ +k >= d && delete handedBack[k];
141
+ }
81
142
  },
82
- create(router) {
83
- // Restore once the traversal has settled: key on the location (a fully
84
- // synchronous pop commits without isRouting ever flipping) and on
85
- // isRouting, which reports in-flight transitions — native pops
86
- // included — and holds the restore until they commit. restore() no-ops
87
- // unless a traversal marked a target, so push navigations are inert.
88
- // `transparent` keeps the effect invisible to the hydration id scheme —
89
- // same reasoning as the link-claims effect (claims.ts): this setup is
90
- // client-only, so an id-consuming node here has no server counterpart
91
- // and every hydration id allocated after it shifts by one child slot.
92
- // The visible failure is any <Loading> content that settled before the
93
- // shell flush (a cache hit, a preloaded query): its serialized value and
94
- // inlined markup are keyed under the server's ids, the shifted client
95
- // misses both, recomputes, and re-renders the route fresh — duplicating
96
- // the server DOM and leaving it inert.
97
- createEffect(() => ({
98
- url: router.location.pathname + router.location.search + router.location.hash,
99
- routing: router.isRouting()
100
- }), current => {
101
- if (!current.routing)
102
- restore();
103
- }, { transparent: true });
143
+ create() {
104
144
  onCleanup(() => unbind.forEach(u => u()));
105
145
  // reload/back_forward document loads land on an existing entry (a fresh
106
- // navigation starts a new one and belongs at the top); the effect's
107
- // initial run performs the restore after first render
146
+ // navigation starts a new one and belongs at the top); the router calls
147
+ // `settled` once the initial page has; a deferred arrival is the browser's
148
+ if (deferred) {
149
+ document.readyState === "complete" && claim();
150
+ return;
151
+ }
108
152
  const [nav] = performance.getEntriesByType?.("navigation");
109
153
  if (nav && nav.type !== "navigate")
110
154
  pending = depth();
@@ -121,6 +165,7 @@ export function withScrollRestoration(history, restoration) {
121
165
  return {
122
166
  ...history,
123
167
  set(next) {
168
+ restoration.beforeWrite();
124
169
  history.set(next);
125
170
  next.replace || restoration.onPush();
126
171
  },
package/dist/routing.d.ts CHANGED
@@ -54,6 +54,8 @@ export declare const useLocation: <S = unknown>() => Location<S>;
54
54
  /**
55
55
  * Retrieves a signal that indicates whether the router is currently processing a navigation.
56
56
  * Useful for showing pending navigation state while the next route and its data settle.
57
+ * This is the way to read routing state: `RouterContext` doesn't carry `isRouting`.
58
+ * For the in-flight destination, read the location with Solid's `isPending`/`latest`.
57
59
  *
58
60
  * @example
59
61
  * ```js
@@ -102,21 +104,6 @@ export declare const useMatch: <S extends string | TypedPath>(path: () => S, mat
102
104
  * ```
103
105
  */
104
106
  export declare const useRouteMatches: () => () => RouteMatch[];
105
- /**
106
- * `usePreloadRoute` returns a function for warming a route by hand — the same
107
- * work link hover/focus intent triggers automatically: the matched routes'
108
- * lazy components load, and with `preloadData` their `preload` functions run.
109
- *
110
- * @example
111
- * ```js
112
- * const preload = usePreloadRoute();
113
- *
114
- * preload(paths.users(2).settings, { preloadData: true });
115
- * ```
116
- */
117
- export declare const usePreloadRoute: () => (url: string | URL | TypedPath, options?: {
118
- preloadData?: boolean;
119
- }) => void;
120
107
  /**
121
108
  * Retrieves a reactive, store-like object containing the current route path parameters as defined in the Route.
122
109
  *
@@ -177,7 +164,11 @@ export interface LinkState {
177
164
  * parameter order and hash aside — what `aria-current="page"` reflects.
178
165
  */
179
166
  current: () => boolean;
180
- /** This link is the target of an in-flight navigation. Styling: `data-pending`. */
167
+ /**
168
+ * This link is the target of an in-flight navigation (back/forward
169
+ * excluded). Works without the `pendingLinks` plugin, which only adds
170
+ * `data-pending` to plain anchors.
171
+ */
181
172
  pending: () => boolean;
182
173
  }
183
174
  /**
@@ -250,6 +241,7 @@ export declare function provideFlightConsumer(factory: (router: RouterContext) =
250
241
  type FlashDecoder = (cookieHeader: string | null) => Promise<FlashSubmission | undefined>;
251
242
  export declare function provideFlashDecoder(decoder: FlashDecoder): void;
252
243
  export declare function getIntent(): Intent | undefined;
244
+ export declare function setPreloadIntent(intent: Intent | undefined): Intent | undefined;
253
245
  export declare function getInPreloadFn(): boolean;
254
246
  export declare function setInPreloadFn(value: boolean): void;
255
247
  export declare function createRouterContext(integration: RouterIntegration, branches: () => Branch[], getContext?: () => any, options?: {
package/dist/routing.js CHANGED
@@ -2,10 +2,11 @@ import { runWithOwner } from "solid-js";
2
2
  // standalone import: `DEV` is undefined in solid's production build, so app
3
3
  // bundlers fold `DEV &&` diagnostics out of shipped bundles
4
4
  import { DEV } from "solid-js";
5
- import { createComponent, createContext, createMemo, createSignal, getOwner, isPending, latest, NotReadyError, untrack, useContext } from "solid-js";
5
+ import { createComponent, createContext, createMemo, createSignal, getOwner, NotReadyError, untrack, useContext } from "solid-js";
6
6
  import { clearFlashCookie, getRequestEvent, hasFlashCookie, isServer } from "@solidjs/web";
7
- import { mockBase, createMemoObject, extractSearchParams, invariant, matchLink, resolvePath, createMatcher, joinPaths, scoreRoute, mergeSearchString, expandOptionals, validateSearch } from "./utils.js";
7
+ import { mockBase, createMemoObject, extractSearchParams, invariant, linkMatcher, resolvePath, createMatcher, joinPaths, scoreRoute, mergeSearchString, expandOptionals, validateSearch } from "./utils.js";
8
8
  import { HREF } from "./paths.js";
9
+ import { linkPending, routingState } from "./pending.js";
9
10
  import { serverRouteOf, serverRouteArgs, serverRouteArgsEqual } from "./serverRouteShared.js";
10
11
  const MAX_REDIRECTS = 100;
11
12
  /**
@@ -81,6 +82,8 @@ export const useLocation = () => useRouter().location;
81
82
  /**
82
83
  * Retrieves a signal that indicates whether the router is currently processing a navigation.
83
84
  * Useful for showing pending navigation state while the next route and its data settle.
85
+ * This is the way to read routing state: `RouterContext` doesn't carry `isRouting`.
86
+ * For the in-flight destination, read the location with Solid's `isPending`/`latest`.
84
87
  *
85
88
  * @example
86
89
  * ```js
@@ -93,7 +96,7 @@ export const useLocation = () => useRouter().location;
93
96
  * );
94
97
  * ```
95
98
  */
96
- export const useIsRouting = () => useRouter().isRouting;
99
+ export const useIsRouting = () => routingState(useRouter());
97
100
  /**
98
101
  * `useMatch` takes an accessor that returns the path and creates a `Memo` that returns match information if the current path matches the provided path.
99
102
  * Useful for determining if a given path matches the current route.
@@ -145,22 +148,6 @@ export const useRouteMatches = () => {
145
148
  // return a copy so user mutations (eg. `.reverse()`) can't corrupt router state
146
149
  return () => router.matches().slice();
147
150
  };
148
- /**
149
- * `usePreloadRoute` returns a function for warming a route by hand — the same
150
- * work link hover/focus intent triggers automatically: the matched routes'
151
- * lazy components load, and with `preloadData` their `preload` functions run.
152
- *
153
- * @example
154
- * ```js
155
- * const preload = usePreloadRoute();
156
- *
157
- * preload(paths.users(2).settings, { preloadData: true });
158
- * ```
159
- */
160
- export const usePreloadRoute = () => {
161
- const pre = useRouter().preloadRoute;
162
- return (url, options = {}) => pre(url instanceof URL ? url : new URL(String(url), mockBase), options.preloadData);
163
- };
164
151
  export function useParams(_path) {
165
152
  return useRoute().params;
166
153
  }
@@ -228,7 +215,7 @@ export const useLinkState = (href, options = {}) => {
228
215
  const location = router.location;
229
216
  const to = useResolvedPath(() => String(href()));
230
217
  const base = router.base.path();
231
- const state = createMemo(() => matchLink(location, to(), base, options.end));
218
+ const state = createMemo(() => linkMatcher(location, base, options.end)(to()));
232
219
  return {
233
220
  active: createMemo(() => state().active),
234
221
  current: createMemo(() => state().current),
@@ -237,10 +224,7 @@ export const useLinkState = (href, options = {}) => {
237
224
  // observe the committed location during a transition
238
225
  pending: createMemo(() => {
239
226
  state(); // location dependency: mid-flight target swaps recompute
240
- return (router.isRouting() &&
241
- !!router.pendingTarget &&
242
- matchLink({ pathname: router.pendingTarget.value, search: "" }, to(), base, options.end)
243
- .active);
227
+ return linkPending(router, to(), base, options.end);
244
228
  })
245
229
  };
246
230
  };
@@ -551,6 +535,11 @@ let preloadIntent;
551
535
  export function getIntent() {
552
536
  return preloadIntent || useOptionalContext(RouterContextObj)?.intent?.();
553
537
  }
538
+ export function setPreloadIntent(intent) {
539
+ const prev = preloadIntent;
540
+ preloadIntent = intent;
541
+ return prev;
542
+ }
554
543
  let inPreloadFn = false;
555
544
  export function getInPreloadFn() {
556
545
  return inPreloadFn;
@@ -577,7 +566,11 @@ export function createRouterContext(integration, branches, getContext, options =
577
566
  _navigation: isServer ? undefined : 1
578
567
  });
579
568
  }
580
- const location = createLocation(() => source().value, () => source().state, utils.queryWrapper);
569
+ // The flushed world — what `latest(source)` answers — as the location's own
570
+ // memo sees it: it recomputes under the transition that carries a write,
571
+ // never for a write of this same tick (A28).
572
+ let flushed = initialSource;
573
+ const location = createLocation(() => (flushed = source()).value, () => source().state, utils.queryWrapper);
581
574
  // The flash cookie is consumed eagerly: its one-shot clear (Set-Cookie)
582
575
  // must be appended before streaming flushes the response headers, and an
583
576
  // unread outcome must not haunt a later request's render. Only detection
@@ -643,7 +636,9 @@ export function createRouterContext(integration, branches, getContext, options =
643
636
  // NotReadyError's source must be a reactive async node, not the raw
644
637
  // Promise. Keep one reader per boundary for this router owner so rejection
645
638
  // is delivered through the graph's error channel and resolution wakes the
646
- // parked matches computation.
639
+ // parked matches computation. `transparent`: the reader exists on the
640
+ // client only (the server parks `matches` on the promise directly), so it
641
+ // must not take a hydration id the server never allocated.
647
642
  const routerOwner = getOwner();
648
643
  const lazyReaders = new WeakMap();
649
644
  const readLazySubtree = (record) => {
@@ -652,16 +647,14 @@ export function createRouterContext(integration, branches, getContext, options =
652
647
  read = runWithOwner(routerOwner, () => createMemo(() => {
653
648
  const result = resolveLazySubtree(record);
654
649
  return result instanceof Promise ? result.then(() => undefined) : undefined;
655
- }, DEV && { name: "lazyRoutes" }));
650
+ }, { transparent: true, ...(DEV && { name: "lazyRoutes" }) }));
656
651
  lazyReaders.set(record, read);
657
652
  }
658
653
  return read();
659
654
  };
655
+ const matchPath = (pathname) => getRouteMatches(branches(), options.transformUrl ? options.transformUrl(pathname) : pathname);
660
656
  const matches = createMemo(() => {
661
- const pathname = typeof options.transformUrl === "function"
662
- ? options.transformUrl(location.pathname)
663
- : location.pathname;
664
- const m = getRouteMatches(branches(), pathname);
657
+ const m = matchPath(location.pathname);
665
658
  // An unresolved lazy subtree parks readers on not-ready semantics — the
666
659
  // navigation transition (or the SSR stream) holds until the table lands.
667
660
  // NotReadyError (not a returned promise) because a match chain is full
@@ -688,30 +681,16 @@ export function createRouterContext(integration, branches, getContext, options =
688
681
  }
689
682
  return m;
690
683
  }, DEV && { name: "matches" });
691
- const routingPending = createMemo(() => isPending(() => {
692
- try {
693
- matches();
694
- }
695
- catch (e) {
696
- if (e instanceof NotReadyError)
697
- throw e;
698
- }
699
- location.search;
700
- location.hash;
701
- }), DEV && { name: "routingPending" });
702
- const isRouting = () => routingPending() || isPending(source);
684
+ const headedLocation = () => (isServer ? untrack(source) : flushed);
685
+ // A flushed navigation write whose settle has not run is still in flight.
686
+ const unsettled = (change) => change._navigation !== undefined && !!integration.settled && !integration.settled(change);
703
687
  const transitionIntent = () => {
704
- if (!isPending(source))
688
+ const head = headedLocation();
689
+ if (!unsettled(head))
705
690
  return;
706
- const navigation = latest(source)._navigation;
691
+ const navigation = head._navigation;
707
692
  return navigation === -1 ? "native" : navigation && navigation > 0 ? "navigate" : undefined;
708
693
  };
709
- const pendingNavigation = () => {
710
- if (!isRouting())
711
- return;
712
- const target = latest(source);
713
- return target._navigation && target._navigation > 0 ? target : undefined;
714
- };
715
694
  const buildParams = () => mergeParams(matches());
716
695
  const wrapParams = utils.paramsWrapper
717
696
  ? (getParams) => utils.paramsWrapper(getParams, branches)
@@ -731,17 +710,16 @@ export function createRouterContext(integration, branches, getContext, options =
731
710
  location,
732
711
  params,
733
712
  wrapParams,
734
- isRouting,
735
713
  intent: transitionIntent,
736
- get pendingTarget() {
737
- return pendingNavigation();
738
- },
714
+ _source: source,
715
+ _owner: routerOwner,
716
+ _routeOwner: getContext,
717
+ _match: matchPath,
739
718
  renderPath,
740
719
  parsePath,
741
720
  navigatorFactory,
742
721
  matches,
743
722
  beforeLeave,
744
- preloadRoute,
745
723
  singleFlight: options.singleFlight === undefined ? true : options.singleFlight,
746
724
  get submissions() {
747
725
  return (submissions ||= createSignal(isServer ? initSubmissions() : [], { ownedWrite: true }));
@@ -795,10 +773,10 @@ export function createRouterContext(integration, branches, getContext, options =
795
773
  // the destination the leave guard is told all read it. A write of this
796
774
  // same tick is not in it (A28) — see `compose` below.
797
775
  //
798
- // A redirect hop: the previous navigation is still pending, or has landed
799
- // but not yet reached history (a guard redirecting in the landing flush
800
- // — its destination was never shown either way).
801
- const headed = latest(source);
776
+ // A redirect hop: the previous navigation's write has not settled — it
777
+ // is still held, or a guard is redirecting in its landing flush — so its
778
+ // destination was never shown.
779
+ const headed = headedLocation();
802
780
  // A composed target (`setSearchParams`) is a function of where the
803
781
  // router is heading, an unflushed write of this tick included. Only
804
782
  // the integration's functional updater serves that value, so the
@@ -834,7 +812,7 @@ export function createRouterContext(integration, branches, getContext, options =
834
812
  const navigationDepth = !isServer &&
835
813
  headed._navigation !== undefined &&
836
814
  headed._navigation > 0 &&
837
- (isPending(source) || integration.inflight?.() === headed)
815
+ unsettled(headed)
838
816
  ? headed._navigation
839
817
  : 0;
840
818
  if (navigationDepth >= MAX_REDIRECTS) {
@@ -870,72 +848,6 @@ export function createRouterContext(integration, branches, getContext, options =
870
848
  route = route || useOptionalContext(RouteContextObj) || baseRoute;
871
849
  return ((to, options) => navigateFromRoute(route, to, options));
872
850
  }
873
- function preloadRoute(url, preloadData) {
874
- const next = getRouteMatches(branches(), url.pathname);
875
- // An unresolved lazy subtree in the chain: the placeholder's
876
- // component.preload (below) kicks the table load; once it lands,
877
- // preload again so the real inner routes warm too. Preloads are
878
- // speculative: a failed load (held sync throw or rejection) is ignored
879
- // here — the real navigation surfaces and retries it.
880
- const boundary = next.find(m => m.route.lazy && !m.route.lazy.resolved);
881
- if (boundary) {
882
- try {
883
- resolveLazySubtree(boundary.route.lazy).then(() => preloadRoute(url, preloadData), () => { });
884
- }
885
- catch { }
886
- }
887
- // Data preloads run only for levels a navigation would mount fresh or
888
- // reuse with changed inputs: this level's params, and search as the
889
- // declared schema's output or else the raw string. Navigation itself is
890
- // already this selective (a matching level is reused and re-reads through
891
- // tracked params), so an unchanged level has nothing new to warm.
892
- let current;
893
- try {
894
- current = untrack(matches);
895
- }
896
- catch { }
897
- const query = extractSearchParams(url);
898
- const inputs = (p, q, s, r) => {
899
- const a = serverRouteArgs(r, p, q);
900
- a.search === undefined && (a.search = s);
901
- return a;
902
- };
903
- const prevIntent = preloadIntent;
904
- preloadIntent = "preload";
905
- for (let match in next) {
906
- const { route, params } = next[match];
907
- const { preload, component } = route;
908
- component?.preload?.();
909
- const now = current && current[match];
910
- const unchanged = now &&
911
- now.route.key === route.key &&
912
- serverRouteArgsEqual(inputs(params, query, url.search, route), inputs(now.params, location.query, location.search, route));
913
- inPreloadFn = true;
914
- preloadData &&
915
- !unchanged &&
916
- runWithOwner(getContext(), () => {
917
- // A server component route's data IS its call: warm the same
918
- // query entry the render will read, under the same derived args.
919
- const server = serverRouteOf(component);
920
- server && server.call(serverRouteArgs(route, params, query));
921
- preload &&
922
- preload({
923
- params,
924
- location: {
925
- pathname: url.pathname,
926
- search: url.search,
927
- hash: url.hash,
928
- query,
929
- state: null,
930
- key: ""
931
- },
932
- intent: "preload"
933
- });
934
- });
935
- inPreloadFn = false;
936
- }
937
- preloadIntent = prevIntent;
938
- }
939
851
  // Seeds the initial submission from a no-JS form post: the server
940
852
  // function runtime redirected back with the outcome in a one-shot flash
941
853
  // cookie (its default no-JS convention), consumed eagerly above and
@@ -8,7 +8,7 @@
8
8
  // // before: a client component exists only to make the call
9
9
  // const getStory = query(storyView, "story");
10
10
  // component: props => {
11
- // const View = dynamic(() => getStory(props.params.id));
11
+ // const View = dynamicComponent(() => getStory(props.params.id));
12
12
  // return <View>{props.children}</View>;
13
13
  // }
14
14
  // // after
@@ -36,7 +36,7 @@
36
36
  // A server component that takes other client positions — handlers, refs,
37
37
  // named slots — has a client half, and that half is a client component; the
38
38
  // helper does not pretend otherwise. Write the wrapper for that route.
39
- import { dynamic } from "@solidjs/web";
39
+ import { dynamicComponent } from "@solidjs/web";
40
40
  import { createComponent, DEV } from "solid-js";
41
41
  import { SERVER_ROUTE } from "./serverRouteShared.js";
42
42
  /**
@@ -77,8 +77,9 @@ source) {
77
77
  };
78
78
  function render(args, routeProps) {
79
79
  // The source may answer a component, a promise of one, or (a live query)
80
- // successive components; `dynamic`'s memo lands each the same way.
81
- const View = dynamic(() => call(args()));
80
+ // successive components; `dynamicComponent`'s memo lands each the same
81
+ // way. Never a tag, so the element runtime `dynamic` retains stays out.
82
+ const View = dynamicComponent(() => call(args()));
82
83
  return createComponent(View, {
83
84
  get children() {
84
85
  return routeProps.children;