@tanstack/solid-router 2.0.0-alpha.5 → 2.0.0-alpha.7

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 (219) hide show
  1. package/dist/cjs/Asset.cjs +2 -2
  2. package/dist/cjs/Asset.cjs.map +1 -1
  3. package/dist/cjs/HeadContent.cjs +11 -1
  4. package/dist/cjs/HeadContent.cjs.map +1 -1
  5. package/dist/cjs/HeadContent.dev.cjs +11 -1
  6. package/dist/cjs/HeadContent.dev.cjs.map +1 -1
  7. package/dist/cjs/Match.cjs +265 -248
  8. package/dist/cjs/Match.cjs.map +1 -1
  9. package/dist/cjs/Match.d.cts +1 -3
  10. package/dist/cjs/Matches.cjs +35 -34
  11. package/dist/cjs/Matches.cjs.map +1 -1
  12. package/dist/cjs/RouterProvider.cjs +12 -8
  13. package/dist/cjs/RouterProvider.cjs.map +1 -1
  14. package/dist/cjs/RouterProvider.d.cts +1 -1
  15. package/dist/cjs/Scripts.cjs +23 -12
  16. package/dist/cjs/Scripts.cjs.map +1 -1
  17. package/dist/cjs/Scripts.d.cts +2 -1
  18. package/dist/cjs/Transitioner.cjs +57 -36
  19. package/dist/cjs/Transitioner.cjs.map +1 -1
  20. package/dist/cjs/headContentUtils.cjs +26 -23
  21. package/dist/cjs/headContentUtils.cjs.map +1 -1
  22. package/dist/cjs/headContentUtils.d.cts +2 -1
  23. package/dist/cjs/index.cjs +1 -1
  24. package/dist/cjs/index.dev.cjs +1 -1
  25. package/dist/cjs/link.cjs +143 -101
  26. package/dist/cjs/link.cjs.map +1 -1
  27. package/dist/cjs/matchContext.cjs +7 -5
  28. package/dist/cjs/matchContext.cjs.map +1 -1
  29. package/dist/cjs/matchContext.d.cts +8 -2
  30. package/dist/cjs/not-found.cjs +8 -4
  31. package/dist/cjs/not-found.cjs.map +1 -1
  32. package/dist/cjs/not-found.d.cts +1 -1
  33. package/dist/cjs/router.cjs +2 -1
  34. package/dist/cjs/router.cjs.map +1 -1
  35. package/dist/cjs/routerStores.cjs +75 -0
  36. package/dist/cjs/routerStores.cjs.map +1 -0
  37. package/dist/cjs/routerStores.d.cts +10 -0
  38. package/dist/cjs/ssr/RouterClient.cjs +1 -1
  39. package/dist/cjs/ssr/RouterClient.cjs.map +1 -1
  40. package/dist/cjs/ssr/renderRouterToStream.cjs +1 -1
  41. package/dist/cjs/ssr/renderRouterToStream.cjs.map +1 -1
  42. package/dist/cjs/ssr/renderRouterToString.cjs +1 -1
  43. package/dist/cjs/ssr/renderRouterToString.cjs.map +1 -1
  44. package/dist/cjs/useBlocker.cjs +12 -3
  45. package/dist/cjs/useBlocker.cjs.map +1 -1
  46. package/dist/cjs/useCanGoBack.cjs +6 -2
  47. package/dist/cjs/useCanGoBack.cjs.map +1 -1
  48. package/dist/cjs/useCanGoBack.d.cts +2 -1
  49. package/dist/cjs/useLoaderDeps.cjs +2 -3
  50. package/dist/cjs/useLoaderDeps.cjs.map +1 -1
  51. package/dist/cjs/useLocation.cjs +13 -2
  52. package/dist/cjs/useLocation.cjs.map +1 -1
  53. package/dist/cjs/useMatch.cjs +27 -15
  54. package/dist/cjs/useMatch.cjs.map +1 -1
  55. package/dist/cjs/useParams.cjs +1 -1
  56. package/dist/cjs/useParams.cjs.map +1 -1
  57. package/dist/cjs/useRouterState.cjs +12 -30
  58. package/dist/cjs/useRouterState.cjs.map +1 -1
  59. package/dist/cjs/useSearch.cjs +2 -1
  60. package/dist/cjs/useSearch.cjs.map +1 -1
  61. package/dist/cjs/utils.cjs +3 -17
  62. package/dist/cjs/utils.cjs.map +1 -1
  63. package/dist/cjs/utils.d.cts +0 -5
  64. package/dist/esm/Asset.js +6 -6
  65. package/dist/esm/Asset.js.map +1 -1
  66. package/dist/esm/HeadContent.dev.js +12 -2
  67. package/dist/esm/HeadContent.dev.js.map +1 -1
  68. package/dist/esm/HeadContent.js +12 -2
  69. package/dist/esm/HeadContent.js.map +1 -1
  70. package/dist/esm/Match.d.ts +1 -3
  71. package/dist/esm/Match.js +267 -250
  72. package/dist/esm/Match.js.map +1 -1
  73. package/dist/esm/Matches.js +40 -39
  74. package/dist/esm/Matches.js.map +1 -1
  75. package/dist/esm/RouterProvider.d.ts +1 -1
  76. package/dist/esm/RouterProvider.js +10 -7
  77. package/dist/esm/RouterProvider.js.map +1 -1
  78. package/dist/esm/ScriptOnce.js +2 -2
  79. package/dist/esm/ScriptOnce.js.map +1 -1
  80. package/dist/esm/Scripts.d.ts +2 -1
  81. package/dist/esm/Scripts.js +21 -11
  82. package/dist/esm/Scripts.js.map +1 -1
  83. package/dist/esm/Transitioner.js +58 -37
  84. package/dist/esm/Transitioner.js.map +1 -1
  85. package/dist/esm/headContentUtils.d.ts +2 -1
  86. package/dist/esm/headContentUtils.js +26 -23
  87. package/dist/esm/headContentUtils.js.map +1 -1
  88. package/dist/esm/index.dev.js +1 -1
  89. package/dist/esm/index.js +1 -1
  90. package/dist/esm/link.js +146 -104
  91. package/dist/esm/link.js.map +1 -1
  92. package/dist/esm/matchContext.d.ts +8 -2
  93. package/dist/esm/matchContext.js +7 -4
  94. package/dist/esm/matchContext.js.map +1 -1
  95. package/dist/esm/not-found.d.ts +1 -1
  96. package/dist/esm/not-found.js +6 -3
  97. package/dist/esm/not-found.js.map +1 -1
  98. package/dist/esm/router.js +2 -1
  99. package/dist/esm/router.js.map +1 -1
  100. package/dist/esm/routerStores.d.ts +10 -0
  101. package/dist/esm/routerStores.js +73 -0
  102. package/dist/esm/routerStores.js.map +1 -0
  103. package/dist/esm/scroll-restoration.js +2 -2
  104. package/dist/esm/scroll-restoration.js.map +1 -1
  105. package/dist/esm/ssr/RouterClient.js +1 -1
  106. package/dist/esm/ssr/RouterClient.js.map +1 -1
  107. package/dist/esm/ssr/renderRouterToStream.js +1 -1
  108. package/dist/esm/ssr/renderRouterToStream.js.map +1 -1
  109. package/dist/esm/ssr/renderRouterToString.js +1 -1
  110. package/dist/esm/ssr/renderRouterToString.js.map +1 -1
  111. package/dist/esm/useBlocker.js +12 -3
  112. package/dist/esm/useBlocker.js.map +1 -1
  113. package/dist/esm/useCanGoBack.d.ts +2 -1
  114. package/dist/esm/useCanGoBack.js +4 -2
  115. package/dist/esm/useCanGoBack.js.map +1 -1
  116. package/dist/esm/useLoaderDeps.js +2 -3
  117. package/dist/esm/useLoaderDeps.js.map +1 -1
  118. package/dist/esm/useLocation.js +11 -2
  119. package/dist/esm/useLocation.js.map +1 -1
  120. package/dist/esm/useMatch.js +28 -16
  121. package/dist/esm/useMatch.js.map +1 -1
  122. package/dist/esm/useParams.js +1 -1
  123. package/dist/esm/useParams.js.map +1 -1
  124. package/dist/esm/useRouterState.js +11 -30
  125. package/dist/esm/useRouterState.js.map +1 -1
  126. package/dist/esm/useSearch.js +2 -1
  127. package/dist/esm/useSearch.js.map +1 -1
  128. package/dist/esm/utils.d.ts +0 -5
  129. package/dist/esm/utils.js +4 -17
  130. package/dist/esm/utils.js.map +1 -1
  131. package/dist/source/Asset.jsx +3 -3
  132. package/dist/source/Asset.jsx.map +1 -1
  133. package/dist/source/HeadContent.dev.jsx +5 -1
  134. package/dist/source/HeadContent.dev.jsx.map +1 -1
  135. package/dist/source/HeadContent.jsx +5 -1
  136. package/dist/source/HeadContent.jsx.map +1 -1
  137. package/dist/source/Match.d.ts +1 -3
  138. package/dist/source/Match.jsx +260 -264
  139. package/dist/source/Match.jsx.map +1 -1
  140. package/dist/source/Matches.jsx +46 -46
  141. package/dist/source/Matches.jsx.map +1 -1
  142. package/dist/source/RouterProvider.d.ts +1 -1
  143. package/dist/source/RouterProvider.jsx +13 -9
  144. package/dist/source/RouterProvider.jsx.map +1 -1
  145. package/dist/source/Scripts.d.ts +2 -1
  146. package/dist/source/Scripts.jsx +46 -47
  147. package/dist/source/Scripts.jsx.map +1 -1
  148. package/dist/source/Transitioner.jsx +80 -44
  149. package/dist/source/Transitioner.jsx.map +1 -1
  150. package/dist/source/headContentUtils.d.ts +2 -1
  151. package/dist/source/headContentUtils.jsx +79 -80
  152. package/dist/source/headContentUtils.jsx.map +1 -1
  153. package/dist/source/link.jsx +145 -112
  154. package/dist/source/link.jsx.map +1 -1
  155. package/dist/source/matchContext.d.ts +8 -2
  156. package/dist/source/matchContext.jsx +7 -3
  157. package/dist/source/matchContext.jsx.map +1 -1
  158. package/dist/source/not-found.d.ts +1 -1
  159. package/dist/source/not-found.jsx +6 -5
  160. package/dist/source/not-found.jsx.map +1 -1
  161. package/dist/source/router.js +2 -1
  162. package/dist/source/router.js.map +1 -1
  163. package/dist/source/routerStores.d.ts +10 -0
  164. package/dist/source/routerStores.js +82 -0
  165. package/dist/source/routerStores.js.map +1 -0
  166. package/dist/source/ssr/RouterClient.jsx +1 -1
  167. package/dist/source/ssr/RouterClient.jsx.map +1 -1
  168. package/dist/source/ssr/renderRouterToStream.jsx +1 -1
  169. package/dist/source/ssr/renderRouterToStream.jsx.map +1 -1
  170. package/dist/source/ssr/renderRouterToString.jsx +1 -1
  171. package/dist/source/ssr/renderRouterToString.jsx.map +1 -1
  172. package/dist/source/useBlocker.jsx +19 -8
  173. package/dist/source/useBlocker.jsx.map +1 -1
  174. package/dist/source/useCanGoBack.d.ts +2 -1
  175. package/dist/source/useCanGoBack.js +4 -2
  176. package/dist/source/useCanGoBack.js.map +1 -1
  177. package/dist/source/useLoaderDeps.jsx +2 -3
  178. package/dist/source/useLoaderDeps.jsx.map +1 -1
  179. package/dist/source/useLocation.jsx +13 -3
  180. package/dist/source/useLocation.jsx.map +1 -1
  181. package/dist/source/useMatch.jsx +33 -23
  182. package/dist/source/useMatch.jsx.map +1 -1
  183. package/dist/source/useParams.jsx +1 -1
  184. package/dist/source/useParams.jsx.map +1 -1
  185. package/dist/source/useRouterState.jsx +14 -55
  186. package/dist/source/useRouterState.jsx.map +1 -1
  187. package/dist/source/useSearch.jsx +2 -1
  188. package/dist/source/useSearch.jsx.map +1 -1
  189. package/dist/source/utils.d.ts +0 -5
  190. package/dist/source/utils.js +2 -15
  191. package/dist/source/utils.js.map +1 -1
  192. package/package.json +7 -7
  193. package/skills/solid-router/SKILL.md +2 -0
  194. package/src/Asset.tsx +3 -3
  195. package/src/HeadContent.dev.tsx +10 -1
  196. package/src/HeadContent.tsx +10 -1
  197. package/src/Match.tsx +395 -349
  198. package/src/Matches.tsx +55 -54
  199. package/src/RouterProvider.tsx +13 -10
  200. package/src/Scripts.tsx +55 -54
  201. package/src/Transitioner.tsx +103 -60
  202. package/src/headContentUtils.tsx +104 -96
  203. package/src/link.tsx +188 -146
  204. package/src/matchContext.tsx +16 -7
  205. package/src/not-found.tsx +6 -6
  206. package/src/router.ts +2 -1
  207. package/src/routerStores.ts +119 -0
  208. package/src/ssr/RouterClient.tsx +1 -1
  209. package/src/ssr/renderRouterToStream.tsx +1 -1
  210. package/src/ssr/renderRouterToString.tsx +1 -1
  211. package/src/useBlocker.tsx +80 -63
  212. package/src/useCanGoBack.ts +6 -2
  213. package/src/useLoaderDeps.tsx +2 -3
  214. package/src/useLocation.tsx +18 -5
  215. package/src/useMatch.tsx +37 -38
  216. package/src/useParams.tsx +2 -3
  217. package/src/useRouterState.tsx +21 -67
  218. package/src/useSearch.tsx +2 -1
  219. package/src/utils.ts +2 -24
