@solidjs/router 2.0.0-next.35 → 2.0.0-next.37

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.
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
7
  import { mockBase, createMemoObject, extractSearchParams, invariant, matchLink, 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.
@@ -237,10 +240,7 @@ export const useLinkState = (href, options = {}) => {
237
240
  // observe the committed location during a transition
238
241
  pending: createMemo(() => {
239
242
  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);
243
+ return linkPending(router, to(), base, options.end);
244
244
  })
245
245
  };
246
246
  };
@@ -577,7 +577,11 @@ export function createRouterContext(integration, branches, getContext, options =
577
577
  _navigation: isServer ? undefined : 1
578
578
  });
579
579
  }
580
- const location = createLocation(() => source().value, () => source().state, utils.queryWrapper);
580
+ // The flushed world — what `latest(source)` answers — as the location's own
581
+ // memo sees it: it recomputes under the transition that carries a write,
582
+ // never for a write of this same tick (A28).
583
+ let flushed = initialSource;
584
+ const location = createLocation(() => (flushed = source()).value, () => source().state, utils.queryWrapper);
581
585
  // The flash cookie is consumed eagerly: its one-shot clear (Set-Cookie)
582
586
  // must be appended before streaming flushes the response headers, and an
583
587
  // unread outcome must not haunt a later request's render. Only detection
@@ -643,7 +647,9 @@ export function createRouterContext(integration, branches, getContext, options =
643
647
  // NotReadyError's source must be a reactive async node, not the raw
644
648
  // Promise. Keep one reader per boundary for this router owner so rejection
645
649
  // is delivered through the graph's error channel and resolution wakes the
646
- // parked matches computation.
650
+ // parked matches computation. `transparent`: the reader exists on the
651
+ // client only (the server parks `matches` on the promise directly), so it
652
+ // must not take a hydration id the server never allocated.
647
653
  const routerOwner = getOwner();
648
654
  const lazyReaders = new WeakMap();
649
655
  const readLazySubtree = (record) => {
@@ -652,7 +658,7 @@ export function createRouterContext(integration, branches, getContext, options =
652
658
  read = runWithOwner(routerOwner, () => createMemo(() => {
653
659
  const result = resolveLazySubtree(record);
654
660
  return result instanceof Promise ? result.then(() => undefined) : undefined;
655
- }, DEV && { name: "lazyRoutes" }));
661
+ }, { transparent: true, ...(DEV && { name: "lazyRoutes" }) }));
656
662
  lazyReaders.set(record, read);
657
663
  }
658
664
  return read();
@@ -688,30 +694,16 @@ export function createRouterContext(integration, branches, getContext, options =
688
694
  }
689
695
  return m;
690
696
  }, 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);
697
+ const headedLocation = () => (isServer ? untrack(source) : flushed);
698
+ // A flushed navigation write whose settle has not run is still in flight.
699
+ const unsettled = (change) => change._navigation !== undefined && !!integration.settled && !integration.settled(change);
703
700
  const transitionIntent = () => {
704
- if (!isPending(source))
701
+ const head = headedLocation();
702
+ if (!unsettled(head))
705
703
  return;
706
- const navigation = latest(source)._navigation;
704
+ const navigation = head._navigation;
707
705
  return navigation === -1 ? "native" : navigation && navigation > 0 ? "navigate" : undefined;
708
706
  };
709
- const pendingNavigation = () => {
710
- if (!isRouting())
711
- return;
712
- const target = latest(source);
713
- return target._navigation && target._navigation > 0 ? target : undefined;
714
- };
715
707
  const buildParams = () => mergeParams(matches());
716
708
  const wrapParams = utils.paramsWrapper
717
709
  ? (getParams) => utils.paramsWrapper(getParams, branches)
