@tanstack/solid-router 1.108.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.
Files changed (271) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +29 -0
  3. package/dist/cjs/Asset.cjs +59 -0
  4. package/dist/cjs/Asset.cjs.map +1 -0
  5. package/dist/cjs/Asset.d.cts +2 -0
  6. package/dist/cjs/CatchBoundary.cjs +92 -0
  7. package/dist/cjs/CatchBoundary.cjs.map +1 -0
  8. package/dist/cjs/CatchBoundary.d.cts +11 -0
  9. package/dist/cjs/HeadContent.cjs +129 -0
  10. package/dist/cjs/HeadContent.cjs.map +1 -0
  11. package/dist/cjs/HeadContent.d.cts +8 -0
  12. package/dist/cjs/Match.cjs +340 -0
  13. package/dist/cjs/Match.cjs.map +1 -0
  14. package/dist/cjs/Match.d.cts +8 -0
  15. package/dist/cjs/Matches.cjs +151 -0
  16. package/dist/cjs/Matches.cjs.map +1 -0
  17. package/dist/cjs/Matches.d.cts +69 -0
  18. package/dist/cjs/RouterProvider.cjs +45 -0
  19. package/dist/cjs/RouterProvider.cjs.map +1 -0
  20. package/dist/cjs/RouterProvider.d.cts +35 -0
  21. package/dist/cjs/SafeFragment.cjs +8 -0
  22. package/dist/cjs/SafeFragment.cjs.map +1 -0
  23. package/dist/cjs/SafeFragment.d.cts +1 -0
  24. package/dist/cjs/ScriptOnce.cjs +23 -0
  25. package/dist/cjs/ScriptOnce.cjs.map +1 -0
  26. package/dist/cjs/ScriptOnce.d.cts +5 -0
  27. package/dist/cjs/Scripts.cjs +48 -0
  28. package/dist/cjs/Scripts.cjs.map +1 -0
  29. package/dist/cjs/Scripts.d.cts +1 -0
  30. package/dist/cjs/ScrollRestoration.cjs +37 -0
  31. package/dist/cjs/ScrollRestoration.cjs.map +1 -0
  32. package/dist/cjs/ScrollRestoration.d.cts +15 -0
  33. package/dist/cjs/Transitioner.cjs +132 -0
  34. package/dist/cjs/Transitioner.cjs.map +1 -0
  35. package/dist/cjs/Transitioner.d.cts +1 -0
  36. package/dist/cjs/awaited.cjs +53 -0
  37. package/dist/cjs/awaited.cjs.map +1 -0
  38. package/dist/cjs/awaited.d.cts +11 -0
  39. package/dist/cjs/fileRoute.cjs +90 -0
  40. package/dist/cjs/fileRoute.cjs.map +1 -0
  41. package/dist/cjs/fileRoute.d.cts +58 -0
  42. package/dist/cjs/history.d.cts +8 -0
  43. package/dist/cjs/index.cjs +260 -0
  44. package/dist/cjs/index.cjs.map +1 -0
  45. package/dist/cjs/index.d.cts +53 -0
  46. package/dist/cjs/lazyRouteComponent.cjs +74 -0
  47. package/dist/cjs/lazyRouteComponent.cjs.map +1 -0
  48. package/dist/cjs/lazyRouteComponent.d.cts +7 -0
  49. package/dist/cjs/link.cjs +279 -0
  50. package/dist/cjs/link.cjs.map +1 -0
  51. package/dist/cjs/link.d.cts +113 -0
  52. package/dist/cjs/matchContext.cjs +25 -0
  53. package/dist/cjs/matchContext.cjs.map +1 -0
  54. package/dist/cjs/matchContext.d.cts +3 -0
  55. package/dist/cjs/not-found.cjs +51 -0
  56. package/dist/cjs/not-found.cjs.map +1 -0
  57. package/dist/cjs/not-found.d.cts +27 -0
  58. package/dist/cjs/redirects.cjs +29 -0
  59. package/dist/cjs/redirects.cjs.map +1 -0
  60. package/dist/cjs/redirects.d.cts +21 -0
  61. package/dist/cjs/renderRouteNotFound.cjs +23 -0
  62. package/dist/cjs/renderRouteNotFound.cjs.map +1 -0
  63. package/dist/cjs/renderRouteNotFound.d.cts +3 -0
  64. package/dist/cjs/route.cjs +233 -0
  65. package/dist/cjs/route.cjs.map +1 -0
  66. package/dist/cjs/route.d.cts +297 -0
  67. package/dist/cjs/routeInfo.d.cts +53 -0
  68. package/dist/cjs/router.cjs +1687 -0
  69. package/dist/cjs/router.cjs.map +1 -0
  70. package/dist/cjs/router.d.cts +555 -0
  71. package/dist/cjs/routerContext.cjs +33 -0
  72. package/dist/cjs/routerContext.cjs.map +1 -0
  73. package/dist/cjs/routerContext.d.cts +8 -0
  74. package/dist/cjs/scroll-restoration.cjs +183 -0
  75. package/dist/cjs/scroll-restoration.cjs.map +1 -0
  76. package/dist/cjs/scroll-restoration.d.cts +29 -0
  77. package/dist/cjs/typePrimitives.d.cts +66 -0
  78. package/dist/cjs/useBlocker.cjs +165 -0
  79. package/dist/cjs/useBlocker.cjs.map +1 -0
  80. package/dist/cjs/useBlocker.d.cts +68 -0
  81. package/dist/cjs/useCanGoBack.cjs +8 -0
  82. package/dist/cjs/useCanGoBack.cjs.map +1 -0
  83. package/dist/cjs/useCanGoBack.d.cts +1 -0
  84. package/dist/cjs/useLoaderData.cjs +14 -0
  85. package/dist/cjs/useLoaderData.cjs.map +1 -0
  86. package/dist/cjs/useLoaderData.d.cts +13 -0
  87. package/dist/cjs/useLoaderDeps.cjs +17 -0
  88. package/dist/cjs/useLoaderDeps.cjs.map +1 -0
  89. package/dist/cjs/useLoaderDeps.d.cts +12 -0
  90. package/dist/cjs/useLocation.cjs +10 -0
  91. package/dist/cjs/useLocation.cjs.map +1 -0
  92. package/dist/cjs/useLocation.d.cts +7 -0
  93. package/dist/cjs/useMatch.cjs +39 -0
  94. package/dist/cjs/useMatch.cjs.map +1 -0
  95. package/dist/cjs/useMatch.d.cts +14 -0
  96. package/dist/cjs/useNavigate.cjs +45 -0
  97. package/dist/cjs/useNavigate.cjs.map +1 -0
  98. package/dist/cjs/useNavigate.d.cts +7 -0
  99. package/dist/cjs/useParams.cjs +15 -0
  100. package/dist/cjs/useParams.cjs.map +1 -0
  101. package/dist/cjs/useParams.d.cts +15 -0
  102. package/dist/cjs/useRouteContext.cjs +11 -0
  103. package/dist/cjs/useRouteContext.cjs.map +1 -0
  104. package/dist/cjs/useRouteContext.d.cts +13 -0
  105. package/dist/cjs/useRouter.cjs +29 -0
  106. package/dist/cjs/useRouter.cjs.map +1 -0
  107. package/dist/cjs/useRouter.d.cts +4 -0
  108. package/dist/cjs/useRouterState.cjs +16 -0
  109. package/dist/cjs/useRouterState.cjs.map +1 -0
  110. package/dist/cjs/useRouterState.d.cts +8 -0
  111. package/dist/cjs/useSearch.cjs +15 -0
  112. package/dist/cjs/useSearch.cjs.map +1 -0
  113. package/dist/cjs/useSearch.d.cts +15 -0
  114. package/dist/cjs/utils.cjs +58 -0
  115. package/dist/cjs/utils.cjs.map +1 -0
  116. package/dist/cjs/utils.d.cts +44 -0
  117. package/dist/esm/Asset.d.ts +2 -0
  118. package/dist/esm/Asset.js +59 -0
  119. package/dist/esm/Asset.js.map +1 -0
  120. package/dist/esm/CatchBoundary.d.ts +11 -0
  121. package/dist/esm/CatchBoundary.js +75 -0
  122. package/dist/esm/CatchBoundary.js.map +1 -0
  123. package/dist/esm/HeadContent.d.ts +8 -0
  124. package/dist/esm/HeadContent.js +112 -0
  125. package/dist/esm/HeadContent.js.map +1 -0
  126. package/dist/esm/Match.d.ts +8 -0
  127. package/dist/esm/Match.js +323 -0
  128. package/dist/esm/Match.js.map +1 -0
  129. package/dist/esm/Matches.d.ts +69 -0
  130. package/dist/esm/Matches.js +134 -0
  131. package/dist/esm/Matches.js.map +1 -0
  132. package/dist/esm/RouterProvider.d.ts +35 -0
  133. package/dist/esm/RouterProvider.js +45 -0
  134. package/dist/esm/RouterProvider.js.map +1 -0
  135. package/dist/esm/SafeFragment.d.ts +1 -0
  136. package/dist/esm/SafeFragment.js +8 -0
  137. package/dist/esm/SafeFragment.js.map +1 -0
  138. package/dist/esm/ScriptOnce.d.ts +5 -0
  139. package/dist/esm/ScriptOnce.js +23 -0
  140. package/dist/esm/ScriptOnce.js.map +1 -0
  141. package/dist/esm/Scripts.d.ts +1 -0
  142. package/dist/esm/Scripts.js +48 -0
  143. package/dist/esm/Scripts.js.map +1 -0
  144. package/dist/esm/ScrollRestoration.d.ts +15 -0
  145. package/dist/esm/ScrollRestoration.js +37 -0
  146. package/dist/esm/ScrollRestoration.js.map +1 -0
  147. package/dist/esm/Transitioner.d.ts +1 -0
  148. package/dist/esm/Transitioner.js +115 -0
  149. package/dist/esm/Transitioner.js.map +1 -0
  150. package/dist/esm/awaited.d.ts +11 -0
  151. package/dist/esm/awaited.js +36 -0
  152. package/dist/esm/awaited.js.map +1 -0
  153. package/dist/esm/fileRoute.d.ts +58 -0
  154. package/dist/esm/fileRoute.js +90 -0
  155. package/dist/esm/fileRoute.js.map +1 -0
  156. package/dist/esm/history.d.ts +8 -0
  157. package/dist/esm/index.d.ts +53 -0
  158. package/dist/esm/index.js +149 -0
  159. package/dist/esm/index.js.map +1 -0
  160. package/dist/esm/lazyRouteComponent.d.ts +7 -0
  161. package/dist/esm/lazyRouteComponent.js +74 -0
  162. package/dist/esm/lazyRouteComponent.js.map +1 -0
  163. package/dist/esm/link.d.ts +113 -0
  164. package/dist/esm/link.js +262 -0
  165. package/dist/esm/link.js.map +1 -0
  166. package/dist/esm/matchContext.d.ts +3 -0
  167. package/dist/esm/matchContext.js +8 -0
  168. package/dist/esm/matchContext.js.map +1 -0
  169. package/dist/esm/not-found.d.ts +27 -0
  170. package/dist/esm/not-found.js +51 -0
  171. package/dist/esm/not-found.js.map +1 -0
  172. package/dist/esm/redirects.d.ts +21 -0
  173. package/dist/esm/redirects.js +29 -0
  174. package/dist/esm/redirects.js.map +1 -0
  175. package/dist/esm/renderRouteNotFound.d.ts +3 -0
  176. package/dist/esm/renderRouteNotFound.js +23 -0
  177. package/dist/esm/renderRouteNotFound.js.map +1 -0
  178. package/dist/esm/route.d.ts +297 -0
  179. package/dist/esm/route.js +233 -0
  180. package/dist/esm/route.js.map +1 -0
  181. package/dist/esm/routeInfo.d.ts +53 -0
  182. package/dist/esm/router.d.ts +555 -0
  183. package/dist/esm/router.js +1687 -0
  184. package/dist/esm/router.js.map +1 -0
  185. package/dist/esm/routerContext.d.ts +8 -0
  186. package/dist/esm/routerContext.js +16 -0
  187. package/dist/esm/routerContext.js.map +1 -0
  188. package/dist/esm/scroll-restoration.d.ts +29 -0
  189. package/dist/esm/scroll-restoration.js +183 -0
  190. package/dist/esm/scroll-restoration.js.map +1 -0
  191. package/dist/esm/typePrimitives.d.ts +66 -0
  192. package/dist/esm/useBlocker.d.ts +68 -0
  193. package/dist/esm/useBlocker.js +148 -0
  194. package/dist/esm/useBlocker.js.map +1 -0
  195. package/dist/esm/useCanGoBack.d.ts +1 -0
  196. package/dist/esm/useCanGoBack.js +8 -0
  197. package/dist/esm/useCanGoBack.js.map +1 -0
  198. package/dist/esm/useLoaderData.d.ts +13 -0
  199. package/dist/esm/useLoaderData.js +14 -0
  200. package/dist/esm/useLoaderData.js.map +1 -0
  201. package/dist/esm/useLoaderDeps.d.ts +12 -0
  202. package/dist/esm/useLoaderDeps.js +17 -0
  203. package/dist/esm/useLoaderDeps.js.map +1 -0
  204. package/dist/esm/useLocation.d.ts +7 -0
  205. package/dist/esm/useLocation.js +10 -0
  206. package/dist/esm/useLocation.js.map +1 -0
  207. package/dist/esm/useMatch.d.ts +14 -0
  208. package/dist/esm/useMatch.js +22 -0
  209. package/dist/esm/useMatch.js.map +1 -0
  210. package/dist/esm/useNavigate.d.ts +7 -0
  211. package/dist/esm/useNavigate.js +28 -0
  212. package/dist/esm/useNavigate.js.map +1 -0
  213. package/dist/esm/useParams.d.ts +15 -0
  214. package/dist/esm/useParams.js +15 -0
  215. package/dist/esm/useParams.js.map +1 -0
  216. package/dist/esm/useRouteContext.d.ts +13 -0
  217. package/dist/esm/useRouteContext.js +11 -0
  218. package/dist/esm/useRouteContext.js.map +1 -0
  219. package/dist/esm/useRouter.d.ts +4 -0
  220. package/dist/esm/useRouter.js +12 -0
  221. package/dist/esm/useRouter.js.map +1 -0
  222. package/dist/esm/useRouterState.d.ts +8 -0
  223. package/dist/esm/useRouterState.js +16 -0
  224. package/dist/esm/useRouterState.js.map +1 -0
  225. package/dist/esm/useSearch.d.ts +15 -0
  226. package/dist/esm/useSearch.js +15 -0
  227. package/dist/esm/useSearch.js.map +1 -0
  228. package/dist/esm/utils.d.ts +44 -0
  229. package/dist/esm/utils.js +41 -0
  230. package/dist/esm/utils.js.map +1 -0
  231. package/package.json +75 -0
  232. package/src/Asset.tsx +23 -0
  233. package/src/CatchBoundary.tsx +78 -0
  234. package/src/HeadContent.tsx +146 -0
  235. package/src/Match.tsx +356 -0
  236. package/src/Matches.tsx +348 -0
  237. package/src/RouterProvider.tsx +130 -0
  238. package/src/SafeFragment.tsx +3 -0
  239. package/src/ScriptOnce.tsx +30 -0
  240. package/src/Scripts.tsx +65 -0
  241. package/src/ScrollRestoration.tsx +65 -0
  242. package/src/Transitioner.tsx +152 -0
  243. package/src/awaited.tsx +49 -0
  244. package/src/fileRoute.ts +274 -0
  245. package/src/history.ts +9 -0
  246. package/src/index.tsx +359 -0
  247. package/src/lazyRouteComponent.tsx +114 -0
  248. package/src/link.tsx +1002 -0
  249. package/src/matchContext.tsx +10 -0
  250. package/src/not-found.tsx +69 -0
  251. package/src/redirects.ts +71 -0
  252. package/src/renderRouteNotFound.tsx +27 -0
  253. package/src/route.ts +1477 -0
  254. package/src/routeInfo.ts +239 -0
  255. package/src/router.ts +3066 -0
  256. package/src/routerContext.tsx +26 -0
  257. package/src/scroll-restoration.tsx +337 -0
  258. package/src/typePrimitives.ts +195 -0
  259. package/src/useBlocker.tsx +298 -0
  260. package/src/useCanGoBack.ts +5 -0
  261. package/src/useLoaderData.tsx +64 -0
  262. package/src/useLoaderDeps.tsx +52 -0
  263. package/src/useLocation.tsx +26 -0
  264. package/src/useMatch.tsx +96 -0
  265. package/src/useNavigate.tsx +61 -0
  266. package/src/useParams.tsx +83 -0
  267. package/src/useRouteContext.ts +62 -0
  268. package/src/useRouter.tsx +15 -0
  269. package/src/useRouterState.tsx +32 -0
  270. package/src/useSearch.tsx +84 -0
  271. package/src/utils.ts +96 -0
