@real-router/solid 0.11.1 → 0.13.0
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/README.md +170 -15
- package/dist/cjs/index.d.ts +20 -1
- package/dist/cjs/index.js +0 -0
- package/dist/cjs/ssr.d.ts +163 -0
- package/dist/cjs/ssr.js +263 -0
- package/dist/esm/index.d.mts +20 -1
- package/dist/esm/index.mjs +0 -0
- package/dist/esm/ssr.d.mts +163 -0
- package/dist/esm/ssr.mjs +254 -0
- package/dist/types/RouterProvider.d.ts.map +1 -1
- package/dist/types/components/Await.d.ts +30 -0
- package/dist/types/components/Await.d.ts.map +1 -0
- package/dist/types/components/ClientOnly.d.ts +7 -0
- package/dist/types/components/ClientOnly.d.ts.map +1 -0
- package/dist/types/components/HttpStatusCode.d.ts +52 -0
- package/dist/types/components/HttpStatusCode.d.ts.map +1 -0
- package/dist/types/components/HttpStatusProvider.d.ts +9 -0
- package/dist/types/components/HttpStatusProvider.d.ts.map +1 -0
- package/dist/types/components/Link.d.ts.map +1 -1
- package/dist/types/components/RouteView/RouteView.d.ts.map +1 -1
- package/dist/types/components/RouteView/components.d.ts.map +1 -1
- package/dist/types/components/RouteView/helpers.d.ts.map +1 -1
- package/dist/types/components/ServerOnly.d.ts +7 -0
- package/dist/types/components/ServerOnly.d.ts.map +1 -0
- package/dist/types/components/Streamed.d.ts +18 -0
- package/dist/types/components/Streamed.d.ts.map +1 -0
- package/dist/types/constants.d.ts +20 -2
- package/dist/types/constants.d.ts.map +1 -1
- package/dist/types/context.d.ts +9 -0
- package/dist/types/context.d.ts.map +1 -1
- package/dist/types/createSignalFromSource.d.ts.map +1 -1
- package/dist/types/createStoreFromSource.d.ts +19 -0
- package/dist/types/createStoreFromSource.d.ts.map +1 -1
- package/dist/types/directives/link.d.ts.map +1 -1
- package/dist/types/dom-utils/__test-helpers/expected-fragment.d.ts +31 -0
- package/dist/types/dom-utils/__test-helpers/expected-fragment.d.ts.map +1 -0
- package/dist/types/dom-utils/__test-helpers/index.d.ts +9 -0
- package/dist/types/dom-utils/__test-helpers/index.d.ts.map +1 -0
- package/dist/types/dom-utils/link-utils.d.ts +23 -0
- package/dist/types/dom-utils/link-utils.d.ts.map +1 -1
- package/dist/types/dom-utils/route-announcer.d.ts.map +1 -1
- package/dist/types/dom-utils/scroll-restore.d.ts +38 -1
- package/dist/types/dom-utils/scroll-restore.d.ts.map +1 -1
- package/dist/types/hooks/useDeferred.d.ts +16 -0
- package/dist/types/hooks/useDeferred.d.ts.map +1 -0
- package/dist/types/hooks/useNavigator.d.ts.map +1 -1
- package/dist/types/hooks/useRouteUtils.d.ts.map +1 -1
- package/dist/types/hooks/useRouter.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/ssr.d.ts +16 -0
- package/dist/types/ssr.d.ts.map +1 -0
- package/dist/types/utils/createHttpStatusSink.d.ts +29 -0
- package/dist/types/utils/createHttpStatusSink.d.ts.map +1 -0
- package/dist/types/utils/createMountedSignal.d.ts +16 -0
- package/dist/types/utils/createMountedSignal.d.ts.map +1 -0
- package/package.json +21 -5
- package/src/RouterProvider.tsx +36 -30
- package/src/components/Await.tsx +56 -0
- package/src/components/ClientOnly.tsx +20 -0
- package/src/components/HttpStatusCode.tsx +65 -0
- package/src/components/HttpStatusProvider.tsx +21 -0
- package/src/components/Link.tsx +21 -15
- package/src/components/RouteView/RouteView.tsx +19 -18
- package/src/components/RouteView/components.tsx +34 -28
- package/src/components/RouteView/helpers.tsx +0 -0
- package/src/components/ServerOnly.tsx +20 -0
- package/src/components/Streamed.tsx +23 -0
- package/src/constants.ts +20 -2
- package/src/context.ts +21 -1
- package/src/createSignalFromSource.ts +48 -5
- package/src/createStoreFromSource.ts +49 -2
- package/src/directives/link.tsx +41 -16
- package/src/hooks/useDeferred.tsx +36 -0
- package/src/hooks/useNavigator.tsx +3 -12
- package/src/hooks/useRouteUtils.tsx +39 -1
- package/src/hooks/useRouter.tsx +3 -12
- package/src/index.tsx +2 -0
- package/src/ssr.tsx +39 -0
- package/src/utils/createHttpStatusSink.ts +31 -0
- package/src/utils/createMountedSignal.ts +26 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Router } from "@real-router/core";
|
|
1
|
+
import type { Router, State } from "@real-router/core";
|
|
2
2
|
export type ScrollRestorationMode = "restore" | "top" | "native";
|
|
3
3
|
export interface ScrollRestorationOptions {
|
|
4
4
|
mode?: ScrollRestorationMode | undefined;
|
|
@@ -29,4 +29,41 @@ export interface ScrollRestorationOptions {
|
|
|
29
29
|
export declare function createScrollRestoration(router: Router, options?: ScrollRestorationOptions): {
|
|
30
30
|
destroy: () => void;
|
|
31
31
|
};
|
|
32
|
+
export declare function keyOf(state: State): string;
|
|
33
|
+
/**
|
|
34
|
+
* Stable JSON serializer with sorted object keys.
|
|
35
|
+
*
|
|
36
|
+
* **Exported for testing only — not part of the public API** (intentionally
|
|
37
|
+
* excluded from `index.ts` barrel). Adapter property tests import it via
|
|
38
|
+
* the direct path to lock the key-order-insensitive property
|
|
39
|
+
* (`canonicalJson({a:1,b:2}) === canonicalJson({b:2,a:1})`).
|
|
40
|
+
*
|
|
41
|
+
* ## Divergence from `@real-router/sources/canonicalJson` — by design
|
|
42
|
+
*
|
|
43
|
+
* Two independent implementations live in the monorepo:
|
|
44
|
+
*
|
|
45
|
+
* - **`shared/dom-utils/scroll-restore.canonicalJson`** (this file) — scroll
|
|
46
|
+
* cache key builder. Uses `localeCompare` and a plain-object accumulator;
|
|
47
|
+
* tolerates `__proto__`-keyed inputs only insofar as `JSON.stringify`'s
|
|
48
|
+
* replacer happens to sort them; relies on `JSON.stringify`'s native cycle
|
|
49
|
+
* detector. Designed to be cheap on the navigation hot path. The
|
|
50
|
+
* surrounding [[safeKeyOf]] wrapper catches the two crash inputs (`BigInt`,
|
|
51
|
+
* cyclic) and skips the offending capture/restore.
|
|
52
|
+
*
|
|
53
|
+
* - **`@real-router/sources/canonicalJson`** — sources cache key builder.
|
|
54
|
+
* Uses byte-order compare (`< / >`) for locale-independence, a
|
|
55
|
+
* `Object.create(null)` accumulator to prevent prototype pollution, and a
|
|
56
|
+
* bespoke path-based cycle detector (the native one cannot see the cloned
|
|
57
|
+
* graph). Throws eagerly on `Map`/`Set`/`RegExp`/cycles — the caller falls
|
|
58
|
+
* back to a non-cached source.
|
|
59
|
+
*
|
|
60
|
+
* **They are intentionally NOT interchangeable.** Aligning them would either
|
|
61
|
+
* regress scroll-restore performance (byte-order + recursive clone is heavier
|
|
62
|
+
* per call) or weaken the sources cache (locale dependence breaks
|
|
63
|
+
* deterministic cache keys across machines). No cross-package equivalence
|
|
64
|
+
* test exists or should be added; the relationship is "different invariants,
|
|
65
|
+
* different costs, different consumers." Audit-2 / audit-2026-05-17 §2
|
|
66
|
+
* documents the choice.
|
|
67
|
+
*/
|
|
68
|
+
export declare function canonicalJson(value: unknown): string;
|
|
32
69
|
//# sourceMappingURL=scroll-restore.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scroll-restore.d.ts","sourceRoot":"","sources":["../../../src/dom-utils/scroll-restore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"scroll-restore.d.ts","sourceRoot":"","sources":["../../../src/dom-utils/scroll-restore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAUvD,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEjE,MAAM,WAAW,wBAAwB;IACvC,IAAI,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;IACzC,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACtC,eAAe,CAAC,EAAE,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,SAAS,CAAC;IACzD;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IACtC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;AAOD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,wBAAwB,GACjC;IAAE,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,CAgQzB;AA2BD,wBAAgB,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAY1C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEpD"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Accessor } from "solid-js";
|
|
2
|
+
/**
|
|
3
|
+
* Read a deferred promise published by `defer({ deferred: { <key>: Promise } })`
|
|
4
|
+
* inside an SSR data loader.
|
|
5
|
+
*
|
|
6
|
+
* Returns a Solid `Accessor<Promise<T>>` so the value tracks the active route
|
|
7
|
+
* — re-reading on navigation picks up the new state's deferred map. Wrap with
|
|
8
|
+
* `<Await name="key">{(value) => …}</Await>` (this package), which builds on
|
|
9
|
+
* `createResource` + `<Suspense>` for native Solid streaming.
|
|
10
|
+
*
|
|
11
|
+
* Returns a forever-pending promise when the key is missing — surfaces
|
|
12
|
+
* loader/consumer key drift as a visible Suspense fallback rather than a
|
|
13
|
+
* silent runtime error.
|
|
14
|
+
*/
|
|
15
|
+
export declare function useDeferred<T = unknown>(key: string): Accessor<Promise<T>>;
|
|
16
|
+
//# sourceMappingURL=useDeferred.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDeferred.d.ts","sourceRoot":"","sources":["../../../src/hooks/useDeferred.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAWzC;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAS1E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useNavigator.d.ts","sourceRoot":"","sources":["../../../src/hooks/useNavigator.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useNavigator.d.ts","sourceRoot":"","sources":["../../../src/hooks/useNavigator.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,eAAO,MAAM,YAAY,QAAO,SACoB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useRouteUtils.d.ts","sourceRoot":"","sources":["../../../src/hooks/useRouteUtils.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useRouteUtils.d.ts","sourceRoot":"","sources":["../../../src/hooks/useRouteUtils.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AA4B3D,eAAO,MAAM,aAAa,QAAO,UAehC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useRouter.d.ts","sourceRoot":"","sources":["../../../src/hooks/useRouter.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useRouter.d.ts","sourceRoot":"","sources":["../../../src/hooks/useRouter.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,eAAO,MAAM,SAAS,QAAO,MACiB,CAAC"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export { useRouteExit } from "./hooks/useRouteExit";
|
|
|
14
14
|
export { useRouteEnter } from "./hooks/useRouteEnter";
|
|
15
15
|
export { RouterProvider } from "./RouterProvider";
|
|
16
16
|
export { RouterContext, RouteContext } from "./context";
|
|
17
|
+
export type { RouterContextValue } from "./context";
|
|
17
18
|
export { createSignalFromSource } from "./createSignalFromSource";
|
|
18
19
|
export { createStoreFromSource } from "./createStoreFromSource";
|
|
19
20
|
export type { LinkProps, RouteState } from "./types";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnD,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAEvE,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE9C,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAE9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAElE,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAExD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAElE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErD,YAAY,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAEjF,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAE9D,YAAY,EACV,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,wBAAwB,CAAC;AAEhC,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAE9B,YAAY,EACV,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,YAAY,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnD,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAEvE,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE9C,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAE9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAElE,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAExD,YAAY,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAEpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAElE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErD,YAAY,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAEjF,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAE9D,YAAY,EACV,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,wBAAwB,CAAC;AAEhC,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAE9B,YAAY,EACV,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,YAAY,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { ClientOnly } from "./components/ClientOnly";
|
|
2
|
+
export { ServerOnly } from "./components/ServerOnly";
|
|
3
|
+
export { Await } from "./components/Await";
|
|
4
|
+
export { Streamed } from "./components/Streamed";
|
|
5
|
+
export { HttpStatusCode } from "./components/HttpStatusCode";
|
|
6
|
+
export { HttpStatusProvider } from "./components/HttpStatusProvider";
|
|
7
|
+
export { useDeferred } from "./hooks/useDeferred";
|
|
8
|
+
export { createHttpStatusSink } from "./utils/createHttpStatusSink";
|
|
9
|
+
export type { ClientOnlyProps } from "./components/ClientOnly";
|
|
10
|
+
export type { ServerOnlyProps } from "./components/ServerOnly";
|
|
11
|
+
export type { AwaitProps } from "./components/Await";
|
|
12
|
+
export type { StreamedProps } from "./components/Streamed";
|
|
13
|
+
export type { HttpStatusCodeProps } from "./components/HttpStatusCode";
|
|
14
|
+
export type { HttpStatusProviderProps } from "./components/HttpStatusProvider";
|
|
15
|
+
export type { HttpStatusSink } from "./utils/createHttpStatusSink";
|
|
16
|
+
//# sourceMappingURL=ssr.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssr.d.ts","sourceRoot":"","sources":["../../src/ssr.tsx"],"names":[],"mappings":"AAOA,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAGrE,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGlD,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAGpE,YAAY,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE/D,YAAY,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE/D,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,YAAY,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAEvE,YAAY,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAE/E,YAAY,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render-scoped HTTP status sink. Created per request on the server, passed to
|
|
3
|
+
* `<HttpStatusProvider sink={...}>`, and read after `renderToString` /
|
|
4
|
+
* `renderToStream` to apply the value to the HTTP response.
|
|
5
|
+
*
|
|
6
|
+
* Last write wins: if the rendered tree mounts more than one
|
|
7
|
+
* `<HttpStatusCode />`, the value reflects the last component that ran during
|
|
8
|
+
* the render pass.
|
|
9
|
+
*
|
|
10
|
+
* No-op on the client — `<HttpStatusCode />` reads the optional context and
|
|
11
|
+
* skips the write when no provider is mounted, so the same component tree can
|
|
12
|
+
* be hydrated without changing behaviour.
|
|
13
|
+
*
|
|
14
|
+
* Constraints:
|
|
15
|
+
* - **Per-request only.** Don't share a sink across requests; the rendered
|
|
16
|
+
* tree mutates `code` in place. Module-level singletons leak status
|
|
17
|
+
* between concurrent requests.
|
|
18
|
+
* - **Don't `Object.freeze` the sink.** The component writes to `.code`;
|
|
19
|
+
* freezing makes the assignment throw under ESM strict mode.
|
|
20
|
+
* - **Hydration symmetry:** mount `<HttpStatusProvider>` on both server and
|
|
21
|
+
* client (with a throwaway client sink). Solid emits `data-hk` markers
|
|
22
|
+
* per component boundary; an extra provider on one side desyncs the
|
|
23
|
+
* counter and breaks the hydration walker.
|
|
24
|
+
*/
|
|
25
|
+
export interface HttpStatusSink {
|
|
26
|
+
code: number | undefined;
|
|
27
|
+
}
|
|
28
|
+
export declare function createHttpStatusSink(): HttpStatusSink;
|
|
29
|
+
//# sourceMappingURL=createHttpStatusSink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createHttpStatusSink.d.ts","sourceRoot":"","sources":["../../../src/utils/createHttpStatusSink.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B;AAED,wBAAgB,oBAAoB,IAAI,cAAc,CAErD"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Accessor } from "solid-js";
|
|
2
|
+
/**
|
|
3
|
+
* Returns a boolean accessor that is `false` during initial render (SSR
|
|
4
|
+
* and the first client paint) and flips to `true` once the component
|
|
5
|
+
* has mounted in the browser.
|
|
6
|
+
*
|
|
7
|
+
* Solid guarantees that `onMount` does NOT fire during `renderToString` /
|
|
8
|
+
* `renderToStream`, so the accessor stays `false` server-side — this is
|
|
9
|
+
* the building block for SSR boundary components (`<ClientOnly>` /
|
|
10
|
+
* `<ServerOnly>`).
|
|
11
|
+
*
|
|
12
|
+
* Consolidates the identical `createSignal(false) + onMount(setMounted)`
|
|
13
|
+
* pattern across the two boundary components (§8a Q15).
|
|
14
|
+
*/
|
|
15
|
+
export declare function createMountedSignal(): Accessor<boolean>;
|
|
16
|
+
//# sourceMappingURL=createMountedSignal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createMountedSignal.d.ts","sourceRoot":"","sources":["../../../src/utils/createMountedSignal.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEzC;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,IAAI,QAAQ,CAAC,OAAO,CAAC,CAQvD"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@real-router/solid",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"description": "Solid.js integration for Real-Router",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
7
7
|
"module": "./dist/esm/index.mjs",
|
|
8
8
|
"types": "./dist/cjs/index.d.ts",
|
|
9
|
+
"typesVersions": {
|
|
10
|
+
"*": {
|
|
11
|
+
"ssr": [
|
|
12
|
+
"./dist/cjs/ssr.d.ts"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
},
|
|
9
16
|
"exports": {
|
|
10
17
|
".": {
|
|
11
18
|
"@real-router/internal-source": "./src/index.tsx",
|
|
@@ -15,6 +22,15 @@
|
|
|
15
22
|
},
|
|
16
23
|
"import": "./dist/esm/index.mjs",
|
|
17
24
|
"require": "./dist/cjs/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./ssr": {
|
|
27
|
+
"@real-router/internal-source": "./src/ssr.tsx",
|
|
28
|
+
"types": {
|
|
29
|
+
"import": "./dist/esm/ssr.d.mts",
|
|
30
|
+
"require": "./dist/cjs/ssr.d.ts"
|
|
31
|
+
},
|
|
32
|
+
"import": "./dist/esm/ssr.mjs",
|
|
33
|
+
"require": "./dist/cjs/ssr.js"
|
|
18
34
|
}
|
|
19
35
|
},
|
|
20
36
|
"files": [
|
|
@@ -51,9 +67,9 @@
|
|
|
51
67
|
"license": "MIT",
|
|
52
68
|
"sideEffects": false,
|
|
53
69
|
"dependencies": {
|
|
54
|
-
"@real-router/core": "^0.
|
|
70
|
+
"@real-router/core": "^0.54.1",
|
|
55
71
|
"@real-router/route-utils": "^0.2.2",
|
|
56
|
-
"@real-router/sources": "^0.8.
|
|
72
|
+
"@real-router/sources": "^0.8.3"
|
|
57
73
|
},
|
|
58
74
|
"devDependencies": {
|
|
59
75
|
"@babel/core": "7.29.0",
|
|
@@ -70,8 +86,8 @@
|
|
|
70
86
|
"rollup-plugin-dts": "6.4.1",
|
|
71
87
|
"solid-js": "1.9.12",
|
|
72
88
|
"vite-plugin-solid": "2.11.11",
|
|
73
|
-
"vitest": "4.1.
|
|
74
|
-
"@real-router/browser-plugin": "^0.17.
|
|
89
|
+
"vitest": "4.1.6",
|
|
90
|
+
"@real-router/browser-plugin": "^0.17.4"
|
|
75
91
|
},
|
|
76
92
|
"peerDependencies": {
|
|
77
93
|
"solid-js": ">=1.7.0"
|
package/src/RouterProvider.tsx
CHANGED
|
@@ -31,54 +31,60 @@ export function isRouteActive(
|
|
|
31
31
|
);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
)
|
|
34
|
+
// §8.1 audit fix (LOW) — collapse three identical onMount lifecycle blocks
|
|
35
|
+
// into a single helper. Each opt-in feature has the same shape:
|
|
36
|
+
// `if (!enabled) return; const handle = create(...); onCleanup(handle.destroy)`.
|
|
37
|
+
// Routing setup through this helper keeps the props.<feature> check + mount
|
|
38
|
+
// side-effect + cleanup wiring in one place.
|
|
39
|
+
function mountFeature(
|
|
40
|
+
enabled: unknown,
|
|
41
|
+
factory: () => { destroy: () => void },
|
|
42
|
+
): void {
|
|
37
43
|
onMount(() => {
|
|
38
|
-
if (!
|
|
44
|
+
if (!enabled) {
|
|
39
45
|
return;
|
|
40
46
|
}
|
|
41
47
|
|
|
42
|
-
const
|
|
48
|
+
const handle = factory();
|
|
43
49
|
|
|
44
50
|
onCleanup(() => {
|
|
45
|
-
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
onMount(() => {
|
|
50
|
-
if (!props.scrollRestoration) {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const sr = createScrollRestoration(props.router, props.scrollRestoration);
|
|
55
|
-
|
|
56
|
-
onCleanup(() => {
|
|
57
|
-
sr.destroy();
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
onMount(() => {
|
|
62
|
-
if (!props.viewTransitions) {
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const vt = createViewTransitions(props.router);
|
|
67
|
-
|
|
68
|
-
onCleanup(() => {
|
|
69
|
-
vt.destroy();
|
|
51
|
+
handle.destroy();
|
|
70
52
|
});
|
|
71
53
|
});
|
|
54
|
+
}
|
|
72
55
|
|
|
56
|
+
export function RouterProvider(
|
|
57
|
+
props: ParentProps<RouteProviderProps>,
|
|
58
|
+
): JSX.Element {
|
|
59
|
+
// Setup vars FIRST (§8.1 audit fix LOW #2 — semantic ordering): the router
|
|
60
|
+
// subscription wiring is the core of the provider, the opt-in features
|
|
61
|
+
// below ride on top of it.
|
|
73
62
|
const navigator = getNavigator(props.router);
|
|
74
63
|
const routeSource = createRouteSource(props.router);
|
|
75
64
|
const routeSignal = createSignalFromSource(routeSource);
|
|
76
65
|
|
|
77
66
|
const routeSelector = createSelector(
|
|
67
|
+
// The empty-string sentinel guarantees no Link is "active" while the
|
|
68
|
+
// router has no route (unstarted / stopped) — `isRouteActive` short-
|
|
69
|
+
// circuits because no real route name equals or starts with `""` +
|
|
70
|
+
// dot boundary. Without the sentinel, `routeSignal().route?.name`
|
|
71
|
+
// would be `undefined` and the selector would compare against
|
|
72
|
+
// `undefined`, defeating Solid's identity-based change detection.
|
|
78
73
|
() => routeSignal().route?.name ?? "",
|
|
79
74
|
isRouteActive,
|
|
80
75
|
);
|
|
81
76
|
|
|
77
|
+
// Opt-in features wired through the shared mountFeature helper.
|
|
78
|
+
mountFeature(props.announceNavigation, () =>
|
|
79
|
+
createRouteAnnouncer(props.router),
|
|
80
|
+
);
|
|
81
|
+
mountFeature(props.scrollRestoration, () =>
|
|
82
|
+
createScrollRestoration(props.router, props.scrollRestoration),
|
|
83
|
+
);
|
|
84
|
+
mountFeature(props.viewTransitions, () =>
|
|
85
|
+
createViewTransitions(props.router),
|
|
86
|
+
);
|
|
87
|
+
|
|
82
88
|
return (
|
|
83
89
|
<RouterContext.Provider
|
|
84
90
|
value={{ router: props.router, navigator, routeSelector }}
|
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
}
|
package/src/components/Link.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createActiveRouteSource } from "@real-router/sources";
|
|
2
|
-
import { createMemo, mergeProps, splitProps
|
|
2
|
+
import { createMemo, mergeProps, splitProps } from "solid-js";
|
|
3
3
|
|
|
4
4
|
import { EMPTY_PARAMS, EMPTY_OPTIONS } from "../constants";
|
|
5
|
-
import {
|
|
5
|
+
import { useRequiredRouterContext } from "../context";
|
|
6
6
|
import { createSignalFromSource } from "../createSignalFromSource";
|
|
7
7
|
import {
|
|
8
8
|
shouldNavigate,
|
|
@@ -43,23 +43,24 @@ export function Link<P extends Params = Params>(
|
|
|
43
43
|
"children",
|
|
44
44
|
]);
|
|
45
45
|
|
|
46
|
-
const ctx =
|
|
47
|
-
|
|
48
|
-
if (!ctx) {
|
|
49
|
-
throw new Error("Link must be used within a RouterProvider");
|
|
50
|
-
}
|
|
51
|
-
|
|
46
|
+
const ctx = useRequiredRouterContext("Link");
|
|
52
47
|
const router = ctx.router;
|
|
53
48
|
|
|
54
49
|
// Hash-aware active state (#532). `routeSelector` (the O(1) shared selector)
|
|
55
50
|
// doesn't know about hash — when `hash` prop is set, fall back to the slow
|
|
56
51
|
// path so the source's hash comparison kicks in. Tab-style UI is opt-in via
|
|
57
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".
|
|
58
59
|
const useFastPath =
|
|
59
60
|
local.hash === undefined &&
|
|
60
61
|
!local.activeStrict &&
|
|
61
62
|
local.ignoreQueryParams &&
|
|
62
|
-
|
|
63
|
+
props.routeParams === undefined;
|
|
63
64
|
|
|
64
65
|
const buildActiveOptions = () => {
|
|
65
66
|
const base = {
|
|
@@ -85,13 +86,18 @@ export function Link<P extends Params = Params>(
|
|
|
85
86
|
),
|
|
86
87
|
);
|
|
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
|
+
|
|
88
99
|
const href = createMemo(() =>
|
|
89
|
-
buildHref(
|
|
90
|
-
router,
|
|
91
|
-
local.routeName,
|
|
92
|
-
local.routeParams,
|
|
93
|
-
local.hash === undefined ? undefined : { hash: local.hash },
|
|
94
|
-
),
|
|
100
|
+
buildHref(router, local.routeName, local.routeParams, hashOpts()),
|
|
95
101
|
);
|
|
96
102
|
|
|
97
103
|
const handleClick = (evt: MouseEvent) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { children as resolveChildren, createMemo } from "solid-js";
|
|
1
|
+
import { children as resolveChildren, createMemo, For, Show } from "solid-js";
|
|
2
2
|
|
|
3
3
|
import { Match, NotFound, Self } from "./components";
|
|
4
4
|
import { buildRenderList, collectElements } from "./helpers";
|
|
@@ -21,24 +21,25 @@ function RouteViewRoot(props: Readonly<RouteViewProps>): JSX.Element {
|
|
|
21
21
|
return arr;
|
|
22
22
|
});
|
|
23
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
|
+
|
|
24
39
|
return (
|
|
25
|
-
|
|
26
|
-
{(() =>
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (!state.route) {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const rendered = buildRenderList(
|
|
34
|
-
elements(),
|
|
35
|
-
state.route.name,
|
|
36
|
-
props.nodeName,
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
return rendered.length > 0 ? rendered : null;
|
|
40
|
-
})()}
|
|
41
|
-
</>
|
|
40
|
+
<Show when={renderList().length > 0}>
|
|
41
|
+
<For each={renderList()}>{(node) => node}</For>
|
|
42
|
+
</Show>
|
|
42
43
|
);
|
|
43
44
|
}
|
|
44
45
|
|