@@ -731,11 +723,9 @@ export function createRouterContext(integration, branches, getContext, options =
731
723
  location,
732
724
  params,
733
725
  wrapParams,
734
- isRouting,
735
726
  intent: transitionIntent,
736
- get pendingTarget() {
737
- return pendingNavigation();
738
- },
727
+ _source: source,
728
+ _owner: routerOwner,
739
729
  renderPath,
740
730
  parsePath,
741
731
  navigatorFactory,
@@ -795,10 +785,10 @@ export function createRouterContext(integration, branches, getContext, options =
795
785
  // the destination the leave guard is told all read it. A write of this
796
786
  // same tick is not in it (A28) — see `compose` below.
797
787
  //
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);
788
+ // A redirect hop: the previous navigation's write has not settled — it
789
+ // is still held, or a guard is redirecting in its landing flush — so its
790
+ // destination was never shown.
791
+ const headed = headedLocation();
802
792
  // A composed target (`setSearchParams`) is a function of where the
803
793
  // router is heading, an unflushed write of this tick included. Only
804
794
  // the integration's functional updater serves that value, so the
@@ -834,7 +824,7 @@ export function createRouterContext(integration, branches, getContext, options =
834
824
  const navigationDepth = !isServer &&
835
825
  headed._navigation !== undefined &&
836
826
  headed._navigation > 0 &&
837
- (isPending(source) || integration.inflight?.() === headed)
827
+ unsettled(headed)
838
828
  ? headed._navigation
839
829
  : 0;
840
830
  if (navigationDepth >= MAX_REDIRECTS) {
@@ -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;
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Component, Signal } from "solid-js";
1
+ import type { Component, Owner, Signal } from "solid-js";
2
2
  import type { JSX, ResponseEnvelope } from "@solidjs/web";
3
3
  declare module "@solidjs/web" {
4
4
  interface RequestEvent {
@@ -118,13 +118,13 @@ export interface LocationWrite<S = unknown> extends Omit<LocationChange<S>, "val
118
118
  export interface RouterIntegration {
119
119
  signal: [get: () => LocationChange, set: (next: LocationWrite) => void];
120
120
  /**
121
- * The navigation written but not yet committed to history — the window
122
- * between its location write and the settle that pushes it. A destination
123
- * in that window was never shown, so a `navigate()` issued inside it (a
124
- * guard redirecting as the held route lands) is a hop of that navigation
125
- * rather than a new one, exactly as one issued while it is still pending.
121
+ * Whether a navigation write's settle has run: Solid's `onSettled`,
122
+ * registered beside the write, fired for the transition that carried it.
123
+ * Until then the write is in flight: its destination was never shown, so a
124
+ * `navigate()` issued meanwhile (a guard redirecting as the held route
125
+ * lands) is a hop of that navigation rather than a new one.
126
126
  */
127
- inflight?: () => LocationChange | undefined;
127
+ settled?: (write: LocationChange) => boolean;
128
128
  utils?: Partial<RouterUtils>;
129
129
  }
130
130
  export type Intent = "initial" | "native" | "navigate" | "preload";
@@ -330,11 +330,12 @@ export interface RouterContext {
330
330
  params: Params;
331
331
  wrapParams: (getParams: () => Params) => Params;
332
332
  navigatorFactory: NavigatorFactory;
333
- isRouting: () => boolean;
334
333
  /** @internal Intent of the in-flight canonical location transition. */
335
334
  intent?: () => Intent | undefined;
336
- /** The target of the in-flight navigation transition, if any. Not reactive. */
337
- readonly pendingTarget?: LocationChange;
335
+ /** @internal The integration's location signal: the last flushed write. */
336
+ _source: () => LocationChange;
337
+ /** @internal The owner the router's own computations live under. */
338
+ _owner: Owner | null;
338
339
  matches: () => RouteMatch[];
339
340
  renderPath(path: string): string;
340
341
  parsePath(str: string): string;
@@ -343,6 +344,22 @@ export interface RouterContext {
343
344
  singleFlight: boolean;
344
345
  submissions: Signal<Submission<any, any>[]>;
345
346
  }
347
+ /**
348
+ * A plugin for the router's `links` option that extends the state it writes
349
+ * onto claimed anchors. Built once per router with its base path. The router
350
+ * ships `pendingLinks`, which adds `data-pending`.
351
+ *
352
+ * @example
353
+ * ```ts
354
+ * const Router = createRouter({ routes, links: pendingLinks });
355
+ * ```
356
+ */
357
+ export type LinksPlugin = (router: RouterContext, base: string) => {
358
+ /** The reactive read the claims sweep re-runs on. */
359
+ track(): unknown;
360
+ /** Whether an anchor resolving to `target` is the in-flight destination; read when `track()` is truthy. */
361
+ pending(target: string | undefined): boolean;
362
+ };
346
363
  export interface BeforeLeaveEventArgs {
347
364
  from: Location;
348
365
  to: string | number;
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "Ryan Turnquist"
7
7
  ],
8
8
  "license": "MIT",
9
- "version": "2.0.0-next.35",
9
+ "version": "2.0.0-next.37",
10
10
  "homepage": "https://github.com/solidjs/solid-router#readme",
11
11
  "repository": {
12
12
  "type": "git",
@@ -37,23 +37,23 @@
37
37
  "@rollup/plugin-babel": "6.0.4",
38
38
  "@rollup/plugin-node-resolve": "15.3.0",
39
39
  "@rollup/plugin-terser": "0.4.4",
40
- "@solidjs/vite-plugin": "3.0.0-next.35",
41
- "@solidjs/web": "^2.0.0-rc.13",
40
+ "@solidjs/vite-plugin": "3.0.0-next.47",
41
+ "@solidjs/web": "^2.0.0-rc.14",
42
42
  "@types/jest": "^29.5.14",
43
43
  "@types/node": "^22.10.0",
44
44
  "babel-preset-solid": "^2.0.0-rc.2",
45
45
  "jsdom": "^25.0.1",
46
46
  "prettier": "^3.4.1",
47
47
  "rollup": "^4.27.4",
48
- "solid-js": "^2.0.0-rc.13",
48
+ "solid-js": "^2.0.0-rc.14",
49
49
  "typescript": "^5.7.2",
50
50
  "vite": "^8.2.2",
51
51
  "vitest": "^4.1.11"
52
52
  },
53
53
  "peerDependencies": {
54
- "@solidjs/web": "^2.0.0-rc.13",
54
+ "@solidjs/web": "^2.0.0-rc.14",
55
55
  "filesystem-routing": ">=0.4.0",
56
- "solid-js": "^2.0.0-rc.13"
56
+ "solid-js": "^2.0.0-rc.14"
57
57
  },
58
58
  "peerDependenciesMeta": {
59
59
  "filesystem-routing": {