@real-router/solid 0.14.2 → 0.14.4

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.
Files changed (41) hide show
  1. package/dist/cjs/index.js +0 -0
  2. package/dist/esm/index.mjs +0 -0
  3. package/dist/types/dom-utils/link-utils.d.ts.map +1 -1
  4. package/dist/types/dom-utils/scroll-spy.d.ts.map +1 -1
  5. package/package.json +7 -8
  6. package/src/RouterProvider.tsx +0 -112
  7. package/src/components/Await.tsx +0 -56
  8. package/src/components/ClientOnly.tsx +0 -20
  9. package/src/components/HttpStatusCode.tsx +0 -65
  10. package/src/components/HttpStatusProvider.tsx +0 -21
  11. package/src/components/Link.tsx +0 -140
  12. package/src/components/RouteView/RouteView.tsx +0 -59
  13. package/src/components/RouteView/components.tsx +0 -85
  14. package/src/components/RouteView/helpers.tsx +0 -0
  15. package/src/components/RouteView/index.ts +0 -8
  16. package/src/components/RouteView/types.ts +0 -24
  17. package/src/components/RouterErrorBoundary.tsx +0 -45
  18. package/src/components/ServerOnly.tsx +0 -20
  19. package/src/components/Streamed.tsx +0 -23
  20. package/src/constants.ts +0 -27
  21. package/src/context.ts +0 -35
  22. package/src/createSignalFromSource.ts +0 -71
  23. package/src/createStoreFromSource.ts +0 -65
  24. package/src/directives/link.tsx +0 -111
  25. package/src/directives.d.ts +0 -10
  26. package/src/hooks/useDeferred.tsx +0 -36
  27. package/src/hooks/useNavigator.tsx +0 -6
  28. package/src/hooks/useRoute.tsx +0 -27
  29. package/src/hooks/useRouteEnter.tsx +0 -121
  30. package/src/hooks/useRouteExit.tsx +0 -123
  31. package/src/hooks/useRouteNode.tsx +0 -13
  32. package/src/hooks/useRouteNodeStore.tsx +0 -12
  33. package/src/hooks/useRouteStore.tsx +0 -12
  34. package/src/hooks/useRouteUtils.tsx +0 -50
  35. package/src/hooks/useRouter.tsx +0 -6
  36. package/src/hooks/useRouterTransition.tsx +0 -14
  37. package/src/index.tsx +0 -66
  38. package/src/ssr.tsx +0 -39
  39. package/src/types.ts +0 -28
  40. package/src/utils/createHttpStatusSink.ts +0 -31
  41. package/src/utils/createMountedSignal.ts +0 -26