package/src/router.ts ADDED
@@ -0,0 +1,3066 @@
1
+ import {
2
+ createBrowserHistory,
3
+ createMemoryHistory,
4
+ parseHref,
5
+ } from '@tanstack/history'
6
+ import { Store, batch } from '@tanstack/solid-store'
7
+ import invariant from 'tiny-invariant'
8
+ import {
9
+ cleanPath,
10
+ createControlledPromise,
11
+ deepEqual,
12
+ defaultParseSearch,
13
+ defaultStringifySearch,
14
+ functionalUpdate,
15
+ getLocationChangeInfo,
16
+ interpolatePath,
17
+ joinPaths,
18
+ last,
19
+ matchPathname,
20
+ parsePathname,
21
+ pick,
22
+ replaceEqualDeep,
23
+ resolvePath,
24
+ rootRouteId,
25
+ trimPath,
26
+ trimPathLeft,
27
+ trimPathRight,
28
+ } from '@tanstack/router-core'
29
+ import { isRedirect, isResolvedRedirect } from './redirects'
30
+ import { isNotFound } from './not-found'
31
+ import { setupScrollRestoration } from './scroll-restoration'
32
+ import type * as Solid from 'solid-js'
33
+ import type {
34
+ HistoryLocation,
35
+ HistoryState,
36
+ ParsedHistoryState,
37
+ RouterHistory,
38
+ } from '@tanstack/history'
39
+ import type { NoInfer } from '@tanstack/solid-store'
40
+
41
+ import type {
42
+ AnyContext,
43
+ AnySchema,
44
+ AnyValidator,
45
+ CommitLocationOptions,
46
+ ControlledPromise,
47
+ Manifest,
48
+ NonNullableUpdater,
49
+ ParsedLocation,
50
+ PickAsRequired,
51
+ ResolveRelativePath,
52
+ SearchMiddleware,
53
+ SearchParser,
54
+ SearchSerializer,
55
+ StartSerializer,
56
+ TrailingSlashOption,
57
+ Updater,
58
+ ViewTransitionOptions,
59
+ } from '@tanstack/router-core'
60
+ import type {
61
+ AnyRoute,
62
+ AnyRouteWithContext,
63
+ BeforeLoadContextOptions,
64
+ ErrorRouteComponent,
65
+ LoaderFnContext,
66
+ MakeRemountDepsOptionsUnion,
67
+ NotFoundRouteComponent,
68
+ RootRoute,
69
+ RouteComponent,
70
+ RouteContextOptions,
71
+ RouteMask,
72
+ } from './route'
73
+
74
+ import type {
75
+ FullSearchSchema,
76
+ RouteById,
77
+ RoutePaths,
78
+ RoutesById,
79
+ RoutesByPath,
80
+ } from './routeInfo'
81
+ import type {
82
+ AnyRouteMatch,
83
+ MakeRouteMatch,
84
+ MakeRouteMatchUnion,
85
+ MatchRouteOptions,
86
+ } from './Matches'
87
+
88
+ import type { BuildLocationFn, NavigateFn } from './RouterProvider'
89
+
90
+ import type { AnyRedirect, ResolvedRedirect } from './redirects'
91
+ import type { NotFoundError } from './not-found'
92
+ import type { NavigateOptions, ToOptions } from './link'
93
+
94
+ declare global {
95
+ interface Window {
96
+ __TSR_ROUTER__?: AnyRouter
97
+ }
98
+ }
99
+
100
+ export interface Register {
101
+ // router: Router
102
+ }
103
+
104
+ export type AnyRouter = Router<any, any, any, any, any>
105
+
106
+ export type AnyRouterWithContext<TContext> = Router<
107
+ AnyRouteWithContext<TContext>,
108
+ any,
109
+ any,
110
+ any,
111
+ any
112
+ >
113
+
114
+ export type RegisteredRouter = Register extends {
115
+ router: infer TRouter extends AnyRouter
116
+ }
117
+ ? TRouter
118
+ : AnyRouter
119
+
120
+ export type InferRouterContext<TRouteTree extends AnyRoute> =
121
+ TRouteTree extends RootRoute<
122
+ any,
123
+ infer TRouterContext extends AnyContext,
124
+ any,
125
+ any,
126
+ any,
127
+ any,
128
+ any,
129
+ any
130
+ >
131
+ ? TRouterContext
132
+ : AnyContext
133
+
134
+ export type ControllablePromise<T = any> = Promise<T> & {
135
+ resolve: (value: T) => void
136
+ reject: (value?: any) => void
137
+ }
138
+
139
+ export type RouterContextOptions<TRouteTree extends AnyRoute> =
140
+ AnyContext extends InferRouterContext<TRouteTree>
141
+ ? {
142
+ context?: InferRouterContext<TRouteTree>
143
+ }
144
+ : {
145
+ context: InferRouterContext<TRouteTree>
146
+ }
147
+
148
+ export type InjectedHtmlEntry = Promise<string>
149
+
150
+ export interface RouterOptions<
151
+ TRouteTree extends AnyRoute,
152
+ TTrailingSlashOption extends TrailingSlashOption,
153
+ TRouterHistory extends RouterHistory = RouterHistory,
154
+ TDehydrated extends Record<string, any> = Record<string, any>,
155
+ > {
156
+ /**
157
+ * The history object that will be used to manage the browser history.
158
+ *
159
+ * If not provided, a new createBrowserHistory instance will be created and used.
160
+ *
161
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#history-property)
162
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/history-types)
163
+ */
164
+ history?: TRouterHistory
165
+ /**
166
+ * A function that will be used to stringify search params when generating links.
167
+ *
168
+ * @default defaultStringifySearch
169
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#stringifysearch-method)
170
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/custom-search-param-serialization)
171
+ */
172
+ stringifySearch?: SearchSerializer
173
+ /**
174
+ * A function that will be used to parse search params when parsing the current location.
175
+ *
176
+ * @default defaultParseSearch
177
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#parsesearch-method)
178
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/custom-search-param-serialization)
179
+ */
180
+ parseSearch?: SearchParser
181
+ /**
182
+ * If `false`, routes will not be preloaded by default in any way.
183
+ *
184
+ * If `'intent'`, routes will be preloaded by default when the user hovers over a link or a `touchstart` event is detected on a `<Link>`.
185
+ *
186
+ * If `'viewport'`, routes will be preloaded by default when they are within the viewport.
187
+ *
188
+ * @default false
189
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpreload-property)
190
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/preloading)
191
+ */
192
+ defaultPreload?: false | 'intent' | 'viewport' | 'render'
193
+ /**
194
+ * The delay in milliseconds that a route must be hovered over or touched before it is preloaded.
195
+ *
196
+ * @default 50
197
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpreloaddelay-property)
198
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/preloading#preload-delay)
199
+ */
200
+ defaultPreloadDelay?: number
201
+ /**
202
+ * The default `component` a route should use if no component is provided.
203
+ *
204
+ * @default Outlet
205
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultcomponent-property)
206
+ */
207
+ defaultComponent?: RouteComponent
208
+ /**
209
+ * The default `errorComponent` a route should use if no error component is provided.
210
+ *
211
+ * @default ErrorComponent
212
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaulterrorcomponent-property)
213
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#handling-errors-with-routeoptionserrorcomponent)
214
+ */
215
+ defaultErrorComponent?: ErrorRouteComponent
216
+ /**
217
+ * The default `pendingComponent` a route should use if no pending component is provided.
218
+ *
219
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpendingcomponent-property)
220
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#showing-a-pending-component)
221
+ */
222
+ defaultPendingComponent?: RouteComponent
223
+ /**
224
+ * The default `pendingMs` a route should use if no pendingMs is provided.
225
+ *
226
+ * @default 1000
227
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpendingms-property)
228
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#avoiding-pending-component-flash)
229
+ */
230
+ defaultPendingMs?: number
231
+ /**
232
+ * The default `pendingMinMs` a route should use if no pendingMinMs is provided.
233
+ *
234
+ * @default 500
235
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpendingminms-property)
236
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#avoiding-pending-component-flash)
237
+ */
238
+ defaultPendingMinMs?: number
239
+ /**
240
+ * The default `staleTime` a route should use if no staleTime is provided. This is the time in milliseconds that a route will be considered fresh.
241
+ *
242
+ * @default 0
243
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultstaletime-property)
244
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#key-options)
245
+ */
246
+ defaultStaleTime?: number
247
+ /**
248
+ * The default `preloadStaleTime` a route should use if no preloadStaleTime is provided.
249
+ *
250
+ * @default 30_000 `(30 seconds)`
251
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpreloadstaletime-property)
252
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/preloading)
253
+ */
254
+ defaultPreloadStaleTime?: number
255
+ /**
256
+ * The default `defaultPreloadGcTime` a route should use if no preloadGcTime is provided.
257
+ *
258
+ * @default 1_800_000 `(30 minutes)`
259
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpreloadgctime-property)
260
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/preloading)
261
+ */
262
+ defaultPreloadGcTime?: number
263
+ /**
264
+ * The default `onCatch` handler for errors caught by the Router ErrorBoundary
265
+ *
266
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultoncatch-property)
267
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#handling-errors-with-routeoptionsoncatch)
268
+ */
269
+ defaultOnCatch?: (error: Error) => void
270
+ /**
271
+ * If `true`, route navigations will called using `document.startViewTransition()`.
272
+ *
273
+ * If the browser does not support this api, this option will be ignored.
274
+ *
275
+ * See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/startViewTransition) for more information on how this function works.
276
+ *
277
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultviewtransition-property)
278
+ */
279
+ defaultViewTransition?: boolean | ViewTransitionOptions
280
+ /**
281
+ * The default `hashScrollIntoView` a route should use if no hashScrollIntoView is provided while navigating
282
+ *
283
+ * See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView) for more information on `ScrollIntoViewOptions`.
284
+ *
285
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaulthashscrollintoview-property)
286
+ */
287
+ defaultHashScrollIntoView?: boolean | ScrollIntoViewOptions
288
+ /**
289
+ * @default 'fuzzy'
290
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#notfoundmode-property)
291
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/not-found-errors#the-notfoundmode-option)
292
+ */
293
+ notFoundMode?: 'root' | 'fuzzy'
294
+ /**
295
+ * The default `gcTime` a route should use if no gcTime is provided.
296
+ *
297
+ * @default 1_800_000 `(30 minutes)`
298
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultgctime-property)
299
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#key-options)
300
+ */
301
+ defaultGcTime?: number
302
+ /**
303
+ * If `true`, all routes will be matched as case-sensitive.
304
+ *
305
+ * @default false
306
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#casesensitive-property)
307
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/route-trees#case-sensitivity)
308
+ */
309
+ caseSensitive?: boolean
310
+ /**
311
+ *
312
+ * The route tree that will be used to configure the router instance.
313
+ *
314
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#routetree-property)
315
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/route-trees)
316
+ */
317
+ routeTree?: TRouteTree
318
+ /**
319
+ * The basepath for then entire router. This is useful for mounting a router instance at a subpath.
320
+ *
321
+ * @default '/'
322
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#basepath-property)
323
+ */
324
+ basepath?: string
325
+ /**
326
+ * The root context that will be provided to all routes in the route tree.
327
+ *
328
+ * This can be used to provide a context to all routes in the tree without having to provide it to each route individually.
329
+ *
330
+ * Optional or required if the root route was created with [`createRootRouteWithContext()`](https://tanstack.com/router/latest/docs/framework/react/api/router/createRootRouteWithContextFunction).
331
+ *
332
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#context-property)
333
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/router-context)
334
+ */
335
+ context?: InferRouterContext<TRouteTree>
336
+ /**
337
+ * A function that will be called when the router is dehydrated.
338
+ *
339
+ * The return value of this function will be serialized and stored in the router's dehydrated state.
340
+ *
341
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#dehydrate-method)
342
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/external-data-loading#critical-dehydrationhydration)
343
+ */
344
+ dehydrate?: () => TDehydrated
345
+ /**
346
+ * A function that will be called when the router is hydrated.
347
+ *
348
+ * The return value of this function will be serialized and stored in the router's dehydrated state.
349
+ *
350
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#hydrate-method)
351
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/external-data-loading#critical-dehydrationhydration)
352
+ */
353
+ hydrate?: (dehydrated: TDehydrated) => void
354
+ /**
355
+ * An array of route masks that will be used to mask routes in the route tree.
356
+ *
357
+ * Route masking is when you display a route at a different path than the one it is configured to match, like a modal popup that when shared will unmask to the modal's content instead of the modal's context.
358
+ *
359
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#routemasks-property)
360
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/route-masking)
361
+ */
362
+ routeMasks?: Array<RouteMask<TRouteTree>>
363
+ /**
364
+ * If `true`, route masks will, by default, be removed when the page is reloaded.
365
+ *
366
+ * This can be overridden on a per-mask basis by setting the `unmaskOnReload` option on the mask, or on a per-navigation basis by setting the `unmaskOnReload` option in the `Navigate` options.
367
+ *
368
+ * @default false
369
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#unmaskonreload-property)
370
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/route-masking#unmasking-on-page-reload)
371
+ */
372
+ unmaskOnReload?: boolean
373
+ /**
374
+ * A component that will be used to wrap the entire router.
375
+ *
376
+ * This is useful for providing a context to the entire router.
377
+ *
378
+ * Only non-DOM-rendering components like providers should be used, anything else will cause a hydration error.
379
+ *
380
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#wrap-property)
381
+ */
382
+ Wrap?: (props: { children: any }) => Solid.JSX.Element
383
+ /**
384
+ * A component that will be used to wrap the inner contents of the router.
385
+ *
386
+ * This is useful for providing a context to the inner contents of the router where you also need access to the router context and hooks.
387
+ *
388
+ * Only non-DOM-rendering components like providers should be used, anything else will cause a hydration error.
389
+ *
390
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#innerwrap-property)
391
+ */
392
+ InnerWrap?: (props: { children: any }) => Solid.JSX.Element
393
+ /**
394
+ * Use `notFoundComponent` instead.
395
+ *
396
+ * @deprecated
397
+ * See https://tanstack.com/router/v1/docs/guide/not-found-errors#migrating-from-notfoundroute for more info.
398
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#notfoundroute-property)
399
+ */
400
+ notFoundRoute?: AnyRoute
401
+ /**
402
+ * The default `notFoundComponent` a route should use if no notFound component is provided.
403
+ *
404
+ * @default NotFound
405
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultnotfoundcomponent-property)
406
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/not-found-errors#default-router-wide-not-found-handling)
407
+ */
408
+ defaultNotFoundComponent?: NotFoundRouteComponent
409
+ /**
410
+ * Configures how trailing slashes are treated.
411
+ *
412
+ * - `'always'` will add a trailing slash if not present
413
+ * - `'never'` will remove the trailing slash if present
414
+ * - `'preserve'` will not modify the trailing slash.
415
+ *
416
+ * @default 'never'
417
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#trailingslash-property)
418
+ */
419
+ trailingSlash?: TTrailingSlashOption
420
+ /**
421
+ * While usually automatic, sometimes it can be useful to force the router into a server-side state, e.g. when using the router in a non-browser environment that has access to a global.document object.
422
+ *
423
+ * @default typeof document !== 'undefined'
424
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#isserver-property)
425
+ */
426
+ isServer?: boolean
427
+
428
+ defaultSsr?: boolean
429
+
430
+ search?: {
431
+ /**
432
+ * Configures how unknown search params (= not returned by any `validateSearch`) are treated.
433
+ *
434
+ * @default false
435
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#search.strict-property)
436
+ */
437
+ strict?: boolean
438
+ }
439
+
440
+ /**
441
+ * Configures which URI characters are allowed in path params that would ordinarily be escaped by encodeURIComponent.
442
+ *
443
+ * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#pathparamsallowedcharacters-property)
444
+ * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/path-params#allowed-characters)
445
+ */
446
+ pathParamsAllowedCharacters?: Array<
447
+ ';' | ':' | '@' | '&' | '=' | '+' | '$' | ','
448
+ >
449
+
450
+ defaultRemountDeps?: (opts: MakeRemountDepsOptionsUnion<TRouteTree>) => any
451
+
452
+ /**
453
+ * If `true`, scroll restoration will be enabled
454
+ *
455
+ * @default false
456
+ */
457
+ scrollRestoration?: boolean
458
+
459
+ /**
460
+ * A function that will be called to get the key for the scroll restoration cache.
461
+ *
462
+ * @default (location) => location.href
463
+ */
464
+ getScrollRestorationKey?: (location: ParsedLocation) => string
465
+ /**
466
+ * The default behavior for scroll restoration.
467
+ *
468
+ * @default 'auto'
469
+ */
470
+ scrollRestorationBehavior?: ScrollBehavior
471
+ /**
472
+ * An array of selectors that will be used to scroll to the top of the page in addition to `window`
473
+ *
474
+ * @default ['window']
475
+ */
476
+ scrollToTopSelectors?: Array<string>
477
+ }
478
+
479
+ export interface RouterErrorSerializer<TSerializedError> {
480
+ serialize: (err: unknown) => TSerializedError
481
+ deserialize: (err: TSerializedError) => unknown
482
+ }
483
+
484
+ export interface RouterState<
485
+ TRouteTree extends AnyRoute = AnyRoute,
486
+ TRouteMatch = MakeRouteMatchUnion,
487
+ > {
488
+ status: 'pending' | 'idle'
489
+ loadedAt: number
490
+ isLoading: boolean
491
+ isTransitioning: boolean
492
+ matches: Array<TRouteMatch>
493
+ pendingMatches?: Array<TRouteMatch>
494
+ cachedMatches: Array<TRouteMatch>
495
+ location: ParsedLocation<FullSearchSchema<TRouteTree>>
496
+ resolvedLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>
497
+ statusCode: number
498
+ redirect?: ResolvedRedirect
499
+ }
500
+
501
+ export type ListenerFn<TEvent extends RouterEvent> = (event: TEvent) => void
502
+
503
+ export interface BuildNextOptions {
504
+ to?: string | number | null
505
+ params?: true | Updater<unknown>
506
+ search?: true | Updater<unknown>
507
+ hash?: true | Updater<string>
508
+ state?: true | NonNullableUpdater<ParsedHistoryState, HistoryState>
509
+ mask?: {
510
+ to?: string | number | null
511
+ params?: true | Updater<unknown>
512
+ search?: true | Updater<unknown>
513
+ hash?: true | Updater<string>
514
+ state?: true | NonNullableUpdater<ParsedHistoryState, HistoryState>
515
+ unmaskOnReload?: boolean
516
+ }
517
+ from?: string
518
+ _fromLocation?: ParsedLocation
519
+ href?: string
520
+ }
521
+
522
+ export interface MatchedRoutesResult {
523
+ matchedRoutes: Array<AnyRoute>
524
+ routeParams: Record<string, string>
525
+ }
526
+
527
+ export type RouterConstructorOptions<
528
+ TRouteTree extends AnyRoute,
529
+ TTrailingSlashOption extends TrailingSlashOption,
530
+ TRouterHistory extends RouterHistory,
531
+ TDehydrated extends Record<string, any>,
532
+ > = Omit<
533
+ RouterOptions<TRouteTree, TTrailingSlashOption, TRouterHistory, TDehydrated>,
534
+ 'context'
535
+ > &
536
+ RouterContextOptions<TRouteTree>
537
+
538
+ export const componentTypes = [
539
+ 'component',
540
+ 'errorComponent',
541
+ 'pendingComponent',
542
+ 'notFoundComponent',
543
+ ] as const
544
+
545
+ function routeNeedsPreload(route: AnyRoute) {
546
+ for (const componentType of componentTypes) {
547
+ if ((route.options[componentType] as any)?.preload) {
548
+ return true
549
+ }
550
+ }
551
+ return false
552
+ }
553
+
554
+ function validateSearch(validateSearch: AnyValidator, input: unknown): unknown {
555
+ if (validateSearch == null) return {}
556
+
557
+ if ('~standard' in validateSearch) {
558
+ const result = validateSearch['~standard'].validate(input)
559
+
560
+ if (result instanceof Promise)
561
+ throw new SearchParamError('Async validation not supported')
562
+
563
+ if (result.issues)
564
+ throw new SearchParamError(JSON.stringify(result.issues, undefined, 2), {
565
+ cause: result,
566
+ })
567
+
568
+ return result.value
569
+ }
570
+
571
+ if ('parse' in validateSearch) {
572
+ return validateSearch.parse(input)
573
+ }
574
+
575
+ if (typeof validateSearch === 'function') {
576
+ return validateSearch(input)
577
+ }
578
+
579
+ return {}
580
+ }
581
+
582
+ type NavigationEventInfo = {
583
+ fromLocation?: ParsedLocation
584
+ toLocation: ParsedLocation
585
+ pathChanged: boolean
586
+ hrefChanged: boolean
587
+ hashChanged: boolean
588
+ }
589
+
590
+ export type RouterEvents = {
591
+ onBeforeNavigate: {
592
+ type: 'onBeforeNavigate'
593
+ } & NavigationEventInfo
594
+ onBeforeLoad: {
595
+ type: 'onBeforeLoad'
596
+ } & NavigationEventInfo
597
+ onLoad: {
598
+ type: 'onLoad'
599
+ } & NavigationEventInfo
600
+ onResolved: {
601
+ type: 'onResolved'
602
+ } & NavigationEventInfo
603
+ onBeforeRouteMount: {
604
+ type: 'onBeforeRouteMount'
605
+ } & NavigationEventInfo
606
+ onInjectedHtml: {
607
+ type: 'onInjectedHtml'
608
+ promise: Promise<string>
609
+ }
610
+ onRendered: {
611
+ type: 'onRendered'
612
+ } & NavigationEventInfo
613
+ }
614
+
615
+ export type RouterEvent = RouterEvents[keyof RouterEvents]
616
+
617
+ export type RouterListener<TRouterEvent extends RouterEvent> = {
618
+ eventType: TRouterEvent['type']
619
+ fn: ListenerFn<TRouterEvent>
620
+ }
621
+
622
+ export function createRouter<
623
+ TRouteTree extends AnyRoute,
624
+ TTrailingSlashOption extends TrailingSlashOption,
625
+ TRouterHistory extends RouterHistory = RouterHistory,
626
+ TDehydrated extends Record<string, any> = Record<string, any>,
627
+ TSerializedError extends Record<string, any> = Record<string, any>,
628
+ >(
629
+ options: undefined extends number
630
+ ? 'strictNullChecks must be enabled in tsconfig.json'
631
+ : RouterConstructorOptions<
632
+ TRouteTree,
633
+ TTrailingSlashOption,
634
+ TRouterHistory,
635
+ TDehydrated
636
+ >,
637
+ ) {
638
+ return new Router<
639
+ TRouteTree,
640
+ TTrailingSlashOption,
641
+ TRouterHistory,
642
+ TDehydrated,
643
+ TSerializedError
644
+ >(options)
645
+ }
646
+
647
+ type MatchRoutesOpts = {
648
+ preload?: boolean
649
+ throwOnError?: boolean
650
+ _buildLocation?: boolean
651
+ dest?: BuildNextOptions
652
+ }
653
+
654
+ export class Router<
655
+ in out TRouteTree extends AnyRoute,
656
+ in out TTrailingSlashOption extends TrailingSlashOption,
657
+ in out TRouterHistory extends RouterHistory = RouterHistory,
658
+ in out TDehydrated extends Record<string, any> = Record<string, any>,
659
+ in out TSerializedError extends Record<string, any> = Record<string, any>,
660
+ > {
661
+ // Option-independent properties
662
+ tempLocationKey: string | undefined = `${Math.round(
663
+ Math.random() * 10000000,
664
+ )}`
665
+ resetNextScroll = true
666
+ shouldViewTransition?: boolean | ViewTransitionOptions = undefined
667
+ isViewTransitionTypesSupported?: boolean = undefined
668
+ subscribers = new Set<RouterListener<RouterEvent>>()
669
+ viewTransitionPromise?: ControlledPromise<true>
670
+ isScrollRestoring = false
671
+ isScrollRestorationSetup = false
672
+
673
+ // Must build in constructor
674
+ __store!: Store<RouterState<TRouteTree>>
675
+ options!: PickAsRequired<
676
+ RouterOptions<
677
+ TRouteTree,
678
+ TTrailingSlashOption,
679
+ TRouterHistory,
680
+ TDehydrated
681
+ >,
682
+ 'stringifySearch' | 'parseSearch' | 'context'
683
+ >
684
+ history!: TRouterHistory
685
+ latestLocation!: ParsedLocation<FullSearchSchema<TRouteTree>>
686
+ basepath!: string
687
+ routeTree!: TRouteTree
688
+ routesById!: RoutesById<TRouteTree>
689
+ routesByPath!: RoutesByPath<TRouteTree>
690
+ flatRoutes!: Array<AnyRoute>
691
+ isServer!: boolean
692
+ pathParamsDecodeCharMap?: Map<string, string>
693
+
694
+ /**
695
+ * @deprecated Use the `createRouter` function instead
696
+ */
697
+ constructor(
698
+ options: RouterConstructorOptions<
699
+ TRouteTree,
700
+ TTrailingSlashOption,
701
+ TRouterHistory,
702
+ TDehydrated
703
+ >,
704
+ ) {
705
+ this.update({
706
+ defaultPreloadDelay: 50,
707
+ defaultPendingMs: 1000,
708
+ defaultPendingMinMs: 500,
709
+ context: undefined!,
710
+ ...options,
711
+ caseSensitive: options.caseSensitive ?? false,
712
+ notFoundMode: options.notFoundMode ?? 'fuzzy',
713
+ stringifySearch: options.stringifySearch ?? defaultStringifySearch,
714
+ parseSearch: options.parseSearch ?? defaultParseSearch,
715
+ })
716
+
717
+ if (typeof document !== 'undefined') {
718
+ ;(window as any).__TSR_ROUTER__ = this
719
+ }
720
+ }
721
+
722
+ // These are default implementations that can optionally be overridden
723
+ // by the router provider once rendered. We provide these so that the
724
+ // router can be used in a non-react environment if necessary
725
+ startSolidTransition: (fn: () => void) => void = (fn) => fn()
726
+
727
+ update = (
728
+ newOptions: RouterConstructorOptions<
729
+ TRouteTree,
730
+ TTrailingSlashOption,
731
+ TRouterHistory,
732
+ TDehydrated
733
+ >,
734
+ ) => {
735
+ if (newOptions.notFoundRoute) {
736
+ console.warn(
737
+ 'The notFoundRoute API is deprecated and will be removed in the next major version. See https://tanstack.com/router/v1/docs/guide/not-found-errors#migrating-from-notfoundroute for more info.',
738
+ )
739
+ }
740
+
741
+ const previousOptions = this.options
742
+ this.options = {
743
+ ...this.options,
744
+ ...newOptions,
745
+ }
746
+
747
+ this.isServer = this.options.isServer ?? typeof document === 'undefined'
748
+
749
+ this.pathParamsDecodeCharMap = this.options.pathParamsAllowedCharacters
750
+ ? new Map(
751
+ this.options.pathParamsAllowedCharacters.map((char) => [
752
+ encodeURIComponent(char),
753
+ char,
754
+ ]),
755
+ )
756
+ : undefined
757
+
758
+ if (
759
+ !this.basepath ||
760
+ (newOptions.basepath && newOptions.basepath !== previousOptions.basepath)
761
+ ) {
762
+ if (
763
+ newOptions.basepath === undefined ||
764
+ newOptions.basepath === '' ||
765
+ newOptions.basepath === '/'
766
+ ) {
767
+ this.basepath = '/'
768
+ } else {
769
+ this.basepath = `/${trimPath(newOptions.basepath)}`
770
+ }
771
+ }
772
+
773
+ if (
774
+ !this.history ||
775
+ (this.options.history && this.options.history !== this.history)
776
+ ) {
777
+ this.history =
778
+ this.options.history ??
779
+ ((this.isServer
780
+ ? createMemoryHistory({
781
+ initialEntries: [this.basepath || '/'],
782
+ })
783
+ : createBrowserHistory()) as TRouterHistory)
784
+ this.latestLocation = this.parseLocation()
785
+ }
786
+
787
+ if (this.options.routeTree !== this.routeTree) {
788
+ this.routeTree = this.options.routeTree as TRouteTree
789
+ this.buildRouteTree()
790
+ }
791
+
792
+ if (!this.__store) {
793
+ this.__store = new Store(getInitialRouterState(this.latestLocation), {
794
+ onUpdate: () => {
795
+ this.__store.state = {
796
+ ...this.state,
797
+ cachedMatches: this.state.cachedMatches.filter(
798
+ (d) => !['redirected'].includes(d.status),
799
+ ),
800
+ }
801
+ },
802
+ })
803
+
804
+ setupScrollRestoration(this)
805
+ }
806
+
807
+ if (
808
+ typeof window !== 'undefined' &&
809
+ 'CSS' in window &&
810
+ typeof window.CSS?.supports === 'function'
811
+ ) {
812
+ this.isViewTransitionTypesSupported = window.CSS.supports(
813
+ 'selector(:active-view-transition-type(a)',
814
+ )
815
+ }
816
+ }
817
+
818
+ get state() {
819
+ return this.__store.state
820
+ }
821
+
822
+ buildRouteTree = () => {
823
+ this.routesById = {} as RoutesById<TRouteTree>
824
+ this.routesByPath = {} as RoutesByPath<TRouteTree>
825
+
826
+ const notFoundRoute = this.options.notFoundRoute
827
+ if (notFoundRoute) {
828
+ notFoundRoute.init({
829
+ originalIndex: 99999999999,
830
+ defaultSsr: this.options.defaultSsr,
831
+ })
832
+ ;(this.routesById as any)[notFoundRoute.id] = notFoundRoute
833
+ }
834
+
835
+ const recurseRoutes = (childRoutes: Array<AnyRoute>) => {
836
+ childRoutes.forEach((childRoute, i) => {
837
+ childRoute.init({
838
+ originalIndex: i,
839
+ defaultSsr: this.options.defaultSsr,
840
+ })
841
+
842
+ const existingRoute = (this.routesById as any)[childRoute.id]
843
+
844
+ invariant(
845
+ !existingRoute,
846
+ `Duplicate routes found with id: ${String(childRoute.id)}`,
847
+ )
848
+ ;(this.routesById as any)[childRoute.id] = childRoute
849
+
850
+ if (!childRoute.isRoot && childRoute.path) {
851
+ const trimmedFullPath = trimPathRight(childRoute.fullPath)
852
+ if (
853
+ !(this.routesByPath as any)[trimmedFullPath] ||
854
+ childRoute.fullPath.endsWith('/')
855
+ ) {
856
+ ;(this.routesByPath as any)[trimmedFullPath] = childRoute
857
+ }
858
+ }
859
+
860
+ const children = childRoute.children
861
+
862
+ if (children?.length) {
863
+ recurseRoutes(children)
864
+ }
865
+ })
866
+ }
867
+
868
+ recurseRoutes([this.routeTree])
869
+
870
+ const scoredRoutes: Array<{
871
+ child: AnyRoute
872
+ trimmed: string
873
+ parsed: ReturnType<typeof parsePathname>
874
+ index: number
875
+ scores: Array<number>
876
+ }> = []
877
+
878
+ const routes: Array<AnyRoute> = Object.values(this.routesById)
879
+
880
+ routes.forEach((d, i) => {
881
+ if (d.isRoot || !d.path) {
882
+ return
883
+ }
884
+
885
+ const trimmed = trimPathLeft(d.fullPath)
886
+ const parsed = parsePathname(trimmed)
887
+
888
+ while (parsed.length > 1 && parsed[0]?.value === '/') {
889
+ parsed.shift()
890
+ }
891
+
892
+ const scores = parsed.map((segment) => {
893
+ if (segment.value === '/') {
894
+ return 0.75
895
+ }
896
+
897
+ if (segment.type === 'param') {
898
+ return 0.5
899
+ }
900
+
901
+ if (segment.type === 'wildcard') {
902
+ return 0.25
903
+ }
904
+
905
+ return 1
906
+ })
907
+
908
+ scoredRoutes.push({ child: d, trimmed, parsed, index: i, scores })
909
+ })
910
+
911
+ this.flatRoutes = scoredRoutes
912
+ .sort((a, b) => {
913
+ const minLength = Math.min(a.scores.length, b.scores.length)
914
+
915
+ // Sort by min available score
916
+ for (let i = 0; i < minLength; i++) {
917
+ if (a.scores[i] !== b.scores[i]) {
918
+ return b.scores[i]! - a.scores[i]!
919
+ }
920
+ }
921
+
922
+ // Sort by length of score
923
+ if (a.scores.length !== b.scores.length) {
924
+ return b.scores.length - a.scores.length
925
+ }
926
+
927
+ // Sort by min available parsed value
928
+ for (let i = 0; i < minLength; i++) {
929
+ if (a.parsed[i]!.value !== b.parsed[i]!.value) {
930
+ return a.parsed[i]!.value > b.parsed[i]!.value ? 1 : -1
931
+ }
932
+ }
933
+
934
+ // Sort by original index
935
+ return a.index - b.index
936
+ })
937
+ .map((d, i) => {
938
+ d.child.rank = i
939
+ return d.child
940
+ })
941
+ }
942
+
943
+ subscribe = <TType extends keyof RouterEvents>(
944
+ eventType: TType,
945
+ fn: ListenerFn<RouterEvents[TType]>,
946
+ ) => {
947
+ const listener: RouterListener<any> = {
948
+ eventType,
949
+ fn,
950
+ }
951
+
952
+ this.subscribers.add(listener)
953
+
954
+ return () => {
955
+ this.subscribers.delete(listener)
956
+ }
957
+ }
958
+
959
+ emit = (routerEvent: RouterEvent) => {
960
+ this.subscribers.forEach((listener) => {
961
+ if (listener.eventType === routerEvent.type) {
962
+ listener.fn(routerEvent)
963
+ }
964
+ })
965
+ }
966
+
967
+ parseLocation = (
968
+ previousLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>,
969
+ locationToParse?: HistoryLocation,
970
+ ): ParsedLocation<FullSearchSchema<TRouteTree>> => {
971
+ const parse = ({
972
+ pathname,
973
+ search,
974
+ hash,
975
+ state,
976
+ }: HistoryLocation): ParsedLocation<FullSearchSchema<TRouteTree>> => {
977
+ const parsedSearch = this.options.parseSearch(search)
978
+ const searchStr = this.options.stringifySearch(parsedSearch)
979
+
980
+ return {
981
+ pathname,
982
+ searchStr,
983
+ search: replaceEqualDeep(previousLocation?.search, parsedSearch) as any,
984
+ hash: hash.split('#').reverse()[0] ?? '',
985
+ href: `${pathname}${searchStr}${hash}`,
986
+ state: replaceEqualDeep(previousLocation?.state, state),
987
+ }
988
+ }
989
+
990
+ const location = parse(locationToParse ?? this.history.location)
991
+
992
+ const { __tempLocation, __tempKey } = location.state
993
+
994
+ if (__tempLocation && (!__tempKey || __tempKey === this.tempLocationKey)) {
995
+ // Sync up the location keys
996
+ const parsedTempLocation = parse(__tempLocation) as any
997
+ parsedTempLocation.state.key = location.state.key
998
+
999
+ delete parsedTempLocation.state.__tempLocation
1000
+
1001
+ return {
1002
+ ...parsedTempLocation,
1003
+ maskedLocation: location,
1004
+ }
1005
+ }
1006
+
1007
+ return location
1008
+ }
1009
+
1010
+ resolvePathWithBase = (from: string, path: string) => {
1011
+ const resolvedPath = resolvePath({
1012
+ basepath: this.basepath,
1013
+ base: from,
1014
+ to: cleanPath(path),
1015
+ trailingSlash: this.options.trailingSlash,
1016
+ caseSensitive: this.options.caseSensitive,
1017
+ })
1018
+ return resolvedPath
1019
+ }
1020
+
1021
+ get looseRoutesById() {
1022
+ return this.routesById as Record<string, AnyRoute>
1023
+ }
1024
+
1025
+ /**
1026
+ @deprecated use the following signature instead
1027
+ ```ts
1028
+ matchRoutes (
1029
+ next: ParsedLocation,
1030
+ opts?: { preload?: boolean; throwOnError?: boolean },
1031
+ ): Array<AnyRouteMatch>;
1032
+ ```
1033
+ */
1034
+ public matchRoutes(
1035
+ pathname: string,
1036
+ locationSearch: AnySchema,
1037
+ opts?: MatchRoutesOpts,
1038
+ ): Array<AnyRouteMatch>
1039
+ public matchRoutes(
1040
+ next: ParsedLocation,
1041
+ opts?: MatchRoutesOpts,
1042
+ ): Array<AnyRouteMatch>
1043
+
1044
+ public matchRoutes(
1045
+ pathnameOrNext: string | ParsedLocation,
1046
+ locationSearchOrOpts?: AnySchema | MatchRoutesOpts,
1047
+ opts?: MatchRoutesOpts,
1048
+ ) {
1049
+ if (typeof pathnameOrNext === 'string') {
1050
+ return this.matchRoutesInternal(
1051
+ {
1052
+ pathname: pathnameOrNext,
1053
+ search: locationSearchOrOpts,
1054
+ } as ParsedLocation,
1055
+ opts,
1056
+ )
1057
+ } else {
1058
+ return this.matchRoutesInternal(pathnameOrNext, locationSearchOrOpts)
1059
+ }
1060
+ }
1061
+
1062
+ private matchRoutesInternal(
1063
+ next: ParsedLocation,
1064
+ opts?: MatchRoutesOpts,
1065
+ ): Array<AnyRouteMatch> {
1066
+ const { foundRoute, matchedRoutes, routeParams } = this.getMatchedRoutes(
1067
+ next,
1068
+ opts?.dest,
1069
+ )
1070
+ let isGlobalNotFound = false
1071
+
1072
+ // Check to see if the route needs a 404 entry
1073
+ if (
1074
+ // If we found a route, and it's not an index route and we have left over path
1075
+ foundRoute
1076
+ ? foundRoute.path !== '/' && routeParams['**']
1077
+ : // Or if we didn't find a route and we have left over path
1078
+ trimPathRight(next.pathname)
1079
+ ) {
1080
+ // If the user has defined an (old) 404 route, use it
1081
+ if (this.options.notFoundRoute) {
1082
+ matchedRoutes.push(this.options.notFoundRoute)
1083
+ } else {
1084
+ // If there is no routes found during path matching
1085
+ isGlobalNotFound = true
1086
+ }
1087
+ }
1088
+
1089
+ const globalNotFoundRouteId = (() => {
1090
+ if (!isGlobalNotFound) {
1091
+ return undefined
1092
+ }
1093
+
1094
+ if (this.options.notFoundMode !== 'root') {
1095
+ for (let i = matchedRoutes.length - 1; i >= 0; i--) {
1096
+ const route = matchedRoutes[i]!
1097
+ if (route.children) {
1098
+ return route.id
1099
+ }
1100
+ }
1101
+ }
1102
+
1103
+ return rootRouteId
1104
+ })()
1105
+
1106
+ const parseErrors = matchedRoutes.map((route) => {
1107
+ let parsedParamsError
1108
+
1109
+ const parseParams =
1110
+ route.options.params?.parse ?? route.options.parseParams
1111
+
1112
+ if (parseParams) {
1113
+ try {
1114
+ const parsedParams = parseParams(routeParams)
1115
+ // Add the parsed params to the accumulated params bag
1116
+ Object.assign(routeParams, parsedParams)
1117
+ } catch (err: any) {
1118
+ parsedParamsError = new PathParamError(err.message, {
1119
+ cause: err,
1120
+ })
1121
+
1122
+ if (opts?.throwOnError) {
1123
+ throw parsedParamsError
1124
+ }
1125
+
1126
+ return parsedParamsError
1127
+ }
1128
+ }
1129
+
1130
+ return
1131
+ })
1132
+
1133
+ const matches: Array<AnyRouteMatch> = []
1134
+
1135
+ const getParentContext = (parentMatch?: AnyRouteMatch) => {
1136
+ const parentMatchId = parentMatch?.id
1137
+
1138
+ const parentContext = !parentMatchId
1139
+ ? ((this.options.context as any) ?? {})
1140
+ : (parentMatch.context ?? this.options.context ?? {})
1141
+
1142
+ return parentContext
1143
+ }
1144
+
1145
+ matchedRoutes.forEach((route, index) => {
1146
+ // Take each matched route and resolve + validate its search params
1147
+ // This has to happen serially because each route's search params
1148
+ // can depend on the parent route's search params
1149
+ // It must also happen before we create the match so that we can
1150
+ // pass the search params to the route's potential key function
1151
+ // which is used to uniquely identify the route match in state
1152
+
1153
+ const parentMatch = matches[index - 1]
1154
+
1155
+ const [preMatchSearch, strictMatchSearch, searchError]: [
1156
+ Record<string, any>,
1157
+ Record<string, any>,
1158
+ any,
1159
+ ] = (() => {
1160
+ // Validate the search params and stabilize them
1161
+ const parentSearch = parentMatch?.search ?? next.search
1162
+ const parentStrictSearch = parentMatch?._strictSearch ?? {}
1163
+
1164
+ try {
1165
+ const strictSearch =
1166
+ validateSearch(route.options.validateSearch, { ...parentSearch }) ??
1167
+ {}
1168
+
1169
+ return [
1170
+ {
1171
+ ...parentSearch,
1172
+ ...strictSearch,
1173
+ },
1174
+ { ...parentStrictSearch, ...strictSearch },
1175
+ undefined,
1176
+ ]
1177
+ } catch (err: any) {
1178
+ let searchParamError = err
1179
+ if (!(err instanceof SearchParamError)) {
1180
+ searchParamError = new SearchParamError(err.message, {
1181
+ cause: err,
1182
+ })
1183
+ }
1184
+
1185
+ if (opts?.throwOnError) {
1186
+ throw searchParamError
1187
+ }
1188
+
1189
+ return [parentSearch, {}, searchParamError]
1190
+ }
1191
+ })()
1192
+
1193
+ // This is where we need to call route.options.loaderDeps() to get any additional
1194
+ // deps that the route's loader function might need to run. We need to do this
1195
+ // before we create the match so that we can pass the deps to the route's
1196
+ // potential key function which is used to uniquely identify the route match in state
1197
+
1198
+ const loaderDeps =
1199
+ route.options.loaderDeps?.({
1200
+ search: preMatchSearch,
1201
+ }) ?? ''
1202
+
1203
+ const loaderDepsHash = loaderDeps ? JSON.stringify(loaderDeps) : ''
1204
+
1205
+ const { usedParams, interpolatedPath } = interpolatePath({
1206
+ path: route.fullPath,
1207
+ params: routeParams,
1208
+ decodeCharMap: this.pathParamsDecodeCharMap,
1209
+ })
1210
+
1211
+ const matchId =
1212
+ interpolatePath({
1213
+ path: route.id,
1214
+ params: routeParams,
1215
+ leaveWildcards: true,
1216
+ decodeCharMap: this.pathParamsDecodeCharMap,
1217
+ }).interpolatedPath + loaderDepsHash
1218
+
1219
+ // Waste not, want not. If we already have a match for this route,
1220
+ // reuse it. This is important for layout routes, which might stick
1221
+ // around between navigation actions that only change leaf routes.
1222
+
1223
+ // Existing matches are matches that are already loaded along with
1224
+ // pending matches that are still loading
1225
+ const existingMatch = this.getMatch(matchId)
1226
+
1227
+ const previousMatch = this.state.matches.find(
1228
+ (d) => d.routeId === route.id,
1229
+ )
1230
+
1231
+ const cause = previousMatch ? 'stay' : 'enter'
1232
+
1233
+ let match: AnyRouteMatch
1234
+
1235
+ if (existingMatch) {
1236
+ match = {
1237
+ ...existingMatch,
1238
+ cause,
1239
+ params: previousMatch
1240
+ ? replaceEqualDeep(previousMatch.params, routeParams)
1241
+ : routeParams,
1242
+ _strictParams: usedParams,
1243
+ search: previousMatch
1244
+ ? replaceEqualDeep(previousMatch.search, preMatchSearch)
1245
+ : replaceEqualDeep(existingMatch.search, preMatchSearch),
1246
+ _strictSearch: strictMatchSearch,
1247
+ }
1248
+ } else {
1249
+ const status =
1250
+ route.options.loader ||
1251
+ route.options.beforeLoad ||
1252
+ route.lazyFn ||
1253
+ routeNeedsPreload(route)
1254
+ ? 'pending'
1255
+ : 'success'
1256
+
1257
+ match = {
1258
+ id: matchId,
1259
+ index,
1260
+ routeId: route.id,
1261
+ params: previousMatch
1262
+ ? replaceEqualDeep(previousMatch.params, routeParams)
1263
+ : routeParams,
1264
+ _strictParams: usedParams,
1265
+ pathname: joinPaths([this.basepath, interpolatedPath]),
1266
+ updatedAt: Date.now(),
1267
+ search: previousMatch
1268
+ ? replaceEqualDeep(previousMatch.search, preMatchSearch)
1269
+ : preMatchSearch,
1270
+ _strictSearch: strictMatchSearch,
1271
+ searchError: undefined,
1272
+ status,
1273
+ isFetching: false,
1274
+ error: undefined,
1275
+ paramsError: parseErrors[index],
1276
+ __routeContext: {},
1277
+ __beforeLoadContext: {},
1278
+ context: {},
1279
+ abortController: new AbortController(),
1280
+ fetchCount: 0,
1281
+ cause,
1282
+ loaderDeps: previousMatch
1283
+ ? replaceEqualDeep(previousMatch.loaderDeps, loaderDeps)
1284
+ : loaderDeps,
1285
+ invalid: false,
1286
+ preload: false,
1287
+ links: undefined,
1288
+ scripts: undefined,
1289
+ headScripts: undefined,
1290
+ meta: undefined,
1291
+ staticData: route.options.staticData || {},
1292
+ loadPromise: createControlledPromise(),
1293
+ fullPath: route.fullPath,
1294
+ }
1295
+ }
1296
+
1297
+ if (!opts?.preload) {
1298
+ // If we have a global not found, mark the right match as global not found
1299
+ match.globalNotFound = globalNotFoundRouteId === route.id
1300
+ }
1301
+
1302
+ // update the searchError if there is one
1303
+ match.searchError = searchError
1304
+
1305
+ const parentContext = getParentContext(parentMatch)
1306
+
1307
+ match.context = {
1308
+ ...parentContext,
1309
+ ...match.__routeContext,
1310
+ ...match.__beforeLoadContext,
1311
+ }
1312
+
1313
+ matches.push(match)
1314
+ })
1315
+
1316
+ matches.forEach((match, index) => {
1317
+ const route = this.looseRoutesById[match.routeId]!
1318
+ const existingMatch = this.getMatch(match.id)
1319
+
1320
+ // only execute `context` if we are not just building a location
1321
+ if (!existingMatch && opts?._buildLocation !== true) {
1322
+ const parentMatch = matches[index - 1]
1323
+ const parentContext = getParentContext(parentMatch)
1324
+
1325
+ // Update the match's context
1326
+ const contextFnContext: RouteContextOptions<any, any, any, any> = {
1327
+ deps: match.loaderDeps,
1328
+ params: match.params,
1329
+ context: parentContext,
1330
+ location: next,
1331
+ navigate: (opts: any) =>
1332
+ this.navigate({ ...opts, _fromLocation: next }),
1333
+ buildLocation: this.buildLocation,
1334
+ cause: match.cause,
1335
+ abortController: match.abortController,
1336
+ preload: !!match.preload,
1337
+ matches,
1338
+ }
1339
+
1340
+ // Get the route context
1341
+ match.__routeContext = route.options.context?.(contextFnContext) ?? {}
1342
+
1343
+ match.context = {
1344
+ ...parentContext,
1345
+ ...match.__routeContext,
1346
+ ...match.__beforeLoadContext,
1347
+ }
1348
+ }
1349
+
1350
+ // If it's already a success, update headers and head content
1351
+ // These may get updated again if the match is refreshed
1352
+ // due to being stale
1353
+ if (match.status === 'success') {
1354
+ match.headers = route.options.headers?.({
1355
+ loaderData: match.loaderData,
1356
+ })
1357
+ const assetContext = {
1358
+ matches,
1359
+ match,
1360
+ params: match.params,
1361
+ loaderData: match.loaderData,
1362
+ }
1363
+ const headFnContent = route.options.head?.(assetContext)
1364
+ match.links = headFnContent?.links
1365
+ match.headScripts = headFnContent?.scripts
1366
+ match.meta = headFnContent?.meta
1367
+ match.scripts = route.options.scripts?.(assetContext)
1368
+ }
1369
+ })
1370
+
1371
+ return matches
1372
+ }
1373
+
1374
+ getMatchedRoutes = (next: ParsedLocation, dest?: BuildNextOptions) => {
1375
+ let routeParams: Record<string, string> = {}
1376
+ const trimmedPath = trimPathRight(next.pathname)
1377
+ const getMatchedParams = (route: AnyRoute) => {
1378
+ const result = matchPathname(this.basepath, trimmedPath, {
1379
+ to: route.fullPath,
1380
+ caseSensitive:
1381
+ route.options.caseSensitive ?? this.options.caseSensitive,
1382
+ fuzzy: true,
1383
+ })
1384
+ return result
1385
+ }
1386
+
1387
+ let foundRoute: AnyRoute | undefined =
1388
+ dest?.to !== undefined ? this.routesByPath[dest.to!] : undefined
1389
+ if (foundRoute) {
1390
+ routeParams = getMatchedParams(foundRoute)!
1391
+ } else {
1392
+ foundRoute = this.flatRoutes.find((route) => {
1393
+ const matchedParams = getMatchedParams(route)
1394
+
1395
+ if (matchedParams) {
1396
+ routeParams = matchedParams
1397
+ return true
1398
+ }
1399
+
1400
+ return false
1401
+ })
1402
+ }
1403
+
1404
+ let routeCursor: AnyRoute =
1405
+ foundRoute || (this.routesById as any)[rootRouteId]
1406
+
1407
+ const matchedRoutes: Array<AnyRoute> = [routeCursor]
1408
+
1409
+ while (routeCursor.parentRoute) {
1410
+ routeCursor = routeCursor.parentRoute
1411
+ matchedRoutes.unshift(routeCursor)
1412
+ }
1413
+
1414
+ return { matchedRoutes, routeParams, foundRoute }
1415
+ }
1416
+
1417
+ cancelMatch = (id: string) => {
1418
+ const match = this.getMatch(id)
1419
+
1420
+ if (!match) return
1421
+
1422
+ match.abortController.abort()
1423
+ clearTimeout(match.pendingTimeout)
1424
+ }
1425
+
1426
+ cancelMatches = () => {
1427
+ this.state.pendingMatches?.forEach((match) => {
1428
+ this.cancelMatch(match.id)
1429
+ })
1430
+ }
1431
+
1432
+ buildLocation: BuildLocationFn = (opts) => {
1433
+ const build = (
1434
+ dest: BuildNextOptions & {
1435
+ unmaskOnReload?: boolean
1436
+ } = {},
1437
+ matchedRoutesResult?: MatchedRoutesResult,
1438
+ ): ParsedLocation => {
1439
+ const fromMatches = dest._fromLocation
1440
+ ? this.matchRoutes(dest._fromLocation, { _buildLocation: true })
1441
+ : this.state.matches
1442
+
1443
+ const fromMatch =
1444
+ dest.from != null
1445
+ ? fromMatches.find((d) =>
1446
+ matchPathname(this.basepath, trimPathRight(d.pathname), {
1447
+ to: dest.from,
1448
+ caseSensitive: false,
1449
+ fuzzy: false,
1450
+ }),
1451
+ )
1452
+ : undefined
1453
+
1454
+ const fromPath = fromMatch?.pathname || this.latestLocation.pathname
1455
+
1456
+ invariant(
1457
+ dest.from == null || fromMatch != null,
1458
+ 'Could not find match for from: ' + dest.from,
1459
+ )
1460
+
1461
+ const fromSearch = this.state.pendingMatches?.length
1462
+ ? last(this.state.pendingMatches)?.search
1463
+ : last(fromMatches)?.search || this.latestLocation.search
1464
+
1465
+ const stayingMatches = matchedRoutesResult?.matchedRoutes.filter((d) =>
1466
+ fromMatches.find((e) => e.routeId === d.id),
1467
+ )
1468
+ let pathname: string
1469
+ if (dest.to) {
1470
+ const resolvePathTo =
1471
+ fromMatch?.fullPath || this.latestLocation.pathname
1472
+ pathname = this.resolvePathWithBase(resolvePathTo, `${dest.to}`)
1473
+ } else {
1474
+ const fromRouteByFromPathRouteId =
1475
+ this.routesById[
1476
+ stayingMatches?.find((route) => {
1477
+ const interpolatedPath = interpolatePath({
1478
+ path: route.fullPath,
1479
+ params: matchedRoutesResult?.routeParams ?? {},
1480
+ decodeCharMap: this.pathParamsDecodeCharMap,
1481
+ }).interpolatedPath
1482
+ const pathname = joinPaths([this.basepath, interpolatedPath])
1483
+ return pathname === fromPath
1484
+ })?.id as keyof this['routesById']
1485
+ ]
1486
+ pathname = this.resolvePathWithBase(
1487
+ fromPath,
1488
+ fromRouteByFromPathRouteId?.to ?? fromPath,
1489
+ )
1490
+ }
1491
+
1492
+ const prevParams = { ...last(fromMatches)?.params }
1493
+
1494
+ let nextParams =
1495
+ (dest.params ?? true) === true
1496
+ ? prevParams
1497
+ : {
1498
+ ...prevParams,
1499
+ ...functionalUpdate(dest.params as any, prevParams),
1500
+ }
1501
+
1502
+ if (Object.keys(nextParams).length > 0) {
1503
+ matchedRoutesResult?.matchedRoutes
1504
+ .map((route) => {
1505
+ return (
1506
+ route.options.params?.stringify ?? route.options.stringifyParams
1507
+ )
1508
+ })
1509
+ .filter(Boolean)
1510
+ .forEach((fn) => {
1511
+ nextParams = { ...nextParams!, ...fn!(nextParams) }
1512
+ })
1513
+ }
1514
+
1515
+ pathname = interpolatePath({
1516
+ path: pathname,
1517
+ params: nextParams ?? {},
1518
+ leaveWildcards: false,
1519
+ leaveParams: opts.leaveParams,
1520
+ decodeCharMap: this.pathParamsDecodeCharMap,
1521
+ }).interpolatedPath
1522
+
1523
+ let search = fromSearch
1524
+ if (opts._includeValidateSearch && this.options.search?.strict) {
1525
+ let validatedSearch = {}
1526
+ matchedRoutesResult?.matchedRoutes.forEach((route) => {
1527
+ try {
1528
+ if (route.options.validateSearch) {
1529
+ validatedSearch = {
1530
+ ...validatedSearch,
1531
+ ...(validateSearch(route.options.validateSearch, {
1532
+ ...validatedSearch,
1533
+ ...search,
1534
+ }) ?? {}),
1535
+ }
1536
+ }
1537
+ } catch {
1538
+ // ignore errors here because they are already handled in matchRoutes
1539
+ }
1540
+ })
1541
+ search = validatedSearch
1542
+ }
1543
+
1544
+ const applyMiddlewares = (search: any) => {
1545
+ const allMiddlewares =
1546
+ matchedRoutesResult?.matchedRoutes.reduce(
1547
+ (acc, route) => {
1548
+ const middlewares: Array<SearchMiddleware<any>> = []
1549
+ if ('search' in route.options) {
1550
+ if (route.options.search?.middlewares) {
1551
+ middlewares.push(...route.options.search.middlewares)
1552
+ }
1553
+ }
1554
+ // TODO remove preSearchFilters and postSearchFilters in v2
1555
+ else if (
1556
+ route.options.preSearchFilters ||
1557
+ route.options.postSearchFilters
1558
+ ) {
1559
+ const legacyMiddleware: SearchMiddleware<any> = ({
1560
+ search,
1561
+ next,
1562
+ }) => {
1563
+ let nextSearch = search
1564
+ if (
1565
+ 'preSearchFilters' in route.options &&
1566
+ route.options.preSearchFilters
1567
+ ) {
1568
+ nextSearch = route.options.preSearchFilters.reduce(
1569
+ (prev, next) => next(prev),
1570
+ search,
1571
+ )
1572
+ }
1573
+ const result = next(nextSearch)
1574
+ if (
1575
+ 'postSearchFilters' in route.options &&
1576
+ route.options.postSearchFilters
1577
+ ) {
1578
+ return route.options.postSearchFilters.reduce(
1579
+ (prev, next) => next(prev),
1580
+ result,
1581
+ )
1582
+ }
1583
+ return result
1584
+ }
1585
+ middlewares.push(legacyMiddleware)
1586
+ }
1587
+ if (opts._includeValidateSearch && route.options.validateSearch) {
1588
+ const validate: SearchMiddleware<any> = ({ search, next }) => {
1589
+ try {
1590
+ const result = next(search)
1591
+ const validatedSearch = {
1592
+ ...result,
1593
+ ...(validateSearch(
1594
+ route.options.validateSearch,
1595
+ result,
1596
+ ) ?? {}),
1597
+ }
1598
+ return validatedSearch
1599
+ } catch {
1600
+ // ignore errors here because they are already handled in matchRoutes
1601
+ }
1602
+ }
1603
+ middlewares.push(validate)
1604
+ }
1605
+ return acc.concat(middlewares)
1606
+ },
1607
+ [] as Array<SearchMiddleware<any>>,
1608
+ ) ?? []
1609
+
1610
+ // the chain ends here since `next` is not called
1611
+ const final: SearchMiddleware<any> = ({ search }) => {
1612
+ if (!dest.search) {
1613
+ return {}
1614
+ }
1615
+ if (dest.search === true) {
1616
+ return search
1617
+ }
1618
+ return functionalUpdate(dest.search, search)
1619
+ }
1620
+ allMiddlewares.push(final)
1621
+
1622
+ const applyNext = (index: number, currentSearch: any): any => {
1623
+ // no more middlewares left, return the current search
1624
+ if (index >= allMiddlewares.length) {
1625
+ return currentSearch
1626
+ }
1627
+
1628
+ const middleware = allMiddlewares[index]!
1629
+
1630
+ const next = (newSearch: any): any => {
1631
+ return applyNext(index + 1, newSearch)
1632
+ }
1633
+
1634
+ return middleware({ search: currentSearch, next })
1635
+ }
1636
+
1637
+ // Start applying middlewares
1638
+ return applyNext(0, search)
1639
+ }
1640
+
1641
+ search = applyMiddlewares(search)
1642
+
1643
+ search = replaceEqualDeep(fromSearch, search)
1644
+ const searchStr = this.options.stringifySearch(search)
1645
+
1646
+ const hash =
1647
+ dest.hash === true
1648
+ ? this.latestLocation.hash
1649
+ : dest.hash
1650
+ ? functionalUpdate(dest.hash, this.latestLocation.hash)
1651
+ : undefined
1652
+
1653
+ const hashStr = hash ? `#${hash}` : ''
1654
+
1655
+ let nextState =
1656
+ dest.state === true
1657
+ ? this.latestLocation.state
1658
+ : dest.state
1659
+ ? functionalUpdate(dest.state, this.latestLocation.state)
1660
+ : {}
1661
+
1662
+ nextState = replaceEqualDeep(this.latestLocation.state, nextState)
1663
+
1664
+ return {
1665
+ pathname,
1666
+ search,
1667
+ searchStr,
1668
+ state: nextState as any,
1669
+ hash: hash ?? '',
1670
+ href: `${pathname}${searchStr}${hashStr}`,
1671
+ unmaskOnReload: dest.unmaskOnReload,
1672
+ }
1673
+ }
1674
+
1675
+ const buildWithMatches = (
1676
+ dest: BuildNextOptions = {},
1677
+ maskedDest?: BuildNextOptions,
1678
+ ) => {
1679
+ const next = build(dest)
1680
+ let maskedNext = maskedDest ? build(maskedDest) : undefined
1681
+
1682
+ if (!maskedNext) {
1683
+ let params = {}
1684
+
1685
+ const foundMask = this.options.routeMasks?.find((d) => {
1686
+ const match = matchPathname(this.basepath, next.pathname, {
1687
+ to: d.from,
1688
+ caseSensitive: false,
1689
+ fuzzy: false,
1690
+ })
1691
+
1692
+ if (match) {
1693
+ params = match
1694
+ return true
1695
+ }
1696
+
1697
+ return false
1698
+ })
1699
+
1700
+ if (foundMask) {
1701
+ const { from: _from, ...maskProps } = foundMask
1702
+ maskedDest = {
1703
+ ...pick(opts, ['from']),
1704
+ ...maskProps,
1705
+ params,
1706
+ }
1707
+ maskedNext = build(maskedDest)
1708
+ }
1709
+ }
1710
+
1711
+ const nextMatches = this.getMatchedRoutes(next, dest)
1712
+ const final = build(dest, nextMatches)
1713
+
1714
+ if (maskedNext) {
1715
+ const maskedMatches = this.getMatchedRoutes(maskedNext, maskedDest)
1716
+ const maskedFinal = build(maskedDest, maskedMatches)
1717
+ final.maskedLocation = maskedFinal
1718
+ }
1719
+
1720
+ return final
1721
+ }
1722
+
1723
+ if (opts.mask) {
1724
+ return buildWithMatches(opts, {
1725
+ ...pick(opts, ['from']),
1726
+ ...opts.mask,
1727
+ })
1728
+ }
1729
+
1730
+ return buildWithMatches(opts)
1731
+ }
1732
+
1733
+ commitLocationPromise: undefined | ControlledPromise<void>
1734
+
1735
+ commitLocation = ({
1736
+ viewTransition,
1737
+ ignoreBlocker,
1738
+ ...next
1739
+ }: ParsedLocation & CommitLocationOptions): Promise<void> => {
1740
+ const isSameState = () => {
1741
+ // the following props are ignored but may still be provided when navigating,
1742
+ // temporarily add the previous values to the next state so they don't affect
1743
+ // the comparison
1744
+ const ignoredProps = [
1745
+ 'key',
1746
+ '__TSR_index',
1747
+ '__hashScrollIntoViewOptions',
1748
+ ] as const
1749
+ ignoredProps.forEach((prop) => {
1750
+ ;(next.state as any)[prop] = this.latestLocation.state[prop]
1751
+ })
1752
+ const isEqual = deepEqual(next.state, this.latestLocation.state)
1753
+ ignoredProps.forEach((prop) => {
1754
+ delete next.state[prop]
1755
+ })
1756
+ return isEqual
1757
+ }
1758
+
1759
+ const isSameUrl = this.latestLocation.href === next.href
1760
+
1761
+ const previousCommitPromise = this.commitLocationPromise
1762
+ this.commitLocationPromise = createControlledPromise<void>(() => {
1763
+ previousCommitPromise?.resolve()
1764
+ })
1765
+
1766
+ // Don't commit to history if nothing changed
1767
+ if (isSameUrl && isSameState()) {
1768
+ this.load()
1769
+ } else {
1770
+ // eslint-disable-next-line prefer-const
1771
+ let { maskedLocation, hashScrollIntoView, ...nextHistory } = next
1772
+
1773
+ if (maskedLocation) {
1774
+ nextHistory = {
1775
+ ...maskedLocation,
1776
+ state: {
1777
+ ...maskedLocation.state,
1778
+ __tempKey: undefined,
1779
+ __tempLocation: {
1780
+ ...nextHistory,
1781
+ search: nextHistory.searchStr,
1782
+ state: {
1783
+ ...nextHistory.state,
1784
+ __tempKey: undefined!,
1785
+ __tempLocation: undefined!,
1786
+ key: undefined!,
1787
+ },
1788
+ },
1789
+ },
1790
+ }
1791
+
1792
+ if (
1793
+ nextHistory.unmaskOnReload ??
1794
+ this.options.unmaskOnReload ??
1795
+ false
1796
+ ) {
1797
+ nextHistory.state.__tempKey = this.tempLocationKey
1798
+ }
1799
+ }
1800
+
1801
+ nextHistory.state.__hashScrollIntoViewOptions =
1802
+ hashScrollIntoView ?? this.options.defaultHashScrollIntoView ?? true
1803
+
1804
+ this.shouldViewTransition = viewTransition
1805
+
1806
+ this.history[next.replace ? 'replace' : 'push'](
1807
+ nextHistory.href,
1808
+ nextHistory.state,
1809
+ { ignoreBlocker },
1810
+ )
1811
+ }
1812
+
1813
+ this.resetNextScroll = next.resetScroll ?? true
1814
+
1815
+ if (!this.history.subscribers.size) {
1816
+ this.load()
1817
+ }
1818
+
1819
+ return this.commitLocationPromise
1820
+ }
1821
+
1822
+ buildAndCommitLocation = ({
1823
+ replace,
1824
+ resetScroll,
1825
+ hashScrollIntoView,
1826
+ viewTransition,
1827
+ ignoreBlocker,
1828
+ href,
1829
+ ...rest
1830
+ }: BuildNextOptions & CommitLocationOptions = {}) => {
1831
+ if (href) {
1832
+ const currentIndex = this.history.location.state.__TSR_index
1833
+ const parsed = parseHref(href, {
1834
+ __TSR_index: replace ? currentIndex : currentIndex + 1,
1835
+ })
1836
+ rest.to = parsed.pathname
1837
+ rest.search = this.options.parseSearch(parsed.search)
1838
+ // remove the leading `#` from the hash
1839
+ rest.hash = parsed.hash.slice(1)
1840
+ }
1841
+
1842
+ const location = this.buildLocation({
1843
+ ...(rest as any),
1844
+ _includeValidateSearch: true,
1845
+ })
1846
+ return this.commitLocation({
1847
+ ...location,
1848
+ viewTransition,
1849
+ replace,
1850
+ resetScroll,
1851
+ hashScrollIntoView,
1852
+ ignoreBlocker,
1853
+ })
1854
+ }
1855
+
1856
+ navigate: NavigateFn = ({ to, reloadDocument, href, ...rest }) => {
1857
+ if (reloadDocument) {
1858
+ if (!href) {
1859
+ const location = this.buildLocation({ to, ...rest } as any)
1860
+ href = this.history.createHref(location.href)
1861
+ }
1862
+ if (rest.replace) {
1863
+ window.location.replace(href)
1864
+ } else {
1865
+ window.location.href = href
1866
+ }
1867
+ return
1868
+ }
1869
+
1870
+ return this.buildAndCommitLocation({
1871
+ ...rest,
1872
+ href,
1873
+ to: to as string,
1874
+ })
1875
+ }
1876
+
1877
+ latestLoadPromise: undefined | Promise<void>
1878
+
1879
+ load = async (opts?: { sync?: boolean }): Promise<void> => {
1880
+ this.latestLocation = this.parseLocation(this.latestLocation)
1881
+
1882
+ let redirect: ResolvedRedirect | undefined
1883
+ let notFound: NotFoundError | undefined
1884
+
1885
+ let loadPromise: Promise<void>
1886
+
1887
+ // eslint-disable-next-line prefer-const
1888
+ loadPromise = new Promise<void>((resolve) => {
1889
+ this.startSolidTransition(async () => {
1890
+ try {
1891
+ const next = this.latestLocation
1892
+ const prevLocation = this.state.resolvedLocation
1893
+
1894
+ // Cancel any pending matches
1895
+ this.cancelMatches()
1896
+
1897
+ let pendingMatches!: Array<AnyRouteMatch>
1898
+
1899
+ batch(() => {
1900
+ // this call breaks a route context of destination route after a redirect
1901
+ // we should be fine not eagerly calling this since we call it later
1902
+ // this.clearExpiredCache()
1903
+
1904
+ // Match the routes
1905
+ pendingMatches = this.matchRoutes(next)
1906
+
1907
+ // Ingest the new matches
1908
+ this.__store.setState((s) => ({
1909
+ ...s,
1910
+ status: 'pending',
1911
+ isLoading: true,
1912
+ location: next,
1913
+ pendingMatches,
1914
+ // If a cached moved to pendingMatches, remove it from cachedMatches
1915
+ cachedMatches: s.cachedMatches.filter((d) => {
1916
+ return !pendingMatches.find((e) => e.id === d.id)
1917
+ }),
1918
+ }))
1919
+ })
1920
+
1921
+ if (!this.state.redirect) {
1922
+ this.emit({
1923
+ type: 'onBeforeNavigate',
1924
+ ...getLocationChangeInfo({
1925
+ resolvedLocation: prevLocation,
1926
+ location: next,
1927
+ }),
1928
+ })
1929
+ }
1930
+
1931
+ this.emit({
1932
+ type: 'onBeforeLoad',
1933
+ ...getLocationChangeInfo({
1934
+ resolvedLocation: prevLocation,
1935
+ location: next,
1936
+ }),
1937
+ })
1938
+
1939
+ await this.loadMatches({
1940
+ sync: opts?.sync,
1941
+ matches: pendingMatches,
1942
+ location: next,
1943
+ // eslint-disable-next-line @typescript-eslint/require-await
1944
+ onReady: async () => {
1945
+ // eslint-disable-next-line @typescript-eslint/require-await
1946
+ this.startViewTransition(async () => {
1947
+ // this.viewTransitionPromise = createControlledPromise<true>()
1948
+
1949
+ // Commit the pending matches. If a previous match was
1950
+ // removed, place it in the cachedMatches
1951
+ let exitingMatches!: Array<AnyRouteMatch>
1952
+ let enteringMatches!: Array<AnyRouteMatch>
1953
+ let stayingMatches!: Array<AnyRouteMatch>
1954
+
1955
+ batch(() => {
1956
+ this.__store.setState((s) => {
1957
+ const previousMatches = s.matches
1958
+ const newMatches = s.pendingMatches || s.matches
1959
+
1960
+ exitingMatches = previousMatches.filter(
1961
+ (match) => !newMatches.find((d) => d.id === match.id),
1962
+ )
1963
+ enteringMatches = newMatches.filter(
1964
+ (match) =>
1965
+ !previousMatches.find((d) => d.id === match.id),
1966
+ )
1967
+ stayingMatches = previousMatches.filter((match) =>
1968
+ newMatches.find((d) => d.id === match.id),
1969
+ )
1970
+
1971
+ return {
1972
+ ...s,
1973
+ isLoading: false,
1974
+ loadedAt: Date.now(),
1975
+ matches: newMatches,
1976
+ pendingMatches: undefined,
1977
+ cachedMatches: [
1978
+ ...s.cachedMatches,
1979
+ ...exitingMatches.filter((d) => d.status !== 'error'),
1980
+ ],
1981
+ }
1982
+ })
1983
+ this.clearExpiredCache()
1984
+ })
1985
+
1986
+ //
1987
+ ;(
1988
+ [
1989
+ [exitingMatches, 'onLeave'],
1990
+ [enteringMatches, 'onEnter'],
1991
+ [stayingMatches, 'onStay'],
1992
+ ] as const
1993
+ ).forEach(([matches, hook]) => {
1994
+ matches.forEach((match) => {
1995
+ this.looseRoutesById[match.routeId]!.options[hook]?.(match)
1996
+ })
1997
+ })
1998
+ })
1999
+ },
2000
+ })
2001
+ } catch (err) {
2002
+ if (isResolvedRedirect(err)) {
2003
+ redirect = err
2004
+ if (!this.isServer) {
2005
+ this.navigate({
2006
+ ...redirect,
2007
+ replace: true,
2008
+ ignoreBlocker: true,
2009
+ })
2010
+ }
2011
+ } else if (isNotFound(err)) {
2012
+ notFound = err
2013
+ }
2014
+
2015
+ this.__store.setState((s) => ({
2016
+ ...s,
2017
+ statusCode: redirect
2018
+ ? redirect.statusCode
2019
+ : notFound
2020
+ ? 404
2021
+ : s.matches.some((d) => d.status === 'error')
2022
+ ? 500
2023
+ : 200,
2024
+ redirect,
2025
+ }))
2026
+ }
2027
+
2028
+ if (this.latestLoadPromise === loadPromise) {
2029
+ this.commitLocationPromise?.resolve()
2030
+ this.latestLoadPromise = undefined
2031
+ this.commitLocationPromise = undefined
2032
+ }
2033
+ resolve()
2034
+ })
2035
+ })
2036
+
2037
+ this.latestLoadPromise = loadPromise
2038
+
2039
+ await loadPromise
2040
+
2041
+ while (
2042
+ (this.latestLoadPromise as any) &&
2043
+ loadPromise !== this.latestLoadPromise
2044
+ ) {
2045
+ await this.latestLoadPromise
2046
+ }
2047
+
2048
+ if (this.hasNotFoundMatch()) {
2049
+ this.__store.setState((s) => ({
2050
+ ...s,
2051
+ statusCode: 404,
2052
+ }))
2053
+ }
2054
+ }
2055
+
2056
+ startViewTransition = (fn: () => Promise<void>) => {
2057
+ // Determine if we should start a view transition from the navigation
2058
+ // or from the router default
2059
+ const shouldViewTransition =
2060
+ this.shouldViewTransition ?? this.options.defaultViewTransition
2061
+
2062
+ // Reset the view transition flag
2063
+ delete this.shouldViewTransition
2064
+ // Attempt to start a view transition (or just apply the changes if we can't)
2065
+ if (
2066
+ shouldViewTransition &&
2067
+ typeof document !== 'undefined' &&
2068
+ 'startViewTransition' in document &&
2069
+ typeof document.startViewTransition === 'function'
2070
+ ) {
2071
+ // lib.dom.ts doesn't support viewTransition types variant yet.
2072
+ // TODO: Fix this when dom types are updated
2073
+ let startViewTransitionParams: any
2074
+
2075
+ if (
2076
+ typeof shouldViewTransition === 'object' &&
2077
+ this.isViewTransitionTypesSupported
2078
+ ) {
2079
+ startViewTransitionParams = {
2080
+ update: fn,
2081
+ types: shouldViewTransition.types,
2082
+ }
2083
+ } else {
2084
+ startViewTransitionParams = fn
2085
+ }
2086
+
2087
+ document.startViewTransition(startViewTransitionParams)
2088
+ } else {
2089
+ fn()
2090
+ }
2091
+ }
2092
+
2093
+ updateMatch = (
2094
+ id: string,
2095
+ updater: (match: AnyRouteMatch) => AnyRouteMatch,
2096
+ ) => {
2097
+ let updated!: AnyRouteMatch
2098
+ const isPending = this.state.pendingMatches?.find((d) => d.id === id)
2099
+ const isMatched = this.state.matches.find((d) => d.id === id)
2100
+ const isCached = this.state.cachedMatches.find((d) => d.id === id)
2101
+
2102
+ const matchesKey = isPending
2103
+ ? 'pendingMatches'
2104
+ : isMatched
2105
+ ? 'matches'
2106
+ : isCached
2107
+ ? 'cachedMatches'
2108
+ : ''
2109
+
2110
+ if (matchesKey) {
2111
+ this.__store.setState((s) => ({
2112
+ ...s,
2113
+ [matchesKey]: s[matchesKey]?.map((d) =>
2114
+ d.id === id ? (updated = updater(d)) : d,
2115
+ ),
2116
+ }))
2117
+ }
2118
+
2119
+ return updated
2120
+ }
2121
+
2122
+ getMatch = (matchId: string) => {
2123
+ return [
2124
+ ...this.state.cachedMatches,
2125
+ ...(this.state.pendingMatches ?? []),
2126
+ ...this.state.matches,
2127
+ ].find((d) => d.id === matchId)
2128
+ }
2129
+
2130
+ loadMatches = async ({
2131
+ location,
2132
+ matches,
2133
+ preload: allPreload,
2134
+ onReady,
2135
+ updateMatch = this.updateMatch,
2136
+ sync,
2137
+ }: {
2138
+ location: ParsedLocation
2139
+ matches: Array<AnyRouteMatch>
2140
+ preload?: boolean
2141
+ onReady?: () => Promise<void>
2142
+ updateMatch?: (
2143
+ id: string,
2144
+ updater: (match: AnyRouteMatch) => AnyRouteMatch,
2145
+ ) => void
2146
+ getMatch?: (matchId: string) => AnyRouteMatch | undefined
2147
+ sync?: boolean
2148
+ }): Promise<Array<MakeRouteMatch>> => {
2149
+ let firstBadMatchIndex: number | undefined
2150
+ let rendered = false
2151
+
2152
+ const triggerOnReady = async () => {
2153
+ if (!rendered) {
2154
+ rendered = true
2155
+ await onReady?.()
2156
+ }
2157
+ }
2158
+
2159
+ const resolvePreload = (matchId: string) => {
2160
+ return !!(allPreload && !this.state.matches.find((d) => d.id === matchId))
2161
+ }
2162
+
2163
+ if (!this.isServer && !this.state.matches.length) {
2164
+ triggerOnReady()
2165
+ }
2166
+
2167
+ const handleRedirectAndNotFound = (match: AnyRouteMatch, err: any) => {
2168
+ if (isResolvedRedirect(err)) {
2169
+ if (!err.reloadDocument) {
2170
+ throw err
2171
+ }
2172
+ }
2173
+
2174
+ if (isRedirect(err) || isNotFound(err)) {
2175
+ updateMatch(match.id, (prev) => ({
2176
+ ...prev,
2177
+ status: isRedirect(err)
2178
+ ? 'redirected'
2179
+ : isNotFound(err)
2180
+ ? 'notFound'
2181
+ : 'error',
2182
+ isFetching: false,
2183
+ error: err,
2184
+ beforeLoadPromise: undefined,
2185
+ loaderPromise: undefined,
2186
+ }))
2187
+
2188
+ if (!(err as any).routeId) {
2189
+ ;(err as any).routeId = match.routeId
2190
+ }
2191
+
2192
+ match.beforeLoadPromise?.resolve()
2193
+ match.loaderPromise?.resolve()
2194
+ match.loadPromise?.resolve()
2195
+
2196
+ if (isRedirect(err)) {
2197
+ rendered = true
2198
+ err = this.resolveRedirect({ ...err, _fromLocation: location })
2199
+ throw err
2200
+ } else if (isNotFound(err)) {
2201
+ this._handleNotFound(matches, err, {
2202
+ updateMatch,
2203
+ })
2204
+ this.serverSsr?.onMatchSettled({
2205
+ router: this,
2206
+ match: this.getMatch(match.id)!,
2207
+ })
2208
+ throw err
2209
+ }
2210
+ }
2211
+ }
2212
+
2213
+ try {
2214
+ await new Promise<void>((resolveAll, rejectAll) => {
2215
+ ;(async () => {
2216
+ try {
2217
+ const handleSerialError = (
2218
+ index: number,
2219
+ err: any,
2220
+ routerCode: string,
2221
+ ) => {
2222
+ const { id: matchId, routeId } = matches[index]!
2223
+ const route = this.looseRoutesById[routeId]!
2224
+
2225
+ // Much like suspense, we use a promise here to know if
2226
+ // we've been outdated by a new loadMatches call and
2227
+ // should abort the current async operation
2228
+ if (err instanceof Promise) {
2229
+ throw err
2230
+ }
2231
+
2232
+ err.routerCode = routerCode
2233
+ firstBadMatchIndex = firstBadMatchIndex ?? index
2234
+ handleRedirectAndNotFound(this.getMatch(matchId)!, err)
2235
+
2236
+ try {
2237
+ route.options.onError?.(err)
2238
+ } catch (errorHandlerErr) {
2239
+ err = errorHandlerErr
2240
+ handleRedirectAndNotFound(this.getMatch(matchId)!, err)
2241
+ }
2242
+
2243
+ updateMatch(matchId, (prev) => {
2244
+ prev.beforeLoadPromise?.resolve()
2245
+ prev.loadPromise?.resolve()
2246
+
2247
+ return {
2248
+ ...prev,
2249
+ error: err,
2250
+ status: 'error',
2251
+ isFetching: false,
2252
+ updatedAt: Date.now(),
2253
+ abortController: new AbortController(),
2254
+ beforeLoadPromise: undefined,
2255
+ }
2256
+ })
2257
+ }
2258
+
2259
+ for (const [index, { id: matchId, routeId }] of matches.entries()) {
2260
+ const existingMatch = this.getMatch(matchId)!
2261
+ const parentMatchId = matches[index - 1]?.id
2262
+
2263
+ const route = this.looseRoutesById[routeId]!
2264
+
2265
+ const pendingMs =
2266
+ route.options.pendingMs ?? this.options.defaultPendingMs
2267
+
2268
+ const shouldPending = !!(
2269
+ onReady &&
2270
+ !this.isServer &&
2271
+ !resolvePreload(matchId) &&
2272
+ (route.options.loader || route.options.beforeLoad) &&
2273
+ typeof pendingMs === 'number' &&
2274
+ pendingMs !== Infinity &&
2275
+ (route.options.pendingComponent ??
2276
+ this.options.defaultPendingComponent)
2277
+ )
2278
+
2279
+ let executeBeforeLoad = true
2280
+ if (
2281
+ // If we are in the middle of a load, either of these will be present
2282
+ // (not to be confused with `loadPromise`, which is always defined)
2283
+ existingMatch.beforeLoadPromise ||
2284
+ existingMatch.loaderPromise
2285
+ ) {
2286
+ if (shouldPending) {
2287
+ setTimeout(() => {
2288
+ try {
2289
+ // Update the match and prematurely resolve the loadMatches promise so that
2290
+ // the pending component can start rendering
2291
+ triggerOnReady()
2292
+ } catch {}
2293
+ }, pendingMs)
2294
+ }
2295
+
2296
+ // Wait for the beforeLoad to resolve before we continue
2297
+ await existingMatch.beforeLoadPromise
2298
+ executeBeforeLoad = this.getMatch(matchId)!.status !== 'success'
2299
+ }
2300
+ if (executeBeforeLoad) {
2301
+ // If we are not in the middle of a load OR the previous load failed, start it
2302
+ try {
2303
+ updateMatch(matchId, (prev) => ({
2304
+ ...prev,
2305
+ loadPromise: createControlledPromise<void>(() => {
2306
+ prev.loadPromise?.resolve()
2307
+ }),
2308
+ beforeLoadPromise: createControlledPromise<void>(),
2309
+ }))
2310
+
2311
+ const abortController = new AbortController()
2312
+
2313
+ let pendingTimeout: ReturnType<typeof setTimeout>
2314
+
2315
+ if (shouldPending) {
2316
+ // If we might show a pending component, we need to wait for the
2317
+ // pending promise to resolve before we start showing that state
2318
+ pendingTimeout = setTimeout(() => {
2319
+ try {
2320
+ // Update the match and prematurely resolve the loadMatches promise so that
2321
+ // the pending component can start rendering
2322
+ triggerOnReady()
2323
+ } catch {}
2324
+ }, pendingMs)
2325
+ }
2326
+
2327
+ const { paramsError, searchError } = this.getMatch(matchId)!
2328
+
2329
+ if (paramsError) {
2330
+ handleSerialError(index, paramsError, 'PARSE_PARAMS')
2331
+ }
2332
+
2333
+ if (searchError) {
2334
+ handleSerialError(index, searchError, 'VALIDATE_SEARCH')
2335
+ }
2336
+
2337
+ const getParentMatchContext = () =>
2338
+ parentMatchId
2339
+ ? this.getMatch(parentMatchId)!.context
2340
+ : (this.options.context ?? {})
2341
+
2342
+ updateMatch(matchId, (prev) => ({
2343
+ ...prev,
2344
+ isFetching: 'beforeLoad',
2345
+ fetchCount: prev.fetchCount + 1,
2346
+ abortController,
2347
+ pendingTimeout,
2348
+ context: {
2349
+ ...getParentMatchContext(),
2350
+ ...prev.__routeContext,
2351
+ },
2352
+ }))
2353
+
2354
+ const { search, params, context, cause } =
2355
+ this.getMatch(matchId)!
2356
+
2357
+ const preload = resolvePreload(matchId)
2358
+
2359
+ const beforeLoadFnContext: BeforeLoadContextOptions<
2360
+ any,
2361
+ any,
2362
+ any,
2363
+ any,
2364
+ any
2365
+ > = {
2366
+ search,
2367
+ abortController,
2368
+ params,
2369
+ preload,
2370
+ context,
2371
+ location,
2372
+ navigate: (opts: any) =>
2373
+ this.navigate({ ...opts, _fromLocation: location }),
2374
+ buildLocation: this.buildLocation,
2375
+ cause: preload ? 'preload' : cause,
2376
+ matches,
2377
+ }
2378
+
2379
+ const beforeLoadContext =
2380
+ (await route.options.beforeLoad?.(beforeLoadFnContext)) ??
2381
+ {}
2382
+
2383
+ if (
2384
+ isRedirect(beforeLoadContext) ||
2385
+ isNotFound(beforeLoadContext)
2386
+ ) {
2387
+ handleSerialError(index, beforeLoadContext, 'BEFORE_LOAD')
2388
+ }
2389
+
2390
+ updateMatch(matchId, (prev) => {
2391
+ return {
2392
+ ...prev,
2393
+ __beforeLoadContext: beforeLoadContext,
2394
+ context: {
2395
+ ...getParentMatchContext(),
2396
+ ...prev.__routeContext,
2397
+ ...beforeLoadContext,
2398
+ },
2399
+ abortController,
2400
+ }
2401
+ })
2402
+ } catch (err) {
2403
+ handleSerialError(index, err, 'BEFORE_LOAD')
2404
+ }
2405
+
2406
+ updateMatch(matchId, (prev) => {
2407
+ prev.beforeLoadPromise?.resolve()
2408
+
2409
+ return {
2410
+ ...prev,
2411
+ beforeLoadPromise: undefined,
2412
+ isFetching: false,
2413
+ }
2414
+ })
2415
+ }
2416
+ }
2417
+
2418
+ const validResolvedMatches = matches.slice(0, firstBadMatchIndex)
2419
+ const matchPromises: Array<Promise<AnyRouteMatch>> = []
2420
+
2421
+ validResolvedMatches.forEach(({ id: matchId, routeId }, index) => {
2422
+ matchPromises.push(
2423
+ (async () => {
2424
+ const { loaderPromise: prevLoaderPromise } =
2425
+ this.getMatch(matchId)!
2426
+
2427
+ let loaderShouldRunAsync = false
2428
+ let loaderIsRunningAsync = false
2429
+
2430
+ if (prevLoaderPromise) {
2431
+ await prevLoaderPromise
2432
+ const match = this.getMatch(matchId)!
2433
+ if (match.error) {
2434
+ handleRedirectAndNotFound(match, match.error)
2435
+ }
2436
+ } else {
2437
+ const parentMatchPromise = matchPromises[index - 1] as any
2438
+ const route = this.looseRoutesById[routeId]!
2439
+
2440
+ const getLoaderContext = (): LoaderFnContext => {
2441
+ const {
2442
+ params,
2443
+ loaderDeps,
2444
+ abortController,
2445
+ context,
2446
+ cause,
2447
+ } = this.getMatch(matchId)!
2448
+
2449
+ const preload = resolvePreload(matchId)
2450
+
2451
+ return {
2452
+ params,
2453
+ deps: loaderDeps,
2454
+ preload: !!preload,
2455
+ parentMatchPromise,
2456
+ abortController: abortController,
2457
+ context,
2458
+ location,
2459
+ navigate: (opts) =>
2460
+ this.navigate({ ...opts, _fromLocation: location }),
2461
+ cause: preload ? 'preload' : cause,
2462
+ route,
2463
+ }
2464
+ }
2465
+
2466
+ // This is where all of the stale-while-revalidate magic happens
2467
+ const age = Date.now() - this.getMatch(matchId)!.updatedAt
2468
+
2469
+ const preload = resolvePreload(matchId)
2470
+
2471
+ const staleAge = preload
2472
+ ? (route.options.preloadStaleTime ??
2473
+ this.options.defaultPreloadStaleTime ??
2474
+ 30_000) // 30 seconds for preloads by default
2475
+ : (route.options.staleTime ??
2476
+ this.options.defaultStaleTime ??
2477
+ 0)
2478
+
2479
+ const shouldReloadOption = route.options.shouldReload
2480
+
2481
+ // Default to reloading the route all the time
2482
+ // Allow shouldReload to get the last say,
2483
+ // if provided.
2484
+ const shouldReload =
2485
+ typeof shouldReloadOption === 'function'
2486
+ ? shouldReloadOption(getLoaderContext())
2487
+ : shouldReloadOption
2488
+
2489
+ updateMatch(matchId, (prev) => ({
2490
+ ...prev,
2491
+ loaderPromise: createControlledPromise<void>(),
2492
+ preload:
2493
+ !!preload &&
2494
+ !this.state.matches.find((d) => d.id === matchId),
2495
+ }))
2496
+
2497
+ const runLoader = async () => {
2498
+ try {
2499
+ // If the Matches component rendered
2500
+ // the pending component and needs to show it for
2501
+ // a minimum duration, we''ll wait for it to resolve
2502
+ // before committing to the match and resolving
2503
+ // the loadPromise
2504
+ const potentialPendingMinPromise = async () => {
2505
+ const latestMatch = this.getMatch(matchId)!
2506
+
2507
+ if (latestMatch.minPendingPromise) {
2508
+ await latestMatch.minPendingPromise
2509
+ }
2510
+ }
2511
+
2512
+ // Actually run the loader and handle the result
2513
+ try {
2514
+ this.loadRouteChunk(route)
2515
+
2516
+ updateMatch(matchId, (prev) => ({
2517
+ ...prev,
2518
+ isFetching: 'loader',
2519
+ }))
2520
+
2521
+ // Kick off the loader!
2522
+ const loaderData =
2523
+ await route.options.loader?.(getLoaderContext())
2524
+
2525
+ handleRedirectAndNotFound(
2526
+ this.getMatch(matchId)!,
2527
+ loaderData,
2528
+ )
2529
+
2530
+ // Lazy option can modify the route options,
2531
+ // so we need to wait for it to resolve before
2532
+ // we can use the options
2533
+ await route._lazyPromise
2534
+
2535
+ await potentialPendingMinPromise()
2536
+
2537
+ const assetContext = {
2538
+ matches,
2539
+ match: this.getMatch(matchId)!,
2540
+ params: this.getMatch(matchId)!.params,
2541
+ loaderData,
2542
+ }
2543
+ const headFnContent =
2544
+ route.options.head?.(assetContext)
2545
+ const meta = headFnContent?.meta
2546
+ const links = headFnContent?.links
2547
+ const headScripts = headFnContent?.scripts
2548
+
2549
+ const scripts = route.options.scripts?.(assetContext)
2550
+ const headers = route.options.headers?.({
2551
+ loaderData,
2552
+ })
2553
+
2554
+ updateMatch(matchId, (prev) => ({
2555
+ ...prev,
2556
+ error: undefined,
2557
+ status: 'success',
2558
+ isFetching: false,
2559
+ updatedAt: Date.now(),
2560
+ loaderData,
2561
+ meta,
2562
+ links,
2563
+ headScripts,
2564
+ headers,
2565
+ scripts,
2566
+ }))
2567
+ } catch (e) {
2568
+ let error = e
2569
+
2570
+ await potentialPendingMinPromise()
2571
+
2572
+ handleRedirectAndNotFound(this.getMatch(matchId)!, e)
2573
+
2574
+ try {
2575
+ route.options.onError?.(e)
2576
+ } catch (onErrorError) {
2577
+ error = onErrorError
2578
+ handleRedirectAndNotFound(
2579
+ this.getMatch(matchId)!,
2580
+ onErrorError,
2581
+ )
2582
+ }
2583
+
2584
+ updateMatch(matchId, (prev) => ({
2585
+ ...prev,
2586
+ error,
2587
+ status: 'error',
2588
+ isFetching: false,
2589
+ }))
2590
+ }
2591
+
2592
+ this.serverSsr?.onMatchSettled({
2593
+ router: this,
2594
+ match: this.getMatch(matchId)!,
2595
+ })
2596
+
2597
+ // Last but not least, wait for the the components
2598
+ // to be preloaded before we resolve the match
2599
+ await route._componentsPromise
2600
+ } catch (err) {
2601
+ updateMatch(matchId, (prev) => ({
2602
+ ...prev,
2603
+ loaderPromise: undefined,
2604
+ }))
2605
+ handleRedirectAndNotFound(this.getMatch(matchId)!, err)
2606
+ }
2607
+ }
2608
+
2609
+ // If the route is successful and still fresh, just resolve
2610
+ const { status, invalid } = this.getMatch(matchId)!
2611
+ loaderShouldRunAsync =
2612
+ status === 'success' &&
2613
+ (invalid || (shouldReload ?? age > staleAge))
2614
+ if (preload && route.options.preload === false) {
2615
+ // Do nothing
2616
+ } else if (loaderShouldRunAsync && !sync) {
2617
+ loaderIsRunningAsync = true
2618
+ ;(async () => {
2619
+ try {
2620
+ await runLoader()
2621
+ const { loaderPromise, loadPromise } =
2622
+ this.getMatch(matchId)!
2623
+ loaderPromise?.resolve()
2624
+ loadPromise?.resolve()
2625
+ updateMatch(matchId, (prev) => ({
2626
+ ...prev,
2627
+ loaderPromise: undefined,
2628
+ }))
2629
+ } catch (err) {
2630
+ if (isResolvedRedirect(err)) {
2631
+ await this.navigate(err)
2632
+ }
2633
+ }
2634
+ })()
2635
+ } else if (
2636
+ status !== 'success' ||
2637
+ (loaderShouldRunAsync && sync)
2638
+ ) {
2639
+ await runLoader()
2640
+ }
2641
+ }
2642
+ if (!loaderIsRunningAsync) {
2643
+ const { loaderPromise, loadPromise } =
2644
+ this.getMatch(matchId)!
2645
+ loaderPromise?.resolve()
2646
+ loadPromise?.resolve()
2647
+ }
2648
+
2649
+ updateMatch(matchId, (prev) => ({
2650
+ ...prev,
2651
+ isFetching: loaderIsRunningAsync ? prev.isFetching : false,
2652
+ loaderPromise: loaderIsRunningAsync
2653
+ ? prev.loaderPromise
2654
+ : undefined,
2655
+ invalid: false,
2656
+ }))
2657
+ return this.getMatch(matchId)!
2658
+ })(),
2659
+ )
2660
+ })
2661
+
2662
+ await Promise.all(matchPromises)
2663
+
2664
+ resolveAll()
2665
+ } catch (err) {
2666
+ rejectAll(err)
2667
+ }
2668
+ })()
2669
+ })
2670
+ await triggerOnReady()
2671
+ } catch (err) {
2672
+ if (isRedirect(err) || isNotFound(err)) {
2673
+ if (isNotFound(err) && !allPreload) {
2674
+ await triggerOnReady()
2675
+ }
2676
+ throw err
2677
+ }
2678
+ }
2679
+
2680
+ return matches
2681
+ }
2682
+
2683
+ invalidate = <TRouter extends AnyRouter = typeof this>(opts?: {
2684
+ filter?: (d: MakeRouteMatchUnion<TRouter>) => boolean
2685
+ sync?: boolean
2686
+ }) => {
2687
+ const invalidate = (d: MakeRouteMatch<TRouteTree>) => {
2688
+ if (opts?.filter?.(d as MakeRouteMatchUnion<TRouter>) ?? true) {
2689
+ return {
2690
+ ...d,
2691
+ invalid: true,
2692
+ ...(d.status === 'error'
2693
+ ? ({ status: 'pending', error: undefined } as const)
2694
+ : {}),
2695
+ }
2696
+ }
2697
+ return d
2698
+ }
2699
+
2700
+ this.__store.setState((s) => ({
2701
+ ...s,
2702
+ matches: s.matches.map(invalidate),
2703
+ cachedMatches: s.cachedMatches.map(invalidate),
2704
+ pendingMatches: s.pendingMatches?.map(invalidate),
2705
+ }))
2706
+
2707
+ return this.load({ sync: opts?.sync })
2708
+ }
2709
+
2710
+ resolveRedirect = (err: AnyRedirect): ResolvedRedirect => {
2711
+ const redirect = err as ResolvedRedirect
2712
+
2713
+ if (!redirect.href) {
2714
+ redirect.href = this.buildLocation(redirect as any).href
2715
+ }
2716
+
2717
+ return redirect
2718
+ }
2719
+
2720
+ clearCache = <TRouter extends AnyRouter = typeof this>(opts?: {
2721
+ filter?: (d: MakeRouteMatchUnion<TRouter>) => boolean
2722
+ }) => {
2723
+ const filter = opts?.filter
2724
+ if (filter !== undefined) {
2725
+ this.__store.setState((s) => {
2726
+ return {
2727
+ ...s,
2728
+ cachedMatches: s.cachedMatches.filter(
2729
+ (m) => !filter(m as MakeRouteMatchUnion<TRouter>),
2730
+ ),
2731
+ }
2732
+ })
2733
+ } else {
2734
+ this.__store.setState((s) => {
2735
+ return {
2736
+ ...s,
2737
+ cachedMatches: [],
2738
+ }
2739
+ })
2740
+ }
2741
+ }
2742
+
2743
+ clearExpiredCache = () => {
2744
+ // This is where all of the garbage collection magic happens
2745
+ const filter = (d: MakeRouteMatch<TRouteTree>) => {
2746
+ const route = this.looseRoutesById[d.routeId]!
2747
+
2748
+ if (!route.options.loader) {
2749
+ return true
2750
+ }
2751
+
2752
+ // If the route was preloaded, use the preloadGcTime
2753
+ // otherwise, use the gcTime
2754
+ const gcTime =
2755
+ (d.preload
2756
+ ? (route.options.preloadGcTime ?? this.options.defaultPreloadGcTime)
2757
+ : (route.options.gcTime ?? this.options.defaultGcTime)) ??
2758
+ 5 * 60 * 1000
2759
+
2760
+ return !(d.status !== 'error' && Date.now() - d.updatedAt < gcTime)
2761
+ }
2762
+ this.clearCache({ filter })
2763
+ }
2764
+
2765
+ loadRouteChunk = (route: AnyRoute) => {
2766
+ if (route._lazyPromise === undefined) {
2767
+ if (route.lazyFn) {
2768
+ route._lazyPromise = route.lazyFn().then((lazyRoute) => {
2769
+ // explicitly don't copy over the lazy route's id
2770
+ const { id: _id, ...options } = lazyRoute.options
2771
+ Object.assign(route.options, options)
2772
+ })
2773
+ } else {
2774
+ route._lazyPromise = Promise.resolve()
2775
+ }
2776
+ }
2777
+
2778
+ // If for some reason lazy resolves more lazy components...
2779
+ // We'll wait for that before pre attempt to preload any
2780
+ // components themselves.
2781
+ if (route._componentsPromise === undefined) {
2782
+ route._componentsPromise = route._lazyPromise.then(() =>
2783
+ Promise.all(
2784
+ componentTypes.map(async (type) => {
2785
+ const component = route.options[type]
2786
+ if ((component as any)?.preload) {
2787
+ await (component as any).preload()
2788
+ }
2789
+ }),
2790
+ ),
2791
+ )
2792
+ }
2793
+ return route._componentsPromise
2794
+ }
2795
+
2796
+ preloadRoute = async <
2797
+ TFrom extends RoutePaths<TRouteTree> | string = string,
2798
+ TTo extends string | undefined = undefined,
2799
+ TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
2800
+ TMaskTo extends string = '',
2801
+ >(
2802
+ opts: NavigateOptions<
2803
+ Router<
2804
+ TRouteTree,
2805
+ TTrailingSlashOption,
2806
+ TRouterHistory,
2807
+ TDehydrated,
2808
+ TSerializedError
2809
+ >,
2810
+ TFrom,
2811
+ TTo,
2812
+ TMaskFrom,
2813
+ TMaskTo
2814
+ >,
2815
+ ): Promise<Array<AnyRouteMatch> | undefined> => {
2816
+ const next = this.buildLocation(opts as any)
2817
+
2818
+ let matches = this.matchRoutes(next, {
2819
+ throwOnError: true,
2820
+ preload: true,
2821
+ dest: opts,
2822
+ })
2823
+
2824
+ const activeMatchIds = new Set(
2825
+ [...this.state.matches, ...(this.state.pendingMatches ?? [])].map(
2826
+ (d) => d.id,
2827
+ ),
2828
+ )
2829
+
2830
+ const loadedMatchIds = new Set([
2831
+ ...activeMatchIds,
2832
+ ...this.state.cachedMatches.map((d) => d.id),
2833
+ ])
2834
+
2835
+ // If the matches are already loaded, we need to add them to the cachedMatches
2836
+ batch(() => {
2837
+ matches.forEach((match) => {
2838
+ if (!loadedMatchIds.has(match.id)) {
2839
+ this.__store.setState((s) => ({
2840
+ ...s,
2841
+ cachedMatches: [...(s.cachedMatches as any), match],
2842
+ }))
2843
+ }
2844
+ })
2845
+ })
2846
+
2847
+ try {
2848
+ matches = await this.loadMatches({
2849
+ matches,
2850
+ location: next,
2851
+ preload: true,
2852
+ updateMatch: (id, updater) => {
2853
+ // Don't update the match if it's currently loaded
2854
+ if (activeMatchIds.has(id)) {
2855
+ matches = matches.map((d) => (d.id === id ? updater(d) : d))
2856
+ } else {
2857
+ this.updateMatch(id, updater)
2858
+ }
2859
+ },
2860
+ })
2861
+
2862
+ return matches
2863
+ } catch (err) {
2864
+ if (isRedirect(err)) {
2865
+ if (err.reloadDocument) {
2866
+ return undefined
2867
+ }
2868
+ return await this.preloadRoute({
2869
+ ...(err as any),
2870
+ _fromLocation: next,
2871
+ })
2872
+ }
2873
+ if (!isNotFound(err)) {
2874
+ // Preload errors are not fatal, but we should still log them
2875
+ console.error(err)
2876
+ }
2877
+ return undefined
2878
+ }
2879
+ }
2880
+
2881
+ matchRoute = <
2882
+ TFrom extends RoutePaths<TRouteTree> = '/',
2883
+ TTo extends string | undefined = undefined,
2884
+ TResolved = ResolveRelativePath<TFrom, NoInfer<TTo>>,
2885
+ >(
2886
+ location: ToOptions<
2887
+ Router<
2888
+ TRouteTree,
2889
+ TTrailingSlashOption,
2890
+ TRouterHistory,
2891
+ TDehydrated,
2892
+ TSerializedError
2893
+ >,
2894
+ TFrom,
2895
+ TTo
2896
+ >,
2897
+ opts?: MatchRouteOptions,
2898
+ ): false | RouteById<TRouteTree, TResolved>['types']['allParams'] => {
2899
+ const matchLocation = {
2900
+ ...location,
2901
+ to: location.to
2902
+ ? this.resolvePathWithBase(
2903
+ (location.from || '') as string,
2904
+ location.to as string,
2905
+ )
2906
+ : undefined,
2907
+ params: location.params || {},
2908
+ leaveParams: true,
2909
+ }
2910
+ const next = this.buildLocation(matchLocation as any)
2911
+
2912
+ if (opts?.pending && this.state.status !== 'pending') {
2913
+ return false
2914
+ }
2915
+
2916
+ const pending =
2917
+ opts?.pending === undefined ? !this.state.isLoading : opts.pending
2918
+
2919
+ const baseLocation = pending
2920
+ ? this.latestLocation
2921
+ : this.state.resolvedLocation || this.state.location
2922
+
2923
+ const match = matchPathname(this.basepath, baseLocation.pathname, {
2924
+ ...opts,
2925
+ to: next.pathname,
2926
+ }) as any
2927
+
2928
+ if (!match) {
2929
+ return false
2930
+ }
2931
+ if (location.params) {
2932
+ if (!deepEqual(match, location.params, { partial: true })) {
2933
+ return false
2934
+ }
2935
+ }
2936
+
2937
+ if (match && (opts?.includeSearch ?? true)) {
2938
+ return deepEqual(baseLocation.search, next.search, { partial: true })
2939
+ ? match
2940
+ : false
2941
+ }
2942
+
2943
+ return match
2944
+ }
2945
+
2946
+ ssr?: {
2947
+ manifest: Manifest | undefined
2948
+ serializer: StartSerializer
2949
+ }
2950
+
2951
+ serverSsr?: {
2952
+ injectedHtml: Array<InjectedHtmlEntry>
2953
+ injectHtml: (getHtml: () => string | Promise<string>) => Promise<void>
2954
+ injectScript: (
2955
+ getScript: () => string | Promise<string>,
2956
+ opts?: { logScript?: boolean },
2957
+ ) => Promise<void>
2958
+ streamValue: (key: string, value: any) => void
2959
+ streamedKeys: Set<string>
2960
+ onMatchSettled: (opts: { router: AnyRouter; match: AnyRouteMatch }) => any
2961
+ }
2962
+
2963
+ clientSsr?: {
2964
+ getStreamedValue: <T>(key: string) => T | undefined
2965
+ }
2966
+
2967
+ _handleNotFound = (
2968
+ matches: Array<AnyRouteMatch>,
2969
+ err: NotFoundError,
2970
+ {
2971
+ updateMatch = this.updateMatch,
2972
+ }: {
2973
+ updateMatch?: (
2974
+ id: string,
2975
+ updater: (match: AnyRouteMatch) => AnyRouteMatch,
2976
+ ) => void
2977
+ } = {},
2978
+ ) => {
2979
+ const matchesByRouteId = Object.fromEntries(
2980
+ matches.map((match) => [match.routeId, match]),
2981
+ ) as Record<string, AnyRouteMatch>
2982
+
2983
+ // Start at the route that errored or default to the root route
2984
+ let routeCursor =
2985
+ (err.global
2986
+ ? this.looseRoutesById[rootRouteId]
2987
+ : this.looseRoutesById[err.routeId]) ||
2988
+ this.looseRoutesById[rootRouteId]!
2989
+
2990
+ // Go up the tree until we find a route with a notFoundComponent or we hit the root
2991
+ while (
2992
+ !routeCursor.options.notFoundComponent &&
2993
+ !this.options.defaultNotFoundComponent &&
2994
+ routeCursor.id !== rootRouteId
2995
+ ) {
2996
+ routeCursor = routeCursor.parentRoute
2997
+
2998
+ invariant(
2999
+ routeCursor,
3000
+ 'Found invalid route tree while trying to find not-found handler.',
3001
+ )
3002
+ }
3003
+
3004
+ const match = matchesByRouteId[routeCursor.id]
3005
+
3006
+ invariant(match, 'Could not find match for route: ' + routeCursor.id)
3007
+
3008
+ // Assign the error to the match
3009
+
3010
+ updateMatch(match.id, (prev) => ({
3011
+ ...prev,
3012
+ status: 'notFound',
3013
+ error: err,
3014
+ isFetching: false,
3015
+ }))
3016
+
3017
+ if ((err as any).routerCode === 'BEFORE_LOAD' && routeCursor.parentRoute) {
3018
+ err.routeId = routeCursor.parentRoute.id
3019
+ this._handleNotFound(matches, err, {
3020
+ updateMatch,
3021
+ })
3022
+ }
3023
+ }
3024
+
3025
+ hasNotFoundMatch = () => {
3026
+ return this.__store.state.matches.some(
3027
+ (d) => d.status === 'notFound' || d.globalNotFound,
3028
+ )
3029
+ }
3030
+ }
3031
+
3032
+ // A function that takes an import() argument which is a function and returns a new function that will
3033
+ // proxy arguments from the caller to the imported function, retaining all type
3034
+ // information along the way
3035
+ export function lazyFn<
3036
+ T extends Record<string, (...args: Array<any>) => any>,
3037
+ TKey extends keyof T = 'default',
3038
+ >(fn: () => Promise<T>, key?: TKey) {
3039
+ return async (
3040
+ ...args: Parameters<T[TKey]>
3041
+ ): Promise<Awaited<ReturnType<T[TKey]>>> => {
3042
+ const imported = await fn()
3043
+ return imported[key || 'default'](...args)
3044
+ }
3045
+ }
3046
+
3047
+ export class SearchParamError extends Error {}
3048
+
3049
+ export class PathParamError extends Error {}
3050
+
3051
+ export function getInitialRouterState(
3052
+ location: ParsedLocation,
3053
+ ): RouterState<any> {
3054
+ return {
3055
+ loadedAt: 0,
3056
+ isLoading: false,
3057
+ isTransitioning: false,
3058
+ status: 'idle',
3059
+ resolvedLocation: undefined,
3060
+ location,
3061
+ matches: [],
3062
+ pendingMatches: [],
3063
+ cachedMatches: [],
3064
+ statusCode: 200,
3065
+ }
3066
+ }