@@ -1,28 +1,42 @@
1
1
  import { useRouter } from "./useRouter.js";
2
- import { useRouterState } from "./useRouterState.js";
3
- import { usePrevious } from "./utils.js";
4
- import { getLocationChangeInfo, handleHashScroll, trimPathRight } from "@tanstack/router-core";
2
+ import { getLocationChangeInfo, trimPathRight } from "@tanstack/router-core";
5
3
  import * as Solid from "solid-js";
6
4
  //#region src/Transitioner.tsx
5
+ /**
6
+ * Inline version of handleHashScroll that accepts a pre-captured location
7
+ * to avoid reading router.stores.location.state inside an effect callback
8
+ * (which would trigger a Solid v2 reactive warning).
9
+ */
10
+ function handleHashScrollWithLocation(_router, location) {
11
+ if (typeof document !== "undefined" && document.querySelector) {
12
+ const hashScrollIntoViewOptions = location.state.__hashScrollIntoViewOptions ?? true;
13
+ if (hashScrollIntoViewOptions && location.hash !== "") {
14
+ const el = document.getElementById(location.hash);
15
+ if (el) el.scrollIntoView(hashScrollIntoViewOptions);
16
+ }
17
+ }
18
+ }
7
19
  function Transitioner() {
8
20
  const router = useRouter();
9
21
  let mountLoadForRouter = {
10
22
  router,
11
23
  mounted: false
12
24
  };
13
- const isLoading = useRouterState({ select: ({ isLoading }) => isLoading });
25
+ const isLoading = Solid.createMemo(() => router.stores.isLoading.state);
14
26
  const [isSolidTransitioning] = [() => false];
15
- const hasPendingMatches = useRouterState({ select: (s) => s.matches.some((d) => d.status === "pending") });
16
- const previousIsLoading = usePrevious(isLoading);
17
- const isAnyPending = () => isLoading() || isSolidTransitioning() || hasPendingMatches();
18
- const previousIsAnyPending = usePrevious(isAnyPending);
19
- const isPagePending = () => isLoading() || hasPendingMatches();
20
- const previousIsPagePending = usePrevious(isPagePending);
27
+ const hasPendingMatches = Solid.createMemo(() => router.stores.hasPendingMatches.state);
28
+ const isAnyPending = Solid.createMemo(() => isLoading() || isSolidTransitioning() || hasPendingMatches());
29
+ const isPagePending = Solid.createMemo(() => isLoading() || hasPendingMatches());
21
30
  router.startTransition = (fn) => {
22
31
  Solid.runWithOwner(null, fn);
32
+ try {
33
+ Solid.flush();
34
+ } catch {}
23
35
  };
24
36
  Solid.onSettled(() => {
25
- const unsub = router.history.subscribe(router.load);
37
+ const unsub = router.history.subscribe(() => {
38
+ queueMicrotask(() => router.load());
39
+ });
26
40
  router.updateLatestLocation();
27
41
  const nextLocation = router.buildLocation({
28
42
  to: router.latestLocation.pathname,
@@ -36,17 +50,17 @@ function Transitioner() {
36
50
  ...nextLocation,
37
51
  replace: true
38
52
  });
39
- Solid.onCleanup(() => {
53
+ return () => {
40
54
  unsub();
41
- });
55
+ };
42
56
  });
43
- Solid.createTrackedEffect(() => {
44
- Solid.untrack(() => {
45
- if (typeof window !== "undefined" && router.ssr || mountLoadForRouter.router === router && mountLoadForRouter.mounted) return;
46
- mountLoadForRouter = {
47
- router,
48
- mounted: true
49
- };
57
+ Solid.onSettled(() => {
58
+ if (typeof window !== "undefined" && router.ssr || mountLoadForRouter.router === router && mountLoadForRouter.mounted) return;
59
+ mountLoadForRouter = {
60
+ router,
61
+ mounted: true
62
+ };
63
+ queueMicrotask(() => {
50
64
  const tryLoad = async () => {
51
65
  try {
52
66
  await router.load();
@@ -57,31 +71,38 @@ function Transitioner() {
57
71
  tryLoad();
58
72
  });
59
73
  });
60
- Solid.createEffect(() => [previousIsLoading(), isLoading()], ([previousIsLoading, isLoading]) => {
61
- if (previousIsLoading.previous && !isLoading) router.emit({
74
+ Solid.createRenderEffect(() => [
75
+ isLoading(),
76
+ isPagePending(),
77
+ isAnyPending(),
78
+ router.stores.location.state,
79
+ router.stores.resolvedLocation.state
80
+ ], ([currentIsLoading, currentIsPagePending, currentIsAnyPending, loc, resolvedLoc], prev) => {
81
+ if (!loc) return;
82
+ const previousIsLoading = prev?.[0];
83
+ const previousIsPagePending = prev?.[1];
84
+ const previousIsAnyPending = prev?.[2];
85
+ if (previousIsLoading && !currentIsLoading) router.emit({
62
86
  type: "onLoad",
63
- ...getLocationChangeInfo(router.state)
87
+ ...getLocationChangeInfo(loc, resolvedLoc)
64
88
  });
65
- });
66
- Solid.createEffect(() => [isPagePending(), previousIsPagePending()], ([isPagePending, previousIsPagePending]) => {
67
- if (previousIsPagePending.previous && !isPagePending) router.emit({
89
+ if (previousIsPagePending && !currentIsPagePending) router.emit({
68
90
  type: "onBeforeRouteMount",
69
- ...getLocationChangeInfo(router.state)
91
+ ...getLocationChangeInfo(loc, resolvedLoc)
70
92
  });
71
- });
72
- Solid.createEffect(() => [isAnyPending(), previousIsAnyPending()], ([isAnyPending, previousIsAnyPending]) => {
73
- if (previousIsAnyPending.previous && !isAnyPending) {
74
- const changeInfo = getLocationChangeInfo(router.state);
93
+ if (previousIsAnyPending && !currentIsAnyPending) {
94
+ const changeInfo = getLocationChangeInfo(loc, resolvedLoc);
75
95
  router.emit({
76
96
  type: "onResolved",
77
97
  ...changeInfo
78
98
  });
79
- router.__store.setState((s) => ({
80
- ...s,
81
- status: "idle",
82
- resolvedLocation: s.location
83
- }));
84
- if (changeInfo.hrefChanged) handleHashScroll(router);
99
+ Solid.runWithOwner(null, () => {
100
+ router.batch(() => {
101
+ router.stores.status.setState(() => "idle");
102
+ router.stores.resolvedLocation.setState(() => loc);
103
+ });
104
+ });
105
+ if (changeInfo.hrefChanged) handleHashScrollWithLocation(router, loc);
85
106
  }
86
107
  });
87
108
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"Transitioner.js","names":["Solid","getLocationChangeInfo","handleHashScroll","trimPathRight","useRouter","useRouterState","usePrevious","Transitioner","router","mountLoadForRouter","mounted","isLoading","select","isSolidTransitioning","hasPendingMatches","s","matches","some","d","status","previousIsLoading","isAnyPending","previousIsAnyPending","isPagePending","previousIsPagePending","startTransition","fn","Promise","runWithOwner","onSettled","unsub","history","subscribe","load","updateLatestLocation","nextLocation","buildLocation","to","latestLocation","pathname","search","params","hash","state","_includeValidateSearch","publicHref","commitLocation","replace","onCleanup","createTrackedEffect","untrack","window","ssr","tryLoad","err","console","error","createEffect","const","previous","emit","type","changeInfo","__store","setState","resolvedLocation","location","hrefChanged"],"sources":["../../src/Transitioner.tsx"],"sourcesContent":["import * as Solid from 'solid-js'\nimport {\n getLocationChangeInfo,\n handleHashScroll,\n trimPathRight,\n} from '@tanstack/router-core'\nimport { useRouter } from './useRouter'\nimport { useRouterState } from './useRouterState'\nimport { usePrevious } from './utils'\n\nexport function Transitioner() {\n const router = useRouter()\n let mountLoadForRouter = { router, mounted: false }\n const isLoading = useRouterState({\n select: ({ isLoading }) => isLoading,\n })\n\n const [isSolidTransitioning] = [() => false]\n\n // Track pending state changes\n const hasPendingMatches = useRouterState({\n select: (s) => s.matches.some((d) => d.status === 'pending'),\n })\n\n const previousIsLoading = usePrevious(isLoading)\n\n const isAnyPending = () =>\n isLoading() || isSolidTransitioning() || hasPendingMatches()\n const previousIsAnyPending = usePrevious(isAnyPending)\n\n const isPagePending = () => isLoading() || hasPendingMatches()\n const previousIsPagePending = usePrevious(isPagePending)\n\n router.startTransition = (fn: () => void | Promise<void>) => {\n Solid.runWithOwner(null, fn)\n }\n\n // Subscribe to location changes\n // and try to load the new location\n Solid.onSettled(() => {\n const unsub = router.history.subscribe(router.load)\n\n // Refresh latestLocation from the current browser URL before comparing.\n // The URL may have been changed synchronously (e.g. via replaceState) after\n // render() but before this effect ran, so we must not use the stale\n // render-time location here.\n router.updateLatestLocation()\n\n const nextLocation = router.buildLocation({\n to: router.latestLocation.pathname,\n search: true,\n params: true,\n hash: true,\n state: true,\n _includeValidateSearch: true,\n })\n\n // Check if the current URL matches the canonical form.\n // Compare publicHref (browser-facing URL) for consistency with\n // the server-side redirect check in router.beforeLoad.\n if (\n trimPathRight(router.latestLocation.publicHref) !==\n trimPathRight(nextLocation.publicHref)\n ) {\n router.commitLocation({ ...nextLocation, replace: true })\n }\n\n Solid.onCleanup(() => {\n unsub()\n })\n })\n\n // Try to load the initial location\n Solid.createTrackedEffect(() => {\n Solid.untrack(() => {\n if (\n // if we are hydrating from SSR, loading is triggered in ssr-client\n (typeof window !== 'undefined' && router.ssr) ||\n (mountLoadForRouter.router === router && mountLoadForRouter.mounted)\n ) {\n return\n }\n mountLoadForRouter = { router, mounted: true }\n const tryLoad = async () => {\n try {\n await router.load()\n } catch (err) {\n console.error(err)\n }\n }\n tryLoad()\n })\n })\n\n Solid.createEffect(\n () => [previousIsLoading(), isLoading()] as const,\n ([previousIsLoading, isLoading]) => {\n if (previousIsLoading.previous && !isLoading) {\n router.emit({\n type: 'onLoad',\n ...getLocationChangeInfo(router.state),\n })\n }\n },\n )\n\n Solid.createEffect(\n () => [isPagePending(), previousIsPagePending()] as const,\n ([isPagePending, previousIsPagePending]) => {\n // emit onBeforeRouteMount\n if (previousIsPagePending.previous && !isPagePending) {\n router.emit({\n type: 'onBeforeRouteMount',\n ...getLocationChangeInfo(router.state),\n })\n }\n },\n )\n\n Solid.createEffect(\n () => [isAnyPending(), previousIsAnyPending()] as const,\n ([isAnyPending, previousIsAnyPending]) => {\n if (previousIsAnyPending.previous && !isAnyPending) {\n const changeInfo = getLocationChangeInfo(router.state)\n router.emit({\n type: 'onResolved',\n ...changeInfo,\n })\n\n router.__store.setState((s) => ({\n ...s,\n status: 'idle',\n resolvedLocation: s.location,\n }))\n\n if (changeInfo.hrefChanged) {\n handleHashScroll(router)\n }\n }\n },\n )\n\n return null\n}\n"],"mappings":";;;;;;AAUA,SAAgBO,eAAe;CAC7B,MAAMC,SAASJ,WAAW;CAC1B,IAAIK,qBAAqB;EAAED;EAAQE,SAAS;EAAO;CACnD,MAAMC,YAAYN,eAAe,EAC/BO,SAAS,EAAED,gBAAgBA,WAC5B,CAAC;CAEF,MAAM,CAACE,wBAAwB,OAAO,MAAM;CAG5C,MAAMC,oBAAoBT,eAAe,EACvCO,SAASG,MAAMA,EAAEC,QAAQC,MAAMC,MAAMA,EAAEC,WAAW,UAAS,EAC5D,CAAC;CAEF,MAAMC,oBAAoBd,YAAYK,UAAU;CAEhD,MAAMU,qBACJV,WAAW,IAAIE,sBAAsB,IAAIC,mBAAmB;CAC9D,MAAMQ,uBAAuBhB,YAAYe,aAAa;CAEtD,MAAME,sBAAsBZ,WAAW,IAAIG,mBAAmB;CAC9D,MAAMU,wBAAwBlB,YAAYiB,cAAc;AAExDf,QAAOiB,mBAAmBC,OAAmC;AAC3D1B,QAAM4B,aAAa,MAAMF,GAAG;;AAK9B1B,OAAM6B,gBAAgB;EACpB,MAAMC,QAAQtB,OAAOuB,QAAQC,UAAUxB,OAAOyB,KAAK;AAMnDzB,SAAO0B,sBAAsB;EAE7B,MAAMC,eAAe3B,OAAO4B,cAAc;GACxCC,IAAI7B,OAAO8B,eAAeC;GAC1BC,QAAQ;GACRC,QAAQ;GACRC,MAAM;GACNC,OAAO;GACPC,wBAAwB;GACzB,CAAC;AAKF,MACEzC,cAAcK,OAAO8B,eAAeO,WAAW,KAC/C1C,cAAcgC,aAAaU,WAAW,CAEtCrC,QAAOsC,eAAe;GAAE,GAAGX;GAAcY,SAAS;GAAM,CAAC;AAG3D/C,QAAMgD,gBAAgB;AACpBlB,UAAO;IACP;GACF;AAGF9B,OAAMiD,0BAA0B;AAC9BjD,QAAMkD,cAAc;AAClB,OAEG,OAAOC,WAAW,eAAe3C,OAAO4C,OACxC3C,mBAAmBD,WAAWA,UAAUC,mBAAmBC,QAE5D;AAEFD,wBAAqB;IAAED;IAAQE,SAAS;IAAM;GAC9C,MAAM2C,UAAU,YAAY;AAC1B,QAAI;AACF,WAAM7C,OAAOyB,MAAM;aACZqB,KAAK;AACZC,aAAQC,MAAMF,IAAI;;;AAGtBD,YAAS;IACT;GACF;AAEFrD,OAAMyD,mBACE,CAACrC,mBAAmB,EAAET,WAAW,CAAC,GACvC,CAACS,mBAAmBT,eAAe;AAClC,MAAIS,kBAAkBuC,YAAY,CAAChD,UACjCH,QAAOoD,KAAK;GACVC,MAAM;GACN,GAAG5D,sBAAsBO,OAAOmC,MAAK;GACtC,CAAC;GAGP;AAED3C,OAAMyD,mBACE,CAAClC,eAAe,EAAEC,uBAAuB,CAAC,GAC/C,CAACD,eAAeC,2BAA2B;AAE1C,MAAIA,sBAAsBmC,YAAY,CAACpC,cACrCf,QAAOoD,KAAK;GACVC,MAAM;GACN,GAAG5D,sBAAsBO,OAAOmC,MAAK;GACtC,CAAC;GAGP;AAED3C,OAAMyD,mBACE,CAACpC,cAAc,EAAEC,sBAAsB,CAAC,GAC7C,CAACD,cAAcC,0BAA0B;AACxC,MAAIA,qBAAqBqC,YAAY,CAACtC,cAAc;GAClD,MAAMyC,aAAa7D,sBAAsBO,OAAOmC,MAAM;AACtDnC,UAAOoD,KAAK;IACVC,MAAM;IACN,GAAGC;IACJ,CAAC;AAEFtD,UAAOuD,QAAQC,UAAUjD,OAAO;IAC9B,GAAGA;IACHI,QAAQ;IACR8C,kBAAkBlD,EAAEmD;IACrB,EAAE;AAEH,OAAIJ,WAAWK,YACbjE,kBAAiBM,OAAO;;GAI/B;AAED,QAAO"}
1
+ {"version":3,"file":"Transitioner.js","names":["Solid","getLocationChangeInfo","trimPathRight","useRouter","ParsedLocation","handleHashScrollWithLocation","_router","location","document","querySelector","hashScrollIntoViewOptions","state","__hashScrollIntoViewOptions","hash","el","getElementById","scrollIntoView","Transitioner","router","mountLoadForRouter","mounted","isLoading","createMemo","stores","isSolidTransitioning","hasPendingMatches","isAnyPending","isPagePending","startTransition","fn","Promise","runWithOwner","flush","onSettled","unsub","history","subscribe","queueMicrotask","load","updateLatestLocation","nextLocation","buildLocation","to","latestLocation","pathname","search","params","_includeValidateSearch","publicHref","commitLocation","replace","window","ssr","tryLoad","err","console","error","createRenderEffect","resolvedLocation","const","currentIsLoading","currentIsPagePending","currentIsAnyPending","loc","resolvedLoc","prev","previousIsLoading","previousIsPagePending","previousIsAnyPending","emit","type","changeInfo","batch","status","setState","hrefChanged"],"sources":["../../src/Transitioner.tsx"],"sourcesContent":["import * as Solid from 'solid-js'\nimport { getLocationChangeInfo, trimPathRight } from '@tanstack/router-core'\nimport { useRouter } from './useRouter'\nimport type { ParsedLocation } from '@tanstack/router-core'\n\n/**\n * Inline version of handleHashScroll that accepts a pre-captured location\n * to avoid reading router.stores.location.state inside an effect callback\n * (which would trigger a Solid v2 reactive warning).\n */\nfunction handleHashScrollWithLocation(_router: any, location: ParsedLocation) {\n if (typeof document !== 'undefined' && (document as any).querySelector) {\n const hashScrollIntoViewOptions =\n location.state.__hashScrollIntoViewOptions ?? true\n\n if (hashScrollIntoViewOptions && location.hash !== '') {\n const el = document.getElementById(location.hash)\n if (el) {\n el.scrollIntoView(hashScrollIntoViewOptions)\n }\n }\n }\n}\n\nexport function Transitioner() {\n const router = useRouter()\n let mountLoadForRouter = { router, mounted: false }\n const isLoading = Solid.createMemo(() => router.stores.isLoading.state)\n\n const [isSolidTransitioning] = [() => false]\n\n // Track pending state changes\n const hasPendingMatches = Solid.createMemo(\n () => router.stores.hasPendingMatches.state,\n )\n\n const isAnyPending = Solid.createMemo(\n () => isLoading() || isSolidTransitioning() || hasPendingMatches(),\n )\n\n const isPagePending = Solid.createMemo(\n () => isLoading() || hasPendingMatches(),\n )\n\n router.startTransition = (fn: () => void | Promise<void>) => {\n Solid.runWithOwner(null, fn)\n try {\n Solid.flush()\n } catch {\n // flush() throws inside reactive contexts — Solid auto-flushes there\n }\n }\n\n // Subscribe to location changes\n // and try to load the new location\n Solid.onSettled(() => {\n const unsub = router.history.subscribe(() => {\n queueMicrotask(() => router.load())\n })\n\n // Refresh latestLocation from the current browser URL before comparing.\n // The URL may have been changed synchronously (e.g. via replaceState) after\n // render() but before this effect ran, so we must not use the stale\n // render-time location here.\n router.updateLatestLocation()\n\n const nextLocation = router.buildLocation({\n to: router.latestLocation.pathname,\n search: true,\n params: true,\n hash: true,\n state: true,\n _includeValidateSearch: true,\n })\n\n // Check if the current URL matches the canonical form.\n // Compare publicHref (browser-facing URL) for consistency with\n // the server-side redirect check in router.beforeLoad.\n if (\n trimPathRight(router.latestLocation.publicHref) !==\n trimPathRight(nextLocation.publicHref)\n ) {\n router.commitLocation({ ...nextLocation, replace: true })\n }\n\n return () => {\n unsub()\n }\n })\n\n // Try to load the initial location\n // In Solid v2, signal updates inside onSettled cannot be flushed\n // synchronously (flush() throws). router.load() sets signals via batch(),\n // and the code that runs immediately after needs those values committed.\n // By deferring to queueMicrotask, the load runs outside the reactive\n // scheduling frame so flush() works correctly.\n Solid.onSettled(() => {\n if (\n // if we are hydrating from SSR, loading is triggered in ssr-client\n (typeof window !== 'undefined' && router.ssr) ||\n (mountLoadForRouter.router === router && mountLoadForRouter.mounted)\n ) {\n return\n }\n mountLoadForRouter = { router, mounted: true }\n queueMicrotask(() => {\n const tryLoad = async () => {\n try {\n await router.load()\n } catch (err) {\n console.error(err)\n }\n }\n tryLoad()\n })\n })\n Solid.createRenderEffect(\n () =>\n [\n isLoading(),\n isPagePending(),\n isAnyPending(),\n router.stores.location.state,\n router.stores.resolvedLocation.state,\n ] as const,\n (\n [\n currentIsLoading,\n currentIsPagePending,\n currentIsAnyPending,\n loc,\n resolvedLoc,\n ],\n prev,\n ) => {\n // Guard: if location state isn't available yet, skip all event emissions\n if (!loc) return\n\n const previousIsLoading = prev?.[0]\n const previousIsPagePending = prev?.[1]\n const previousIsAnyPending = prev?.[2]\n\n // onLoad: when the router finishes loading\n if (previousIsLoading && !currentIsLoading) {\n router.emit({\n type: 'onLoad',\n ...getLocationChangeInfo(loc, resolvedLoc),\n })\n }\n\n // onBeforeRouteMount: must fire before onResolved\n if (previousIsPagePending && !currentIsPagePending) {\n router.emit({\n type: 'onBeforeRouteMount',\n ...getLocationChangeInfo(loc, resolvedLoc),\n })\n }\n\n // onResolved: fires after onBeforeRouteMount\n if (previousIsAnyPending && !currentIsAnyPending) {\n const changeInfo = getLocationChangeInfo(loc, resolvedLoc)\n router.emit({\n type: 'onResolved',\n ...changeInfo,\n })\n\n Solid.runWithOwner(null, () => {\n router.batch(() => {\n router.stores.status.setState(() => 'idle')\n // Use `loc` from the source tuple to avoid reading\n // router.stores.location.state inside the effect callback\n router.stores.resolvedLocation.setState(() => loc)\n })\n })\n\n if (changeInfo.hrefChanged) {\n // Pass the already-captured location to avoid a reactive read\n // inside the effect callback (handleHashScroll would otherwise\n // read router.stores.location.state which triggers a warning)\n handleHashScrollWithLocation(router, loc)\n }\n }\n },\n )\n\n return null\n}\n"],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  import { RouterManagedTag } from '@tanstack/router-core';
2
+ import * as Solid from 'solid-js';
2
3
  /**
3
4
  * Build the list of head/link/meta/script tags to render for active matches.
4
5
  * Used internally by `HeadContent`.
5
6
  */
6
- export declare const useTags: () => () => RouterManagedTag[];
7
+ export declare const useTags: () => Solid.Accessor<RouterManagedTag[]>;
7
8
  export declare function uniqBy<T>(arr: Array<T>, fn: (item: T) => string): T[];
@@ -1,5 +1,4 @@
1
1
  import { useRouter } from "./useRouter.js";
2
- import { useRouterState } from "./useRouterState.js";
3
2
  import { escapeHtml } from "@tanstack/router-core";
4
3
  import * as Solid from "solid-js";
5
4
  //#region src/headContentUtils.tsx
@@ -10,9 +9,9 @@ import * as Solid from "solid-js";
10
9
  var useTags = () => {
11
10
  const router = useRouter();
12
11
  const nonce = router.options.ssr?.nonce;
13
- const routeMeta = useRouterState({ select: (state) => {
14
- return state.matches.map((match) => match.meta).filter(Boolean);
15
- } });
12
+ const getTagKey = (tag) => JSON.stringify(tag);
13
+ const activeMatches = Solid.createMemo(() => router.stores.activeMatchesSnapshot.state);
14
+ const routeMeta = Solid.createMemo(() => activeMatches().map((match) => match.meta).filter(Boolean));
16
15
  const meta = Solid.createMemo(() => {
17
16
  const resultMeta = [];
18
17
  const metaByAttribute = {};
@@ -61,8 +60,9 @@ var useTags = () => {
61
60
  resultMeta.reverse();
62
61
  return resultMeta;
63
62
  });
64
- const links = useRouterState({ select: (state) => {
65
- const constructed = state.matches.map((match) => match.links).filter(Boolean).flat(1).map((link) => ({
63
+ const links = Solid.createMemo(() => {
64
+ const matches = activeMatches();
65
+ const constructed = matches.map((match) => match.links).filter(Boolean).flat(1).map((link) => ({
66
66
  tag: "link",
67
67
  attrs: {
68
68
  ...link,
@@ -70,7 +70,7 @@ var useTags = () => {
70
70
  }
71
71
  }));
72
72
  const manifest = router.ssr?.manifest;
73
- const assets = state.matches.map((match) => manifest?.routes[match.routeId]?.assets ?? []).filter(Boolean).flat(1).filter((asset) => asset.tag === "link").map((asset) => ({
73
+ const assets = matches.map((match) => manifest?.routes[match.routeId]?.assets ?? []).filter(Boolean).flat(1).filter((asset) => asset.tag === "link").map((asset) => ({
74
74
  tag: "link",
75
75
  attrs: {
76
76
  ...asset.attrs,
@@ -78,10 +78,11 @@ var useTags = () => {
78
78
  }
79
79
  }));
80
80
  return [...constructed, ...assets];
81
- } });
82
- const preloadLinks = useRouterState({ select: (state) => {
81
+ });
82
+ const preloadLinks = Solid.createMemo(() => {
83
+ const matches = activeMatches();
83
84
  const preloadLinks = [];
84
- state.matches.map((match) => router.looseRoutesById[match.routeId]).forEach((route) => router.ssr?.manifest?.routes[route.id]?.preloads?.filter(Boolean).forEach((preload) => {
85
+ matches.map((match) => router.looseRoutesById[match.routeId]).forEach((route) => router.ssr?.manifest?.routes[route.id]?.preloads?.filter(Boolean).forEach((preload) => {
85
86
  preloadLinks.push({
86
87
  tag: "link",
87
88
  attrs: {
@@ -92,31 +93,33 @@ var useTags = () => {
92
93
  });
93
94
  }));
94
95
  return preloadLinks;
95
- } });
96
- const styles = useRouterState({ select: (state) => state.matches.map((match) => match.styles).flat(1).filter(Boolean).map(({ children, ...style }) => ({
96
+ });
97
+ const styles = Solid.createMemo(() => activeMatches().map((match) => match.styles).flat(1).filter(Boolean).map(({ children, ...style }) => ({
97
98
  tag: "style",
98
99
  attrs: {
99
100
  ...style,
100
101
  nonce
101
102
  },
102
103
  children
103
- })) });
104
- const headScripts = useRouterState({ select: (state) => state.matches.map((match) => match.headScripts).flat(1).filter(Boolean).map(({ children, ...script }) => ({
104
+ })));
105
+ const headScripts = Solid.createMemo(() => activeMatches().map((match) => match.headScripts).flat(1).filter(Boolean).map(({ children, ...script }) => ({
105
106
  tag: "script",
106
107
  attrs: {
107
108
  ...script,
108
109
  nonce
109
110
  },
110
111
  children
111
- })) });
112
- return () => uniqBy([
113
- ...meta(),
114
- ...preloadLinks(),
115
- ...links(),
116
- ...styles(),
117
- ...headScripts()
118
- ], (d) => {
119
- return JSON.stringify(d);
112
+ })));
113
+ return Solid.createMemo((prev) => {
114
+ const next = uniqBy([
115
+ ...meta(),
116
+ ...preloadLinks(),
117
+ ...links(),
118
+ ...styles(),
119
+ ...headScripts()
120
+ ], getTagKey);
121
+ if (prev && prev.length === next.length && prev.every((tag, index) => getTagKey(tag) === getTagKey(next[index]))) return prev;
122
+ return next;
120
123
  });
121
124
  };
122
125
  function uniqBy(arr, fn) {
@@ -1 +1 @@
1
- {"version":3,"file":"headContentUtils.js","names":["Solid","escapeHtml","useRouter","useRouterState","RouterManagedTag","useTags","router","nonce","options","ssr","routeMeta","select","state","matches","map","match","meta","filter","Boolean","Accessor","Array","createMemo","resultMeta","metaByAttribute","Record","title","routeMetasArray","i","length","metas","j","m","tag","children","json","JSON","stringify","push","attrs","type","attribute","name","property","content","reverse","links","constructed","flat","link","manifest","assets","routes","routeId","asset","preloadLinks","looseRoutesById","forEach","route","id","preloads","preload","rel","href","styles","style","headScripts","script","uniqBy","d","arr","T","fn","item","seen","Set","key","has","add"],"sources":["../../src/headContentUtils.tsx"],"sourcesContent":["import * as Solid from 'solid-js'\nimport { escapeHtml } from '@tanstack/router-core'\nimport { useRouter } from './useRouter'\nimport { useRouterState } from './useRouterState'\nimport type { RouterManagedTag } from '@tanstack/router-core'\n\n/**\n * Build the list of head/link/meta/script tags to render for active matches.\n * Used internally by `HeadContent`.\n */\nexport const useTags = () => {\n const router = useRouter()\n const nonce = router.options.ssr?.nonce\n const routeMeta = useRouterState({\n select: (state) => {\n return state.matches.map((match) => match.meta!).filter(Boolean)\n },\n })\n\n const meta: Solid.Accessor<Array<RouterManagedTag>> = Solid.createMemo(() => {\n const resultMeta: Array<RouterManagedTag> = []\n const metaByAttribute: Record<string, true> = {}\n let title: RouterManagedTag | undefined\n const routeMetasArray = routeMeta()\n for (let i = routeMetasArray.length - 1; i >= 0; i--) {\n const metas = routeMetasArray[i]!\n for (let j = metas.length - 1; j >= 0; j--) {\n const m = metas[j]\n if (!m) continue\n\n if (m.title) {\n if (!title) {\n title = {\n tag: 'title',\n children: m.title,\n }\n }\n } else if ('script:ld+json' in m) {\n // Handle JSON-LD structured data\n // Content is HTML-escaped to prevent XSS when injected via innerHTML\n try {\n const json = JSON.stringify(m['script:ld+json'])\n resultMeta.push({\n tag: 'script',\n attrs: {\n type: 'application/ld+json',\n },\n children: escapeHtml(json),\n })\n } catch {\n // Skip invalid JSON-LD objects\n }\n } else {\n const attribute = m.name ?? m.property\n if (attribute) {\n if (metaByAttribute[attribute]) {\n continue\n } else {\n metaByAttribute[attribute] = true\n }\n }\n\n resultMeta.push({\n tag: 'meta',\n attrs: {\n ...m,\n nonce,\n },\n })\n }\n }\n }\n\n if (title) {\n resultMeta.push(title)\n }\n\n if (router.options.ssr?.nonce) {\n resultMeta.push({\n tag: 'meta',\n attrs: {\n property: 'csp-nonce',\n content: router.options.ssr.nonce,\n },\n })\n }\n resultMeta.reverse()\n\n return resultMeta\n })\n\n const links = useRouterState({\n select: (state) => {\n const constructed = state.matches\n .map((match) => match.links!)\n .filter(Boolean)\n .flat(1)\n .map((link) => ({\n tag: 'link',\n attrs: {\n ...link,\n nonce,\n },\n })) satisfies Array<RouterManagedTag>\n\n const manifest = router.ssr?.manifest\n\n const assets = state.matches\n .map((match) => manifest?.routes[match.routeId]?.assets ?? [])\n .filter(Boolean)\n .flat(1)\n .filter((asset) => asset.tag === 'link')\n .map(\n (asset) =>\n ({\n tag: 'link',\n attrs: { ...asset.attrs, nonce },\n }) satisfies RouterManagedTag,\n )\n\n return [...constructed, ...assets]\n },\n })\n\n const preloadLinks = useRouterState({\n select: (state) => {\n const preloadLinks: Array<RouterManagedTag> = []\n\n state.matches\n .map((match) => router.looseRoutesById[match.routeId]!)\n .forEach((route) =>\n router.ssr?.manifest?.routes[route.id]?.preloads\n ?.filter(Boolean)\n .forEach((preload) => {\n preloadLinks.push({\n tag: 'link',\n attrs: {\n rel: 'modulepreload',\n href: preload,\n nonce,\n },\n })\n }),\n )\n\n return preloadLinks\n },\n })\n\n const styles = useRouterState({\n select: (state) =>\n (\n state.matches\n .map((match) => match.styles!)\n .flat(1)\n .filter(Boolean) as Array<RouterManagedTag>\n ).map(({ children, ...style }) => ({\n tag: 'style',\n attrs: {\n ...style,\n nonce,\n },\n children,\n })),\n })\n\n const headScripts = useRouterState({\n select: (state) =>\n (\n state.matches\n .map((match) => match.headScripts!)\n .flat(1)\n .filter(Boolean) as Array<RouterManagedTag>\n ).map(({ children, ...script }) => ({\n tag: 'script',\n attrs: {\n ...script,\n nonce,\n },\n children,\n })),\n })\n\n return () =>\n uniqBy(\n [\n ...meta(),\n ...preloadLinks(),\n ...links(),\n ...styles(),\n ...headScripts(),\n ] as Array<RouterManagedTag>,\n (d) => {\n return JSON.stringify(d)\n },\n )\n}\n\nexport function uniqBy<T>(arr: Array<T>, fn: (item: T) => string) {\n const seen = new Set<string>()\n return arr.filter((item) => {\n const key = fn(item)\n if (seen.has(key)) {\n return false\n }\n seen.add(key)\n return true\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"headContentUtils.js","names":["Solid","escapeHtml","useRouter","RouterManagedTag","useTags","router","nonce","options","ssr","getTagKey","tag","JSON","stringify","activeMatches","createMemo","stores","activeMatchesSnapshot","state","routeMeta","map","match","meta","filter","Boolean","Accessor","Array","resultMeta","metaByAttribute","Record","title","routeMetasArray","i","length","metas","j","m","children","json","push","attrs","type","attribute","name","property","content","reverse","links","matches","constructed","flat","link","manifest","assets","routes","routeId","asset","preloadLinks","looseRoutesById","forEach","route","id","preloads","preload","rel","href","styles","style","headScripts","script","prev","next","uniqBy","every","index","arr","T","fn","item","seen","Set","key","has","add"],"sources":["../../src/headContentUtils.tsx"],"sourcesContent":["import * as Solid from 'solid-js'\nimport { escapeHtml } from '@tanstack/router-core'\nimport { useRouter } from './useRouter'\nimport type { RouterManagedTag } from '@tanstack/router-core'\n\n/**\n * Build the list of head/link/meta/script tags to render for active matches.\n * Used internally by `HeadContent`.\n */\nexport const useTags = () => {\n const router = useRouter()\n const nonce = router.options.ssr?.nonce\n const getTagKey = (tag: RouterManagedTag) => JSON.stringify(tag)\n const activeMatches = Solid.createMemo(\n () => router.stores.activeMatchesSnapshot.state,\n )\n const routeMeta = Solid.createMemo(() =>\n activeMatches()\n .map((match) => match.meta!)\n .filter(Boolean),\n )\n\n const meta: Solid.Accessor<Array<RouterManagedTag>> = Solid.createMemo(() => {\n const resultMeta: Array<RouterManagedTag> = []\n const metaByAttribute: Record<string, true> = {}\n let title: RouterManagedTag | undefined\n const routeMetasArray = routeMeta()\n for (let i = routeMetasArray.length - 1; i >= 0; i--) {\n const metas = routeMetasArray[i]!\n for (let j = metas.length - 1; j >= 0; j--) {\n const m = metas[j]\n if (!m) continue\n\n if (m.title) {\n if (!title) {\n title = {\n tag: 'title',\n children: m.title,\n }\n }\n } else if ('script:ld+json' in m) {\n // Handle JSON-LD structured data\n // Content is HTML-escaped to prevent XSS when injected via innerHTML\n try {\n const json = JSON.stringify(m['script:ld+json'])\n resultMeta.push({\n tag: 'script',\n attrs: {\n type: 'application/ld+json',\n },\n children: escapeHtml(json),\n })\n } catch {\n // Skip invalid JSON-LD objects\n }\n } else {\n const attribute = m.name ?? m.property\n if (attribute) {\n if (metaByAttribute[attribute]) {\n continue\n } else {\n metaByAttribute[attribute] = true\n }\n }\n\n resultMeta.push({\n tag: 'meta',\n attrs: {\n ...m,\n nonce,\n },\n })\n }\n }\n }\n\n if (title) {\n resultMeta.push(title)\n }\n\n if (router.options.ssr?.nonce) {\n resultMeta.push({\n tag: 'meta',\n attrs: {\n property: 'csp-nonce',\n content: router.options.ssr.nonce,\n },\n })\n }\n resultMeta.reverse()\n\n return resultMeta\n })\n\n const links = Solid.createMemo(() => {\n const matches = activeMatches()\n const constructed = matches\n .map((match) => match.links!)\n .filter(Boolean)\n .flat(1)\n .map((link) => ({\n tag: 'link',\n attrs: {\n ...link,\n nonce,\n },\n })) satisfies Array<RouterManagedTag>\n\n const manifest = router.ssr?.manifest\n\n const assets = matches\n .map((match) => manifest?.routes[match.routeId]?.assets ?? [])\n .filter(Boolean)\n .flat(1)\n .filter((asset) => asset.tag === 'link')\n .map(\n (asset) =>\n ({\n tag: 'link',\n attrs: { ...asset.attrs, nonce },\n }) satisfies RouterManagedTag,\n )\n\n return [...constructed, ...assets]\n })\n\n const preloadLinks = Solid.createMemo(() => {\n const matches = activeMatches()\n const preloadLinks: Array<RouterManagedTag> = []\n\n matches\n .map((match) => router.looseRoutesById[match.routeId]!)\n .forEach((route) =>\n router.ssr?.manifest?.routes[route.id]?.preloads\n ?.filter(Boolean)\n .forEach((preload) => {\n preloadLinks.push({\n tag: 'link',\n attrs: {\n rel: 'modulepreload',\n href: preload,\n nonce,\n },\n })\n }),\n )\n\n return preloadLinks\n })\n\n const styles = Solid.createMemo(() =>\n (\n activeMatches()\n .map((match) => match.styles!)\n .flat(1)\n .filter(Boolean) as Array<RouterManagedTag>\n ).map(({ children, ...style }) => ({\n tag: 'style',\n attrs: {\n ...style,\n nonce,\n },\n children,\n })),\n )\n\n const headScripts = Solid.createMemo(() =>\n (\n activeMatches()\n .map((match) => match.headScripts!)\n .flat(1)\n .filter(Boolean) as Array<RouterManagedTag>\n ).map(({ children, ...script }) => ({\n tag: 'script',\n attrs: {\n ...script,\n nonce,\n },\n children,\n })),\n )\n\n return Solid.createMemo((prev: Array<RouterManagedTag> | undefined) => {\n const next = uniqBy(\n [\n ...meta(),\n ...preloadLinks(),\n ...links(),\n ...styles(),\n ...headScripts(),\n ] as Array<RouterManagedTag>,\n getTagKey,\n )\n\n if (\n prev &&\n prev.length === next.length &&\n prev.every((tag, index) => getTagKey(tag) === getTagKey(next[index]!))\n ) {\n return prev\n }\n\n return next\n })\n}\n\nexport function uniqBy<T>(arr: Array<T>, fn: (item: T) => string) {\n const seen = new Set<string>()\n return arr.filter((item) => {\n const key = fn(item)\n if (seen.has(key)) {\n return false\n }\n seen.add(key)\n return true\n })\n}\n"],"mappings":"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"}
@@ -2,7 +2,6 @@ import { Await, useAwaited } from "./awaited.js";
2
2
  import { CatchBoundary, ErrorComponent } from "./CatchBoundary.js";
3
3
  import { ClientOnly, useHydrated } from "./ClientOnly.js";
4
4
  import { useRouter } from "./useRouter.js";
5
- import { useRouterState } from "./useRouterState.js";
6
5
  import { Link, createLink, linkOptions, useLinkProps } from "./link.js";
7
6
  import { useMatch } from "./useMatch.js";
8
7
  import { useLoaderData } from "./useLoaderData.js";
@@ -22,6 +21,7 @@ import { Router, createRouter } from "./router.js";
22
21
  import { RouterContextProvider, RouterProvider } from "./RouterProvider.js";
23
22
  import { ScrollRestoration, useElementScrollRestoration } from "./ScrollRestoration.js";
24
23
  import { Block, useBlocker } from "./useBlocker.js";
24
+ import { useRouterState } from "./useRouterState.js";
25
25
  import { useLocation } from "./useLocation.js";
26
26
  import { useCanGoBack } from "./useCanGoBack.js";
27
27
  import { Asset } from "./Asset.js";
package/dist/esm/index.js CHANGED
@@ -2,7 +2,6 @@ import { Await, useAwaited } from "./awaited.js";
2
2
  import { CatchBoundary, ErrorComponent } from "./CatchBoundary.js";
3
3
  import { ClientOnly, useHydrated } from "./ClientOnly.js";
4
4
  import { useRouter } from "./useRouter.js";
5
- import { useRouterState } from "./useRouterState.js";
6
5
  import { Link, createLink, linkOptions, useLinkProps } from "./link.js";
7
6
  import { useMatch } from "./useMatch.js";
8
7
  import { useLoaderData } from "./useLoaderData.js";
@@ -22,6 +21,7 @@ import { Router, createRouter } from "./router.js";
22
21
  import { RouterContextProvider, RouterProvider } from "./RouterProvider.js";
23
22
  import { ScrollRestoration, useElementScrollRestoration } from "./ScrollRestoration.js";
24
23
  import { Block, useBlocker } from "./useBlocker.js";
24
+ import { useRouterState } from "./useRouterState.js";
25
25
  import { useLocation } from "./useLocation.js";
26
26
  import { useCanGoBack } from "./useCanGoBack.js";
27
27
  import { Asset } from "./Asset.js";