package/dist/cjs/index.js CHANGED
Binary file
Binary file
@@ -1 +1 @@
1
- {"version":3,"file":"link-utils.d.ts","sourceRoot":"","sources":["../../../src/dom-utils/link-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,MAAM,EACN,MAAM,EACN,KAAK,EACN,MAAM,mBAAmB,CAAC;AAE3B,wBAAgB,cAAc,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAQvD;AA8CD;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1B,MAAM,GAAG,SAAS,CAsDpB;AA4BD,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,YAAY,CAAC,EAAE,iBAAiB,GAC/B,OAAO,CAAC,KAAK,CAAC,CAyBhB;AA0BD,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,OAAO,EACjB,eAAe,EAAE,MAAM,GAAG,SAAS,EACnC,aAAa,EAAE,MAAM,GAAG,SAAS,GAChC,MAAM,GAAG,SAAS,CAyBpB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,IAAI,EAAE,MAAM,GAAG,SAAS,GACvB,OAAO,CA8BT;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CA+B3E"}
1
+ {"version":3,"file":"link-utils.d.ts","sourceRoot":"","sources":["../../../src/dom-utils/link-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,MAAM,EACN,MAAM,EACN,KAAK,EACN,MAAM,mBAAmB,CAAC;AAE3B,wBAAgB,cAAc,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAQvD;AA8CD;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1B,MAAM,GAAG,SAAS,CAsDpB;AA4BD,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,YAAY,CAAC,EAAE,iBAAiB,GAC/B,OAAO,CAAC,KAAK,CAAC,CAyBhB;AAyBD,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,OAAO,EACjB,eAAe,EAAE,MAAM,GAAG,SAAS,EACnC,aAAa,EAAE,MAAM,GAAG,SAAS,GAChC,MAAM,GAAG,SAAS,CAyBpB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,IAAI,EAAE,MAAM,GAAG,SAAS,GACvB,OAAO,CA8BT;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CA+B3E"}
@@ -1 +1 @@
1
- {"version":3,"file":"scroll-spy.d.ts","sourceRoot":"","sources":["../../../src/dom-utils/scroll-spy.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAqB,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEnE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAEhC;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,SAAS,CAAC;CAC1D;AAED,MAAM,WAAW,SAAS;IACxB,kDAAkD;IAClD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAuaD,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,gBAAgB,GACxB,SAAS,CAiMX"}
1
+ {"version":3,"file":"scroll-spy.d.ts","sourceRoot":"","sources":["../../../src/dom-utils/scroll-spy.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAqB,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEnE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAEhC;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,SAAS,CAAC;CAC1D;AAED,MAAM,WAAW,SAAS;IACxB,kDAAkD;IAClD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAwaD,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,gBAAgB,GACxB,SAAS,CAiMX"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@real-router/solid",
3
- "version": "0.14.2",
3
+ "version": "0.14.4",
4
4
  "type": "commonjs",
5
5
  "description": "Solid.js integration for Real-Router",
6
6
  "main": "./dist/cjs/index.js",
@@ -34,8 +34,7 @@
34
34
  }
35
35
  },
36
36
  "files": [
37
- "dist",
38
- "src"
37
+ "dist"
39
38
  ],
40
39
  "homepage": "https://github.com/greydragon888/real-router",
41
40
  "repository": {
@@ -67,9 +66,9 @@
67
66
  "license": "MIT",
68
67
  "sideEffects": false,
69
68
  "dependencies": {
70
- "@real-router/core": "^0.56.0",
71
- "@real-router/sources": "^0.8.5",
72
- "@real-router/route-utils": "^0.2.3"
69
+ "@real-router/core": "^0.58.0",
70
+ "@real-router/route-utils": "^0.2.3",
71
+ "@real-router/sources": "^0.8.7"
73
72
  },
74
73
  "devDependencies": {
75
74
  "@babel/core": "7.29.7",
@@ -82,12 +81,12 @@
82
81
  "@testing-library/user-event": "14.6.1",
83
82
  "babel-preset-solid": "1.9.12",
84
83
  "rimraf": "6.1.3",
85
- "rollup": "4.61.1",
84
+ "rollup": "4.62.0",
86
85
  "rollup-plugin-dts": "6.4.1",
87
86
  "solid-js": "1.9.13",
88
87
  "vite-plugin-solid": "2.11.12",
89
88
  "vitest": "4.1.8",
90
- "@real-router/browser-plugin": "^0.17.6"
89
+ "@real-router/browser-plugin": "^0.17.8"
91
90
  },
92
91
  "peerDependencies": {
93
92
  "solid-js": ">=1.7.0"
@@ -1,112 +0,0 @@
1
- import { getNavigator } from "@real-router/core";
2
- import { createRouteSource } from "@real-router/sources";
3
- import { createSelector, onCleanup, onMount } from "solid-js";
4
-
5
- import { RouterContext, RouteContext } from "./context";
6
- import { createSignalFromSource } from "./createSignalFromSource";
7
- import {
8
- createRouteAnnouncer,
9
- createScrollRestoration,
10
- createScrollSpy,
11
- createViewTransitions,
12
- } from "./dom-utils";
13
-
14
- import type { ScrollRestorationOptions, ScrollSpyOptions } from "./dom-utils";
15
- import type { Router } from "@real-router/core";
16
- import type { ParentProps, JSX } from "solid-js";
17
-
18
- export interface RouteProviderProps {
19
- router: Router;
20
- announceNavigation?: boolean;
21
- scrollRestoration?: ScrollRestorationOptions;
22
- scrollSpy?: ScrollSpyOptions;
23
- viewTransitions?: boolean;
24
- }
25
-
26
- export function isRouteActive(
27
- linkRouteName: string,
28
- currentRouteName: string,
29
- ): boolean {
30
- return (
31
- currentRouteName === linkRouteName ||
32
- currentRouteName.startsWith(`${linkRouteName}.`)
33
- );
34
- }
35
-
36
- // §8.1 audit fix (LOW) — collapse three identical onMount lifecycle blocks
37
- // into a single helper. Each opt-in feature has the same shape:
38
- // `if (!enabled) return; const handle = create(...); onCleanup(handle.destroy)`.
39
- // Routing setup through this helper keeps the props.<feature> check + mount
40
- // side-effect + cleanup wiring in one place.
41
- function mountFeature(
42
- enabled: unknown,
43
- factory: () => { destroy: () => void },
44
- ): void {
45
- onMount(() => {
46
- if (!enabled) {
47
- return;
48
- }
49
-
50
- const handle = factory();
51
-
52
- onCleanup(() => {
53
- handle.destroy();
54
- });
55
- });
56
- }
57
-
58
- export function RouterProvider(
59
- props: ParentProps<RouteProviderProps>,
60
- ): JSX.Element {
61
- // Setup vars FIRST (§8.1 audit fix LOW #2 — semantic ordering): the router
62
- // subscription wiring is the core of the provider, the opt-in features
63
- // below ride on top of it.
64
- const navigator = getNavigator(props.router);
65
- const routeSource = createRouteSource(props.router);
66
- const routeSignal = createSignalFromSource(routeSource);
67
-
68
- const routeSelector = createSelector(
69
- // The empty-string sentinel guarantees no Link is "active" while the
70
- // router has no route (unstarted / stopped) — `isRouteActive` short-
71
- // circuits because no real route name equals or starts with `""` +
72
- // dot boundary. Without the sentinel, `routeSignal().route?.name`
73
- // would be `undefined` and the selector would compare against
74
- // `undefined`, defeating Solid's identity-based change detection.
75
- () => routeSignal().route?.name ?? "",
76
- isRouteActive,
77
- );
78
-
79
- // Opt-in features wired through the shared mountFeature helper.
80
- mountFeature(props.announceNavigation, () =>
81
- createRouteAnnouncer(props.router),
82
- );
83
- mountFeature(props.scrollRestoration, () =>
84
- createScrollRestoration(props.router, props.scrollRestoration),
85
- );
86
- onMount(() => {
87
- const spyOpts = props.scrollSpy;
88
-
89
- if (spyOpts === undefined || spyOpts.selector === "") {
90
- return;
91
- }
92
-
93
- const spy = createScrollSpy(props.router, spyOpts);
94
-
95
- onCleanup(() => {
96
- spy.destroy();
97
- });
98
- });
99
- mountFeature(props.viewTransitions, () =>
100
- createViewTransitions(props.router),
101
- );
102
-
103
- return (
104
- <RouterContext.Provider
105
- value={{ router: props.router, navigator, routeSelector }}
106
- >
107
- <RouteContext.Provider value={routeSignal}>
108
- {props.children}
109
- </RouteContext.Provider>
110
- </RouterContext.Provider>
111
- );
112
- }
@@ -1,56 +0,0 @@
1
- import { createResource } from "solid-js";
2
-
3
- import { useDeferred } from "../hooks/useDeferred";
4
-
5
- import type { JSX } from "solid-js";
6
-
7
- export interface AwaitProps<T> {
8
- /** Deferred key declared in the loader's `defer({ deferred: { <name>: ... } })`. */
9
- readonly name: string;
10
- /** Render the resolved value. Surrounding `<Suspense>` shows fallback while
11
- * pending; rejection bubbles through Solid's `<ErrorBoundary>`. */
12
- readonly children: (value: T) => JSX.Element;
13
- }
14
-
15
- /**
16
- * Reads `useDeferred(name)` and hands the resolved value to the render-prop.
17
- * Wraps the deferred promise in `createResource` so Solid's reactivity tracks
18
- * resolution and `<Suspense>` gets the standard suspend signal.
19
- *
20
- * ```tsx
21
- * <Streamed fallback={<Spinner />}>
22
- * <Await<Review[]> name="reviews">
23
- * {(reviews) => <ReviewList items={reviews} />}
24
- * </Await>
25
- * </Streamed>
26
- * ```
27
- *
28
- * Implementation: returns a Solid accessor (function child) that reads
29
- * `resource()` — this both (a) triggers `<Suspense>` suspension while pending
30
- * and (b) re-throws on `errored` for the nearest `<ErrorBoundary>` to catch.
31
- * The render-prop is gated on `resource.state === "ready"` rather than on
32
- * truthiness so falsy resolved values (`0`, `false`, `null`, `""`) still
33
- * reach `props.children`.
34
- */
35
- export function Await<T = unknown>(props: AwaitProps<T>): JSX.Element {
36
- const promiseAccessor = useDeferred<T>(props.name);
37
- const [resource] = createResource(promiseAccessor, (promise) => promise);
38
-
39
- // The double cast `as unknown as JSX.Element` (audit-2026-05-17 §8a) is
40
- // load-bearing: this returns a Solid accessor *function*, not an element
41
- // node. `JSX.Element` in Solid is a union that includes function-as-child
42
- // for reactive bindings, but the type machinery can't narrow the bare
43
- // arrow's signature to that union — going through `unknown` is the
44
- // standard escape hatch used elsewhere in solid-router-style adapters.
45
- // Removing either cast yields a "Type '() => unknown' is not assignable
46
- // to type 'JSX.Element'" error.
47
- return (() => {
48
- const value = resource();
49
-
50
- if (resource.state !== "ready") {
51
- return;
52
- }
53
-
54
- return props.children(value as T);
55
- }) as unknown as JSX.Element;
56
- }
@@ -1,20 +0,0 @@
1
- import { Show } from "solid-js";
2
-
3
- import { createMountedSignal } from "../utils/createMountedSignal";
4
-
5
- import type { JSX } from "solid-js";
6
-
7
- export interface ClientOnlyProps {
8
- readonly children: JSX.Element;
9
- readonly fallback?: JSX.Element;
10
- }
11
-
12
- export function ClientOnly(props: ClientOnlyProps): JSX.Element {
13
- const mounted = createMountedSignal();
14
-
15
- return (
16
- <Show when={mounted()} fallback={props.fallback}>
17
- {props.children}
18
- </Show>
19
- );
20
- }
@@ -1,65 +0,0 @@
1
- import { useContext } from "solid-js";
2
-
3
- import { HttpStatusContext } from "./HttpStatusProvider";
4
-
5
- import type { JSX } from "solid-js";
6
-
7
- export interface HttpStatusCodeProps {
8
- /** HTTP status to apply to the response. Common values: 404, 410, 451, 503. */
9
- readonly code: number;
10
- }
11
-
12
- /**
13
- * Render-time HTTP status declaration. Mount inside a route component (typical
14
- * use case: a glob `*` route's NotFound page) when the status is decided by
15
- * the rendered tree rather than a loader.
16
- *
17
- * Writes `code` to the nearest `<HttpStatusProvider>`'s sink during render and
18
- * returns `null`. With no provider mounted (the standard client-side case)
19
- * the component is a silent no-op — same component tree hydrates without
20
- * touching the DOM or warning about mismatches.
21
- *
22
- * Loader-driven errors (`LoaderNotFound` → 404, `LoaderRedirect` → 30x) keep
23
- * working as before; this component covers render-time decisions only.
24
- *
25
- * Last write wins when several `<HttpStatusCode />` instances mount in the
26
- * same render pass — sink reflects the last component that ran.
27
- *
28
- * ```tsx
29
- * // entry-server.tsx
30
- * import { renderToString } from "solid-js/web";
31
- * import { createHttpStatusSink, HttpStatusProvider } from "@real-router/solid/ssr";
32
- *
33
- * const sink = createHttpStatusSink();
34
- * const html = renderToString(() => (
35
- * <HttpStatusProvider sink={sink}>
36
- * <RouterProvider router={router}>
37
- * <App />
38
- * </RouterProvider>
39
- * </HttpStatusProvider>
40
- * ));
41
- * response.status(sink.code ?? 200).send(html);
42
- * ```
43
- *
44
- * **Streaming SSR (`renderToStream`):** the response status MUST be sent
45
- * before the first body byte flushes. If `<HttpStatusCode />` is mounted
46
- * inside a late-resolving `<Suspense>` boundary, the sink write may happen
47
- * AFTER the headers are already on the wire — the override is then lost.
48
- * Mount the component in the shell (above every `<Suspense>` that could
49
- * delay it), or use `renderToStringAsync` (single-shot, awaits all Suspense
50
- * before returning HTML).
51
- *
52
- * **Valid `code` range:** Node's `res.end()` throws `Invalid status code` on
53
- * `NaN`, `0`, negative values, or values `> 999` — this surfaces as a 5xx /
54
- * dropped connection, not silent corruption. Pass a real HTTP status integer
55
- * (commonly 4xx/5xx; 100-999 is what Node accepts).
56
- */
57
- export function HttpStatusCode(props: HttpStatusCodeProps): JSX.Element {
58
- const sink = useContext(HttpStatusContext);
59
-
60
- if (sink) {
61
- sink.code = props.code;
62
- }
63
-
64
- return null;
65
- }
@@ -1,21 +0,0 @@
1
- import { createContext } from "solid-js";
2
-
3
- import type { HttpStatusSink } from "../utils/createHttpStatusSink";
4
- import type { JSX } from "solid-js";
5
-
6
- export const HttpStatusContext = createContext<HttpStatusSink | null>(null);
7
-
8
- export interface HttpStatusProviderProps {
9
- readonly sink: HttpStatusSink;
10
- readonly children: JSX.Element;
11
- }
12
-
13
- export function HttpStatusProvider(
14
- props: HttpStatusProviderProps,
15
- ): JSX.Element {
16
- return (
17
- <HttpStatusContext.Provider value={props.sink}>
18
- {props.children}
19
- </HttpStatusContext.Provider>
20
- );
21
- }
@@ -1,140 +0,0 @@
1
- import { createActiveRouteSource } from "@real-router/sources";
2
- import { createMemo, mergeProps, splitProps } from "solid-js";
3
-
4
- import { EMPTY_PARAMS, EMPTY_OPTIONS } from "../constants";
5
- import { useRequiredRouterContext } from "../context";
6
- import { createSignalFromSource } from "../createSignalFromSource";
7
- import {
8
- shouldNavigate,
9
- buildHref,
10
- buildActiveClassName,
11
- navigateWithHash,
12
- } from "../dom-utils";
13
-
14
- import type { LinkProps } from "../types";
15
- import type { Params } from "@real-router/core";
16
- import type { JSX } from "solid-js";
17
-
18
- export function Link<P extends Params = Params>(
19
- props: Readonly<LinkProps<P>>,
20
- ): JSX.Element {
21
- const merged = mergeProps(
22
- {
23
- routeParams: EMPTY_PARAMS as P,
24
- routeOptions: EMPTY_OPTIONS,
25
- activeClassName: "active",
26
- activeStrict: false,
27
- ignoreQueryParams: true,
28
- },
29
- props,
30
- );
31
-
32
- const [local, rest] = splitProps(merged, [
33
- "routeName",
34
- "routeParams",
35
- "routeOptions",
36
- "activeClassName",
37
- "activeStrict",
38
- "ignoreQueryParams",
39
- "hash",
40
- "onClick",
41
- "target",
42
- "class",
43
- "children",
44
- ]);
45
-
46
- const ctx = useRequiredRouterContext("Link");
47
- const router = ctx.router;
48
-
49
- // Hash-aware active state (#532). `routeSelector` (the O(1) shared selector)
50
- // doesn't know about hash — when `hash` prop is set, fall back to the slow
51
- // path so the source's hash comparison kicks in. Tab-style UI is opt-in via
52
- // the prop, so the fast path stays open for the typical Link case.
53
- //
54
- // §8.1 audit fix: read `props.routeParams === undefined` directly instead of
55
- // `local.routeParams === EMPTY_PARAMS`. The latter went through `mergeProps`
56
- // proxy and relied on a hidden contract (mergeProps preserves the default
57
- // sentinel identity when consumer omits the field). The new check is
58
- // explicit: "fast path kicks in when consumer did not supply routeParams".
59
- const useFastPath =
60
- local.hash === undefined &&
61
- !local.activeStrict &&
62
- local.ignoreQueryParams &&
63
- props.routeParams === undefined;
64
-
65
- const buildActiveOptions = () => {
66
- const base = {
67
- strict: local.activeStrict,
68
- ignoreQueryParams: local.ignoreQueryParams,
69
- };
70
-
71
- if (local.hash === undefined) {
72
- return base;
73
- }
74
-
75
- return { ...base, hash: local.hash };
76
- };
77
-
78
- const isActive = useFastPath
79
- ? () => ctx.routeSelector(local.routeName)
80
- : createSignalFromSource(
81
- createActiveRouteSource(
82
- router,
83
- local.routeName,
84
- local.routeParams,
85
- buildActiveOptions(),
86
- ),
87
- );
88
-
89
- // Separate memo for the hash-options object so the `{ hash }` literal
90
- // is allocated only when `local.hash` actually changes (instead of on
91
- // every `href` memo evaluation). For static `<Link hash="foo">` this
92
- // produces ONE allocation total; for dynamic hash through `<Show keyed>`
93
- // workaround the allocation cost scales with hash changes, not with
94
- // routeName/routeParams changes (§8c A4 audit fix).
95
- const hashOpts = createMemo(() =>
96
- local.hash === undefined ? undefined : { hash: local.hash },
97
- );
98
-
99
- const href = createMemo(() =>
100
- buildHref(router, local.routeName, local.routeParams, hashOpts()),
101
- );
102
-
103
- const handleClick = (evt: MouseEvent) => {
104
- if (local.onClick) {
105
- local.onClick(evt);
106
-
107
- if (evt.defaultPrevented) {
108
- return;
109
- }
110
- }
111
-
112
- if (!shouldNavigate(evt) || local.target === "_blank") {
113
- return;
114
- }
115
-
116
- evt.preventDefault();
117
- navigateWithHash(
118
- router,
119
- local.routeName,
120
- local.routeParams,
121
- local.hash,
122
- local.routeOptions,
123
- ).catch(() => {});
124
- };
125
-
126
- const finalClassName = createMemo(() =>
127
- buildActiveClassName(isActive(), local.activeClassName, local.class),
128
- );
129
-
130
- return (
131
- <a
132
- {...(rest as JSX.HTMLAttributes<HTMLAnchorElement>)}
133
- href={href()}
134
- class={finalClassName()}
135
- onClick={handleClick}
136
- >
137
- {local.children}
138
- </a>
139
- );
140
- }
@@ -1,59 +0,0 @@
1
- import { children as resolveChildren, createMemo, For, Show } from "solid-js";
2
-
3
- import { Match, NotFound, Self } from "./components";
4
- import { buildRenderList, collectElements } from "./helpers";
5
- import { useRouteNode } from "../../hooks/useRouteNode";
6
-
7
- import type { RouteViewMarker } from "./components";
8
- import type { RouteViewProps } from "./types";
9
- import type { JSX } from "solid-js";
10
-
11
- function RouteViewRoot(props: Readonly<RouteViewProps>): JSX.Element {
12
- const routeState = useRouteNode(props.nodeName);
13
-
14
- const resolved = resolveChildren(() => props.children);
15
-
16
- const elements = createMemo(() => {
17
- const arr: RouteViewMarker[] = [];
18
-
19
- collectElements(resolved(), arr);
20
-
21
- return arr;
22
- });
23
-
24
- // Idiomatic Solid: `<Show>` gates on the route presence, `<For>` iterates
25
- // the build-render-list output. `<Show when={...} keyed>` re-runs the
26
- // child callback only when route identity changes; `<For>` adds/removes
27
- // exactly the changed elements without re-running the rest.
28
- // (§8a Q4 audit fix — replaced an IIFE-in-JSX with idiomatic primitives.)
29
- const renderList = createMemo<JSX.Element[]>(() => {
30
- const state = routeState();
31
-
32
- if (!state.route) {
33
- return [];
34
- }
35
-
36
- return buildRenderList(elements(), state.route.name, props.nodeName);
37
- });
38
-
39
- return (
40
- <Show when={renderList().length > 0}>
41
- <For each={renderList()}>{(node) => node}</For>
42
- </Show>
43
- );
44
- }
45
-
46
- RouteViewRoot.displayName = "RouteView";
47
-
48
- export const RouteView = Object.assign(RouteViewRoot, {
49
- Match,
50
- Self,
51
- NotFound,
52
- });
53
-
54
- export type {
55
- RouteViewProps,
56
- MatchProps as RouteViewMatchProps,
57
- SelfProps as RouteViewSelfProps,
58
- NotFoundProps as RouteViewNotFoundProps,
59
- } from "./types";
@@ -1,85 +0,0 @@
1
- import type { MatchProps, NotFoundProps, SelfProps } from "./types";
2
- import type { JSX } from "solid-js";
3
-
4
- // Local (non-global) Symbols — Symbol.for() would expose markers to spoofing
5
- // via the global Symbol registry. See Gotchas section "RouteView Marker Objects".
6
- export const MATCH_MARKER = Symbol("RouteView.Match");
7
-
8
- export const SELF_MARKER = Symbol("RouteView.Self");
9
-
10
- export const NOT_FOUND_MARKER = Symbol("RouteView.NotFound");
11
-
12
- export interface MatchMarker {
13
- $$type: typeof MATCH_MARKER;
14
- segment: string;
15
- exact: boolean;
16
- fallback?: JSX.Element;
17
- children: JSX.Element;
18
- }
19
-
20
- export interface SelfMarker {
21
- $$type: typeof SELF_MARKER;
22
- fallback?: JSX.Element;
23
- children: JSX.Element;
24
- }
25
-
26
- export interface NotFoundMarker {
27
- $$type: typeof NOT_FOUND_MARKER;
28
- children: JSX.Element;
29
- }
30
-
31
- export type RouteViewMarker = MatchMarker | SelfMarker | NotFoundMarker;
32
-
33
- // §8.1 audit fix (LOW #8) — three marker factories share the
34
- // `$$type + children getter` skeleton plus a small per-marker payload.
35
- // `createMarker` keeps the shared shape in one place; each public factory
36
- // (Match/Self/NotFound) only provides what differs.
37
- //
38
- // The `children` getter (not a plain field) is intentional: it lets the
39
- // marker capture Solid's reactive `props.children` lazily, so swapping the
40
- // marker content in a parent component re-evaluates at render time without
41
- // pulling stale references.
42
- //
43
- // Marker objects are identified by `$$type` Symbol in RouteView/helpers.tsx,
44
- // not rendered as JSX. The `as unknown as JSX.Element` cast is required at
45
- // the call site because `JSX.Element` does not include arbitrary marker
46
- // shapes.
47
- function createMarker<M extends RouteViewMarker>(
48
- type: M["$$type"],
49
- getChildren: () => JSX.Element,
50
- extras?: Omit<M, "$$type" | "children">,
51
- ): JSX.Element {
52
- const result = {
53
- $$type: type,
54
- ...extras,
55
- get children(): JSX.Element {
56
- return getChildren();
57
- },
58
- };
59
-
60
- return result as unknown as JSX.Element;
61
- }
62
-
63
- export function Match(props: MatchProps): JSX.Element {
64
- return createMarker<MatchMarker>(MATCH_MARKER, () => props.children, {
65
- segment: props.segment,
66
- exact: props.exact ?? false,
67
- fallback: props.fallback,
68
- });
69
- }
70
-
71
- Match.displayName = "RouteView.Match";
72
-
73
- export function Self(props: SelfProps): JSX.Element {
74
- return createMarker<SelfMarker>(SELF_MARKER, () => props.children, {
75
- fallback: props.fallback,
76
- });
77
- }
78
-
79
- Self.displayName = "RouteView.Self";
80
-
81
- export function NotFound(props: NotFoundProps): JSX.Element {
82
- return createMarker<NotFoundMarker>(NOT_FOUND_MARKER, () => props.children);
83
- }
84
-
85
- NotFound.displayName = "RouteView.NotFound";
@@ -1,8 +0,0 @@
1
- export { RouteView } from "./RouteView";
2
-
3
- export type {
4
- RouteViewProps,
5
- RouteViewMatchProps,
6
- RouteViewSelfProps,
7
- RouteViewNotFoundProps,
8
- } from "./RouteView";
@@ -1,24 +0,0 @@
1
- import type { JSX } from "solid-js";
2
-
3
- export interface RouteViewProps {
4
- readonly nodeName: string;
5
- readonly children: JSX.Element;
6
- }
7
-
8
- export interface MatchProps {
9
- readonly segment: string;
10
- readonly exact?: boolean;
11
- readonly fallback?: JSX.Element;
12
- readonly children: JSX.Element;
13
- }
14
-
15
- export interface SelfProps {
16
- /** Fallback content while children are suspended. */
17
- readonly fallback?: JSX.Element;
18
- /** Content to render when the active route name equals the parent RouteView's nodeName. */
19
- readonly children: JSX.Element;
20
- }
21
-
22
- export interface NotFoundProps {
23
- readonly children: JSX.Element;
24
- }