@tanstack/react-router 1.20.1 → 1.20.3-alpha.1

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 (320) hide show
  1. package/README.md +31 -0
  2. package/dist/cjs/Asset.cjs +41 -0
  3. package/dist/cjs/Asset.cjs.map +1 -0
  4. package/dist/cjs/Asset.d.cts +2 -0
  5. package/dist/cjs/CatchBoundary.cjs +16 -12
  6. package/dist/cjs/CatchBoundary.cjs.map +1 -1
  7. package/dist/cjs/CatchBoundary.d.cts +8 -32
  8. package/dist/cjs/ClientOnly.cjs +20 -0
  9. package/dist/cjs/ClientOnly.cjs.map +1 -0
  10. package/dist/cjs/ClientOnly.d.cts +29 -0
  11. package/dist/cjs/HeadContent.cjs +155 -0
  12. package/dist/cjs/HeadContent.cjs.map +1 -0
  13. package/dist/cjs/HeadContent.d.cts +7 -0
  14. package/dist/cjs/Match.cjs +252 -0
  15. package/dist/cjs/Match.cjs.map +1 -0
  16. package/dist/cjs/Match.d.cts +8 -0
  17. package/dist/cjs/Matches.cjs +39 -287
  18. package/dist/cjs/Matches.cjs.map +1 -1
  19. package/dist/cjs/Matches.d.cts +23 -83
  20. package/dist/cjs/RouterProvider.cjs +17 -140
  21. package/dist/cjs/RouterProvider.cjs.map +1 -1
  22. package/dist/cjs/RouterProvider.d.cts +8 -27
  23. package/dist/cjs/SafeFragment.cjs +8 -0
  24. package/dist/cjs/SafeFragment.cjs.map +1 -0
  25. package/dist/cjs/SafeFragment.d.cts +1 -0
  26. package/dist/cjs/ScriptOnce.cjs +28 -0
  27. package/dist/cjs/ScriptOnce.cjs.map +1 -0
  28. package/dist/cjs/ScriptOnce.d.cts +5 -0
  29. package/dist/cjs/Scripts.cjs +51 -0
  30. package/dist/cjs/Scripts.cjs.map +1 -0
  31. package/dist/cjs/Scripts.d.cts +1 -0
  32. package/dist/cjs/ScrollRestoration.cjs +39 -0
  33. package/dist/cjs/ScrollRestoration.cjs.map +1 -0
  34. package/dist/cjs/ScrollRestoration.d.cts +14 -0
  35. package/dist/cjs/Transitioner.cjs +115 -0
  36. package/dist/cjs/Transitioner.cjs.map +1 -0
  37. package/dist/cjs/Transitioner.d.cts +1 -0
  38. package/dist/cjs/awaited.cjs +12 -65
  39. package/dist/cjs/awaited.cjs.map +1 -1
  40. package/dist/cjs/awaited.d.cts +4 -4
  41. package/dist/cjs/fileRoute.cjs +41 -15
  42. package/dist/cjs/fileRoute.cjs.map +1 -1
  43. package/dist/cjs/fileRoute.d.cts +33 -108
  44. package/dist/cjs/history.d.cts +1 -0
  45. package/dist/cjs/index.cjs +216 -73
  46. package/dist/cjs/index.cjs.map +1 -1
  47. package/dist/cjs/index.d.cts +52 -29
  48. package/dist/cjs/lazyRouteComponent.cjs +40 -29
  49. package/dist/cjs/lazyRouteComponent.cjs.map +1 -1
  50. package/dist/cjs/lazyRouteComponent.d.cts +1 -1
  51. package/dist/cjs/link.cjs +212 -106
  52. package/dist/cjs/link.cjs.map +1 -1
  53. package/dist/cjs/link.d.cts +41 -86
  54. package/dist/cjs/matchContext.cjs +27 -0
  55. package/dist/cjs/matchContext.cjs.map +1 -0
  56. package/dist/cjs/matchContext.d.cts +3 -0
  57. package/dist/cjs/not-found.cjs +9 -15
  58. package/dist/cjs/not-found.cjs.map +1 -1
  59. package/dist/cjs/not-found.d.cts +5 -22
  60. package/dist/cjs/renderRouteNotFound.cjs +22 -0
  61. package/dist/cjs/renderRouteNotFound.cjs.map +1 -0
  62. package/dist/cjs/renderRouteNotFound.d.cts +2 -0
  63. package/dist/cjs/route.cjs +110 -79
  64. package/dist/cjs/route.cjs.map +1 -1
  65. package/dist/cjs/route.d.cts +64 -361
  66. package/dist/cjs/router.cjs +12 -1237
  67. package/dist/cjs/router.cjs.map +1 -1
  68. package/dist/cjs/router.d.cts +69 -237
  69. package/dist/cjs/routerContext.cjs +1 -1
  70. package/dist/cjs/routerContext.cjs.map +1 -1
  71. package/dist/cjs/routerContext.d.cts +7 -2
  72. package/dist/cjs/scroll-restoration.cjs +16 -177
  73. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  74. package/dist/cjs/scroll-restoration.d.cts +1 -18
  75. package/dist/cjs/serializer.d.cts +6 -0
  76. package/dist/cjs/structuralSharing.d.cts +8 -0
  77. package/dist/cjs/typePrimitives.d.cts +16 -0
  78. package/dist/cjs/useBlocker.cjs +138 -9
  79. package/dist/cjs/useBlocker.cjs.map +1 -1
  80. package/dist/cjs/useBlocker.d.cts +64 -7
  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 +15 -0
  85. package/dist/cjs/useLoaderData.cjs.map +1 -0
  86. package/dist/cjs/useLoaderData.d.cts +8 -0
  87. package/dist/cjs/useLoaderDeps.cjs +14 -0
  88. package/dist/cjs/useLoaderDeps.cjs.map +1 -0
  89. package/dist/cjs/useLoaderDeps.d.cts +8 -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 +47 -0
  94. package/dist/cjs/useMatch.cjs.map +1 -0
  95. package/dist/cjs/useMatch.d.cts +10 -0
  96. package/dist/cjs/useNavigate.cjs +18 -19
  97. package/dist/cjs/useNavigate.cjs.map +1 -1
  98. package/dist/cjs/useNavigate.d.cts +4 -8
  99. package/dist/cjs/useParams.cjs +8 -8
  100. package/dist/cjs/useParams.cjs.map +1 -1
  101. package/dist/cjs/useParams.d.cts +9 -8
  102. package/dist/cjs/useRouteContext.cjs +3 -3
  103. package/dist/cjs/useRouteContext.cjs.map +1 -1
  104. package/dist/cjs/useRouteContext.d.cts +3 -7
  105. package/dist/cjs/useRouter.cjs.map +1 -1
  106. package/dist/cjs/useRouter.d.cts +3 -4
  107. package/dist/cjs/useRouterState.cjs +18 -1
  108. package/dist/cjs/useRouterState.cjs.map +1 -1
  109. package/dist/cjs/useRouterState.d.cts +8 -6
  110. package/dist/cjs/useSearch.cjs +7 -4
  111. package/dist/cjs/useSearch.cjs.map +1 -1
  112. package/dist/cjs/useSearch.d.cts +9 -7
  113. package/dist/cjs/utils.cjs +40 -122
  114. package/dist/cjs/utils.cjs.map +1 -1
  115. package/dist/cjs/utils.d.cts +46 -50
  116. package/dist/esm/Asset.d.ts +2 -0
  117. package/dist/esm/Asset.js +41 -0
  118. package/dist/esm/Asset.js.map +1 -0
  119. package/dist/esm/CatchBoundary.d.ts +8 -32
  120. package/dist/esm/CatchBoundary.js +16 -12
  121. package/dist/esm/CatchBoundary.js.map +1 -1
  122. package/dist/esm/ClientOnly.d.ts +29 -0
  123. package/dist/esm/ClientOnly.js +20 -0
  124. package/dist/esm/ClientOnly.js.map +1 -0
  125. package/dist/esm/HeadContent.d.ts +7 -0
  126. package/dist/esm/HeadContent.js +139 -0
  127. package/dist/esm/HeadContent.js.map +1 -0
  128. package/dist/esm/Match.d.ts +8 -0
  129. package/dist/esm/Match.js +235 -0
  130. package/dist/esm/Match.js.map +1 -0
  131. package/dist/esm/Matches.d.ts +23 -83
  132. package/dist/esm/Matches.js +36 -284
  133. package/dist/esm/Matches.js.map +1 -1
  134. package/dist/esm/RouterProvider.d.ts +8 -27
  135. package/dist/esm/RouterProvider.js +20 -126
  136. package/dist/esm/RouterProvider.js.map +1 -1
  137. package/dist/esm/SafeFragment.d.ts +1 -0
  138. package/dist/esm/SafeFragment.js +8 -0
  139. package/dist/esm/SafeFragment.js.map +1 -0
  140. package/dist/esm/ScriptOnce.d.ts +5 -0
  141. package/dist/esm/ScriptOnce.js +28 -0
  142. package/dist/esm/ScriptOnce.js.map +1 -0
  143. package/dist/esm/Scripts.d.ts +1 -0
  144. package/dist/esm/Scripts.js +51 -0
  145. package/dist/esm/Scripts.js.map +1 -0
  146. package/dist/esm/ScrollRestoration.d.ts +14 -0
  147. package/dist/esm/ScrollRestoration.js +39 -0
  148. package/dist/esm/ScrollRestoration.js.map +1 -0
  149. package/dist/esm/Transitioner.d.ts +1 -0
  150. package/dist/esm/Transitioner.js +98 -0
  151. package/dist/esm/Transitioner.js.map +1 -0
  152. package/dist/esm/awaited.d.ts +4 -4
  153. package/dist/esm/awaited.js +12 -65
  154. package/dist/esm/awaited.js.map +1 -1
  155. package/dist/esm/fileRoute.d.ts +33 -108
  156. package/dist/esm/fileRoute.js +38 -12
  157. package/dist/esm/fileRoute.js.map +1 -1
  158. package/dist/esm/history.d.ts +1 -0
  159. package/dist/esm/index.d.ts +52 -29
  160. package/dist/esm/index.js +41 -29
  161. package/dist/esm/index.js.map +1 -1
  162. package/dist/esm/lazyRouteComponent.d.ts +1 -1
  163. package/dist/esm/lazyRouteComponent.js +40 -29
  164. package/dist/esm/lazyRouteComponent.js.map +1 -1
  165. package/dist/esm/link.d.ts +41 -86
  166. package/dist/esm/link.js +212 -106
  167. package/dist/esm/link.js.map +1 -1
  168. package/dist/esm/matchContext.d.ts +3 -0
  169. package/dist/esm/matchContext.js +10 -0
  170. package/dist/esm/matchContext.js.map +1 -0
  171. package/dist/esm/not-found.d.ts +5 -22
  172. package/dist/esm/not-found.js +9 -15
  173. package/dist/esm/not-found.js.map +1 -1
  174. package/dist/esm/renderRouteNotFound.d.ts +2 -0
  175. package/dist/esm/renderRouteNotFound.js +22 -0
  176. package/dist/esm/renderRouteNotFound.js.map +1 -0
  177. package/dist/esm/route.d.ts +64 -361
  178. package/dist/esm/route.js +103 -72
  179. package/dist/esm/route.js.map +1 -1
  180. package/dist/esm/router.d.ts +69 -237
  181. package/dist/esm/router.js +13 -1238
  182. package/dist/esm/router.js.map +1 -1
  183. package/dist/esm/routerContext.d.ts +7 -2
  184. package/dist/esm/routerContext.js +1 -1
  185. package/dist/esm/routerContext.js.map +1 -1
  186. package/dist/esm/scroll-restoration.d.ts +1 -18
  187. package/dist/esm/scroll-restoration.js +17 -161
  188. package/dist/esm/scroll-restoration.js.map +1 -1
  189. package/dist/esm/serializer.d.ts +6 -0
  190. package/dist/esm/structuralSharing.d.ts +8 -0
  191. package/dist/esm/typePrimitives.d.ts +16 -0
  192. package/dist/esm/useBlocker.d.ts +64 -7
  193. package/dist/esm/useBlocker.js +138 -9
  194. package/dist/esm/useBlocker.js.map +1 -1
  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 +8 -0
  199. package/dist/esm/useLoaderData.js +15 -0
  200. package/dist/esm/useLoaderData.js.map +1 -0
  201. package/dist/esm/useLoaderDeps.d.ts +8 -0
  202. package/dist/esm/useLoaderDeps.js +14 -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 +10 -0
  208. package/dist/esm/useMatch.js +30 -0
  209. package/dist/esm/useMatch.js.map +1 -0
  210. package/dist/esm/useNavigate.d.ts +4 -8
  211. package/dist/esm/useNavigate.js +18 -19
  212. package/dist/esm/useNavigate.js.map +1 -1
  213. package/dist/esm/useParams.d.ts +9 -8
  214. package/dist/esm/useParams.js +8 -8
  215. package/dist/esm/useParams.js.map +1 -1
  216. package/dist/esm/useRouteContext.d.ts +3 -7
  217. package/dist/esm/useRouteContext.js +2 -2
  218. package/dist/esm/useRouteContext.js.map +1 -1
  219. package/dist/esm/useRouter.d.ts +3 -4
  220. package/dist/esm/useRouter.js.map +1 -1
  221. package/dist/esm/useRouterState.d.ts +8 -6
  222. package/dist/esm/useRouterState.js +18 -1
  223. package/dist/esm/useRouterState.js.map +1 -1
  224. package/dist/esm/useSearch.d.ts +9 -7
  225. package/dist/esm/useSearch.js +6 -3
  226. package/dist/esm/useSearch.js.map +1 -1
  227. package/dist/esm/utils.d.ts +46 -50
  228. package/dist/esm/utils.js +41 -123
  229. package/dist/esm/utils.js.map +1 -1
  230. package/package.json +30 -31
  231. package/src/Asset.tsx +40 -0
  232. package/src/CatchBoundary.tsx +35 -19
  233. package/src/ClientOnly.tsx +68 -0
  234. package/src/HeadContent.tsx +174 -0
  235. package/src/Match.tsx +330 -0
  236. package/src/Matches.tsx +149 -558
  237. package/src/RouterProvider.tsx +58 -212
  238. package/src/SafeFragment.tsx +5 -0
  239. package/src/ScriptOnce.tsx +32 -0
  240. package/src/Scripts.tsx +65 -0
  241. package/src/ScrollRestoration.tsx +69 -0
  242. package/src/Transitioner.tsx +130 -0
  243. package/src/awaited.tsx +16 -87
  244. package/src/fileRoute.ts +145 -248
  245. package/src/history.ts +2 -1
  246. package/src/index.tsx +368 -30
  247. package/src/lazyRouteComponent.tsx +68 -54
  248. package/src/link.tsx +397 -522
  249. package/src/matchContext.tsx +8 -0
  250. package/src/not-found.tsx +13 -34
  251. package/src/renderRouteNotFound.tsx +27 -0
  252. package/src/route.tsx +572 -0
  253. package/src/router.ts +99 -2067
  254. package/src/routerContext.tsx +8 -2
  255. package/src/scroll-restoration.tsx +23 -224
  256. package/src/serializer.ts +7 -0
  257. package/src/structuralSharing.ts +47 -0
  258. package/src/typePrimitives.ts +84 -0
  259. package/src/useBlocker.tsx +297 -15
  260. package/src/useCanGoBack.ts +5 -0
  261. package/src/useLoaderData.tsx +80 -0
  262. package/src/useLoaderDeps.tsx +58 -0
  263. package/src/useLocation.tsx +41 -0
  264. package/src/useMatch.tsx +119 -0
  265. package/src/useNavigate.tsx +41 -61
  266. package/src/useParams.tsx +88 -23
  267. package/src/useRouteContext.ts +24 -18
  268. package/src/useRouter.tsx +4 -5
  269. package/src/useRouterState.tsx +52 -10
  270. package/src/useSearch.tsx +87 -24
  271. package/src/utils.ts +97 -312
  272. package/dist/cjs/createServerFn.cjs +0 -40
  273. package/dist/cjs/createServerFn.cjs.map +0 -1
  274. package/dist/cjs/createServerFn.d.cts +0 -44
  275. package/dist/cjs/defer.cjs +0 -30
  276. package/dist/cjs/defer.cjs.map +0 -1
  277. package/dist/cjs/defer.d.cts +0 -25
  278. package/dist/cjs/location.d.cts +0 -12
  279. package/dist/cjs/path.cjs +0 -213
  280. package/dist/cjs/path.cjs.map +0 -1
  281. package/dist/cjs/path.d.cts +0 -24
  282. package/dist/cjs/qss.cjs +0 -45
  283. package/dist/cjs/qss.cjs.map +0 -1
  284. package/dist/cjs/qss.d.cts +0 -2
  285. package/dist/cjs/redirects.cjs +0 -16
  286. package/dist/cjs/redirects.cjs.map +0 -1
  287. package/dist/cjs/redirects.d.cts +0 -18
  288. package/dist/cjs/routeInfo.d.cts +0 -31
  289. package/dist/cjs/searchParams.cjs +0 -63
  290. package/dist/cjs/searchParams.cjs.map +0 -1
  291. package/dist/cjs/searchParams.d.cts +0 -7
  292. package/dist/esm/createServerFn.d.ts +0 -44
  293. package/dist/esm/createServerFn.js +0 -40
  294. package/dist/esm/createServerFn.js.map +0 -1
  295. package/dist/esm/defer.d.ts +0 -25
  296. package/dist/esm/defer.js +0 -30
  297. package/dist/esm/defer.js.map +0 -1
  298. package/dist/esm/location.d.ts +0 -12
  299. package/dist/esm/path.d.ts +0 -24
  300. package/dist/esm/path.js +0 -213
  301. package/dist/esm/path.js.map +0 -1
  302. package/dist/esm/qss.d.ts +0 -2
  303. package/dist/esm/qss.js +0 -45
  304. package/dist/esm/qss.js.map +0 -1
  305. package/dist/esm/redirects.d.ts +0 -18
  306. package/dist/esm/redirects.js +0 -16
  307. package/dist/esm/redirects.js.map +0 -1
  308. package/dist/esm/routeInfo.d.ts +0 -31
  309. package/dist/esm/searchParams.d.ts +0 -7
  310. package/dist/esm/searchParams.js +0 -63
  311. package/dist/esm/searchParams.js.map +0 -1
  312. package/src/createServerFn.ts +0 -107
  313. package/src/defer.ts +0 -70
  314. package/src/location.ts +0 -13
  315. package/src/path.ts +0 -280
  316. package/src/qss.ts +0 -53
  317. package/src/redirects.ts +0 -56
  318. package/src/route.ts +0 -1356
  319. package/src/routeInfo.ts +0 -63
  320. package/src/searchParams.ts +0 -79
package/src/link.tsx CHANGED
@@ -1,386 +1,64 @@
1
1
  import * as React from 'react'
2
- import { useMatch } from './Matches'
3
- import { useRouterState } from './useRouterState'
4
- import { useRouter } from './useRouter'
5
- import { Trim } from './fileRoute'
6
- import { AnyRoute, ReactNode, RootSearchSchema } from './route'
7
- import { RouteByPath, RoutePaths, RoutePathsAutoComplete } from './routeInfo'
8
- import { RegisteredRouter } from './router'
2
+ import { flushSync } from 'react-dom'
9
3
  import {
10
- Expand,
11
- IsUnion,
12
- MakeDifferenceOptional,
13
- NoInfer,
14
- NonNullableUpdater,
15
- PickRequired,
16
- Updater,
17
- WithoutEmpty,
18
4
  deepEqual,
5
+ exactPathTest,
19
6
  functionalUpdate,
20
- } from './utils'
21
- import { HistoryState } from '@tanstack/history'
22
-
23
- export type CleanPath<T extends string> = T extends `${infer L}//${infer R}`
24
- ? CleanPath<`${CleanPath<L>}/${CleanPath<R>}`>
25
- : T extends `${infer L}//`
26
- ? `${CleanPath<L>}/`
27
- : T extends `//${infer L}`
28
- ? `/${CleanPath<L>}`
29
- : T
30
-
31
- export type Split<S, TIncludeTrailingSlash = true> = S extends unknown
32
- ? string extends S
33
- ? string[]
34
- : S extends string
35
- ? CleanPath<S> extends ''
36
- ? []
37
- : TIncludeTrailingSlash extends true
38
- ? CleanPath<S> extends `${infer T}/`
39
- ? [...Split<T>, '/']
40
- : CleanPath<S> extends `/${infer U}`
41
- ? Split<U>
42
- : CleanPath<S> extends `${infer T}/${infer U}`
43
- ? [...Split<T>, ...Split<U>]
44
- : [S]
45
- : CleanPath<S> extends `${infer T}/${infer U}`
46
- ? [...Split<T>, ...Split<U>]
47
- : S extends string
48
- ? [S]
49
- : never
50
- : never
51
- : never
52
-
53
- export type ParsePathParams<T extends string> = keyof {
54
- [K in Trim<Split<T>[number], '_'> as K extends `$${infer L}`
55
- ? L extends ''
56
- ? '_splat'
57
- : L
58
- : never]: K
59
- }
60
-
61
- export type Join<T, Delimiter extends string = '/'> = T extends []
62
- ? ''
63
- : T extends [infer L extends string]
64
- ? L
65
- : T extends [infer L extends string, ...infer Tail extends [...string[]]]
66
- ? CleanPath<`${L}${Delimiter}${Join<Tail>}`>
67
- : never
68
-
69
- export type Last<T extends any[]> = T extends [...infer _, infer L] ? L : never
70
-
71
- export type RemoveTrailingSlashes<T> = T extends `${infer R}/`
72
- ? RemoveTrailingSlashes<R>
73
- : T
74
-
75
- export type RemoveLeadingSlashes<T> = T extends `/${infer R}`
76
- ? RemoveLeadingSlashes<R>
77
- : T
78
-
79
- export type SearchPaths<
80
- TPaths,
81
- TSearchPath extends string,
82
- > = TPaths extends `${TSearchPath}/${infer TRest}` ? TRest : never
83
-
84
- export type SearchRelativePathAutoComplete<
85
- TTo extends string,
86
- TSearchPath extends string,
87
- TPaths,
88
- SearchedPaths = SearchPaths<TPaths, TSearchPath>,
89
- > = SearchedPaths extends string ? `${TTo}/${SearchedPaths}` : never
90
-
91
- export type RelativeToParentPathAutoComplete<
92
- TFrom extends string,
93
- TTo extends string,
94
- TPaths,
95
- TResolvedPath extends string = RemoveTrailingSlashes<
96
- ResolveRelativePath<TFrom, TTo>
97
- >,
98
- > =
99
- | SearchRelativePathAutoComplete<TTo, TResolvedPath, TPaths>
100
- | (TResolvedPath extends '' ? never : `${TTo}/../`)
101
-
102
- export type RelativeToCurrentPathAutoComplete<
103
- TFrom extends string,
104
- TTo extends string,
105
- TRestTo extends string,
106
- TPaths,
107
- TResolvedPath extends
108
- string = RemoveTrailingSlashes<`${RemoveTrailingSlashes<TFrom>}/${RemoveLeadingSlashes<TRestTo>}`>,
109
- > = SearchRelativePathAutoComplete<TTo, TResolvedPath, TPaths>
110
-
111
- export type AbsolutePathAutoComplete<TFrom extends string, TPaths> =
112
- | (string extends TFrom
113
- ? './'
114
- : TFrom extends `/`
115
- ? never
116
- : SearchPaths<
117
- TPaths,
118
- RemoveTrailingSlashes<TFrom>
119
- > extends infer SearchedPaths
120
- ? SearchedPaths extends ''
121
- ? never
122
- : './'
123
- : never)
124
- | (string extends TFrom ? '../' : TFrom extends `/` ? never : '../')
125
- | TPaths
126
-
127
- export type RelativeToPathAutoComplete<
128
- TRouteTree extends AnyRoute,
129
- TFrom extends string,
130
- TTo extends string,
131
- TPaths = RoutePaths<TRouteTree>,
132
- > = TTo extends `..${string}`
133
- ? RelativeToParentPathAutoComplete<TFrom, RemoveTrailingSlashes<TTo>, TPaths>
134
- : TTo extends `./${infer TRestTTo}`
135
- ? RelativeToCurrentPathAutoComplete<
136
- TFrom,
137
- RemoveTrailingSlashes<TTo>,
138
- TRestTTo,
139
- TPaths
140
- >
141
- : AbsolutePathAutoComplete<TFrom, TPaths>
142
-
143
- export type NavigateOptions<
144
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
145
- TFrom extends RoutePaths<TRouteTree> | string = string,
146
- TTo extends string = '',
147
- TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
148
- TMaskTo extends string = '',
149
- > = ToOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> & {
150
- // `replace` is a boolean that determines whether the navigation should replace the current history entry or push a new one.
151
- replace?: boolean
152
- resetScroll?: boolean
153
- // If set to `true`, the link's underlying navigate() call will be wrapped in a `React.startTransition` call. Defaults to `true`.
154
- startTransition?: boolean
155
- }
156
-
157
- export type ToOptions<
158
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
159
- TFrom extends RoutePaths<TRouteTree> | string = string,
160
- TTo extends string = '',
161
- TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
162
- TMaskTo extends string = '',
163
- > = ToSubOptions<TRouteTree, TFrom, TTo> & {
164
- mask?: ToMaskOptions<TRouteTree, TMaskFrom, TMaskTo>
165
- }
166
-
167
- export type ToMaskOptions<
168
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
169
- TMaskFrom extends RoutePaths<TRouteTree> | string = string,
170
- TMaskTo extends string = '',
171
- > = ToSubOptions<TRouteTree, TMaskFrom, TMaskTo> & {
172
- unmaskOnReload?: boolean
173
- }
174
-
175
- export type ToSubOptions<
176
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
177
- TFrom extends RoutePaths<TRouteTree> | string = string,
178
- TTo extends string = '',
179
- > = {
180
- to?: ToPathOption<TRouteTree, TFrom, TTo>
181
- hash?: true | Updater<string>
182
- state?: true | NonNullableUpdater<HistoryState>
183
- // The source route path. This is automatically set when using route-level APIs, but for type-safe relative routing on the router itself, this is required
184
- from?: RoutePathsAutoComplete<TRouteTree, TFrom>
185
- // // When using relative route paths, this option forces resolution from the current path, instead of the route API's path or `from` path
186
- } & CheckPath<TRouteTree, {}, TFrom, TTo> &
187
- SearchParamOptions<TRouteTree, TFrom, TTo> &
188
- PathParamOptions<TRouteTree, TFrom, TTo>
189
-
190
- type ParamsReducer<TFrom, TTo> = TTo | ((current: TFrom) => TTo)
191
-
192
- type ParamVariant = 'PATH' | 'SEARCH'
193
- type ExcludeRootSearchSchema<T, Excluded = Exclude<T, RootSearchSchema>> = [
194
- Excluded,
195
- ] extends [never]
196
- ? {}
197
- : Excluded
198
-
199
- export type ResolveRoute<
200
- TRouteTree extends AnyRoute,
201
- TFrom,
202
- TTo,
203
- TPath = RemoveTrailingSlashes<
204
- string extends TTo ? TFrom : ResolveRelativePath<TFrom, TTo>
205
- >,
206
- > =
207
- RouteByPath<TRouteTree, `${TPath & string}/`> extends never
208
- ? RouteByPath<TRouteTree, TPath>
209
- : RouteByPath<TRouteTree, `${TPath & string}/`>
210
-
211
- type PostProcessParams<
212
- T,
213
- TParamVariant extends ParamVariant,
214
- > = TParamVariant extends 'SEARCH' ? ExcludeRootSearchSchema<T> : T
215
-
216
- export type ParamOptions<
217
- TRouteTree extends AnyRoute,
218
- TFrom,
219
- TTo extends string,
220
- TParamVariant extends ParamVariant,
221
- TFromRouteType extends
222
- | 'allParams'
223
- | 'fullSearchSchema' = TParamVariant extends 'PATH'
224
- ? 'allParams'
225
- : 'fullSearchSchema',
226
- TToRouteType extends
227
- | 'allParams'
228
- | 'fullSearchSchemaInput' = TParamVariant extends 'PATH'
229
- ? 'allParams'
230
- : 'fullSearchSchemaInput',
231
- TFromParams = PostProcessParams<
232
- RouteByPath<TRouteTree, TFrom>['types'][TFromRouteType],
233
- TParamVariant
234
- >,
235
- TToParams = PostProcessParams<
236
- ResolveRoute<TRouteTree, TFrom, TTo>['types'][TToRouteType],
237
- TParamVariant
238
- >,
239
- TRelativeToParams = TParamVariant extends 'SEARCH'
240
- ? TToParams
241
- : true extends IsUnion<TFromParams>
242
- ? TToParams
243
- : MakeDifferenceOptional<TFromParams, TToParams>,
244
- TReducer = ParamsReducer<TFromParams, TRelativeToParams>,
245
- > =
246
- Expand<WithoutEmpty<PickRequired<TRelativeToParams>>> extends never
247
- ? Partial<MakeParamOption<TParamVariant, true | TReducer>>
248
- : TFromParams extends Expand<WithoutEmpty<PickRequired<TRelativeToParams>>>
249
- ? MakeParamOption<TParamVariant, true | TReducer>
250
- : MakeParamOption<TParamVariant, TReducer>
251
-
252
- type MakeParamOption<
253
- TParamVariant extends ParamVariant,
254
- T,
255
- > = TParamVariant extends 'PATH'
256
- ? MakePathParamOptions<T>
257
- : MakeSearchParamOptions<T>
258
- type MakeSearchParamOptions<T> = { search: T }
259
- type MakePathParamOptions<T> = { params: T }
260
-
261
- export type SearchParamOptions<
262
- TRouteTree extends AnyRoute,
263
- TFrom,
264
- TTo extends string,
265
- > = ParamOptions<TRouteTree, TFrom, TTo, 'SEARCH'>
266
-
267
- export type PathParamOptions<
268
- TRouteTree extends AnyRoute,
269
- TFrom,
270
- TTo extends string,
271
- > = ParamOptions<TRouteTree, TFrom, TTo, 'PATH'>
272
-
273
- export type ToPathOption<
274
- TRouteTree extends AnyRoute = AnyRoute,
275
- TFrom extends RoutePaths<TRouteTree> | string = string,
276
- TTo extends string = '',
277
- > =
278
- | TTo
279
- | RelativeToPathAutoComplete<
280
- TRouteTree,
281
- NoInfer<TFrom> extends string ? NoInfer<TFrom> : '',
282
- NoInfer<TTo> & string
283
- >
284
-
285
- export interface ActiveOptions {
286
- exact?: boolean
287
- includeHash?: boolean
288
- includeSearch?: boolean
289
- }
290
-
291
- export type LinkOptions<
292
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
293
- TFrom extends RoutePaths<TRouteTree> | string = string,
294
- TTo extends string = '',
295
- TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
296
- TMaskTo extends string = '',
297
- > = NavigateOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> & {
298
- // The standard anchor tag target attribute
299
- target?: HTMLAnchorElement['target']
300
- // Defaults to `{ exact: false, includeHash: false }`
301
- activeOptions?: ActiveOptions
302
- // If set, will preload the linked route on hover and cache it for this many milliseconds in hopes that the user will eventually navigate there.
303
- preload?: false | 'intent'
304
- // Delay intent preloading by this many milliseconds. If the intent exits before this delay, the preload will be cancelled.
305
- preloadDelay?: number
306
- // If true, will render the link without the href attribute
307
- disabled?: boolean
308
- }
309
-
310
- export type CheckPath<TRouteTree extends AnyRoute, TPass, TFrom, TTo> =
311
- ResolveRoute<TRouteTree, TFrom, TTo> extends never
312
- ? string extends TFrom
313
- ? RemoveTrailingSlashes<TTo> extends '.' | '..'
314
- ? TPass
315
- : CheckPathError<TRouteTree>
316
- : CheckPathError<TRouteTree>
317
- : TPass
318
-
319
- export type CheckPathError<TRouteTree extends AnyRoute> = {
320
- to: RoutePaths<TRouteTree>
321
- }
7
+ preloadWarning,
8
+ removeTrailingSlash,
9
+ } from '@tanstack/router-core'
10
+ import { useRouterState } from './useRouterState'
11
+ import { useRouter } from './useRouter'
322
12
 
323
- export type ResolveRelativePath<TFrom, TTo = '.'> = TFrom extends string
324
- ? TTo extends string
325
- ? TTo extends '.'
326
- ? TFrom
327
- : TTo extends `./`
328
- ? Join<[TFrom, '/']>
329
- : TTo extends `./${infer TRest}`
330
- ? ResolveRelativePath<TFrom, TRest>
331
- : TTo extends `/${infer TRest}`
332
- ? TTo
333
- : Split<TTo> extends ['..', ...infer ToRest]
334
- ? Split<TFrom> extends [...infer FromRest, infer FromTail]
335
- ? ToRest extends ['/']
336
- ? Join<['/', ...FromRest, '/']>
337
- : ResolveRelativePath<Join<FromRest>, Join<ToRest>>
338
- : never
339
- : Split<TTo> extends ['.', ...infer ToRest]
340
- ? ToRest extends ['/']
341
- ? Join<[TFrom, '/']>
342
- : ResolveRelativePath<TFrom, Join<ToRest>>
343
- : CleanPath<Join<['/', ...Split<TFrom>, ...Split<TTo>]>>
344
- : never
345
- : never
346
-
347
- type LinkCurrentTargetElement = {
348
- preloadTimeout?: null | ReturnType<typeof setTimeout>
349
- }
13
+ import {
14
+ useForwardedRef,
15
+ useIntersectionObserver,
16
+ useLayoutEffect,
17
+ } from './utils'
350
18
 
351
- const preloadWarning = 'Error preloading route! ☝️'
19
+ import { useMatches } from './Matches'
20
+ import type {
21
+ AnyRouter,
22
+ Constrain,
23
+ LinkCurrentTargetElement,
24
+ LinkOptions,
25
+ RegisteredRouter,
26
+ RoutePaths,
27
+ } from '@tanstack/router-core'
28
+ import type { ReactNode } from 'react'
29
+ import type {
30
+ ValidateLinkOptions,
31
+ ValidateLinkOptionsArray,
32
+ } from './typePrimitives'
352
33
 
353
34
  export function useLinkProps<
354
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
355
- TFrom extends RoutePaths<TRouteTree> | string = string,
356
- TTo extends string = '',
357
- TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
358
- TMaskTo extends string = '',
35
+ TRouter extends AnyRouter = RegisteredRouter,
36
+ const TFrom extends string = string,
37
+ const TTo extends string | undefined = undefined,
38
+ const TMaskFrom extends string = TFrom,
39
+ const TMaskTo extends string = '',
359
40
  >(
360
- options: UseLinkPropsOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>,
361
- ): React.AnchorHTMLAttributes<HTMLAnchorElement> {
41
+ options: UseLinkPropsOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
42
+ forwardedRef?: React.ForwardedRef<Element>,
43
+ ): React.ComponentPropsWithRef<'a'> {
362
44
  const router = useRouter()
363
- const matchPathname = useMatch({
364
- strict: false,
365
- select: (s) => s.pathname,
366
- })
45
+ const [isTransitioning, setIsTransitioning] = React.useState(false)
46
+ const hasRenderFetched = React.useRef(false)
47
+ const innerRef = useForwardedRef(forwardedRef)
367
48
 
368
49
  const {
369
50
  // custom props
370
51
  activeProps = () => ({ className: 'active' }),
371
52
  inactiveProps = () => ({}),
372
53
  activeOptions,
373
- hash,
374
- search,
375
- params,
376
54
  to,
377
- state,
378
- mask,
379
55
  preload: userPreload,
380
56
  preloadDelay: userPreloadDelay,
57
+ hashScrollIntoView,
381
58
  replace,
382
59
  startTransition,
383
60
  resetScroll,
61
+ viewTransition,
384
62
  // element props
385
63
  children,
386
64
  target,
@@ -392,72 +70,159 @@ export function useLinkProps<
392
70
  onMouseEnter,
393
71
  onMouseLeave,
394
72
  onTouchStart,
73
+ ignoreBlocker,
395
74
  ...rest
396
75
  } = options
397
76
 
77
+ const {
78
+ // prevent these from being returned
79
+ params: _params,
80
+ search: _search,
81
+ hash: _hash,
82
+ state: _state,
83
+ mask: _mask,
84
+ reloadDocument: _reloadDocument,
85
+ ...propsSafeToSpread
86
+ } = rest
87
+
398
88
  // If this link simply reloads the current route,
399
89
  // make sure it has a new key so it will trigger a data refresh
400
90
 
401
91
  // If this `to` is a valid external URL, return
402
92
  // null for LinkUtils
403
93
 
404
- const dest = {
405
- from: options.to ? matchPathname : undefined,
406
- ...options,
407
- }
94
+ const type: 'internal' | 'external' = React.useMemo(() => {
95
+ try {
96
+ new URL(`${to}`)
97
+ return 'external'
98
+ } catch {}
99
+ return 'internal'
100
+ }, [to])
101
+
102
+ // subscribe to search params to re-build location if it changes
103
+ const currentSearch = useRouterState({
104
+ select: (s) => s.location.search,
105
+ structuralSharing: true as any,
106
+ })
408
107
 
409
- let type: 'internal' | 'external' = 'internal'
108
+ // when `from` is not supplied, use the leaf route of the current matches as the `from` location
109
+ // so relative routing works as expected
110
+ const from = useMatches({
111
+ select: (matches) => options.from ?? matches[matches.length - 1]?.fullPath,
112
+ })
113
+ // Use it as the default `from` location
114
+ const _options = React.useMemo(() => ({ ...options, from }), [options, from])
410
115
 
411
- try {
412
- new URL(`${to}`)
413
- type = 'external'
414
- } catch {}
116
+ const next = React.useMemo(
117
+ () => router.buildLocation(_options as any),
118
+ // eslint-disable-next-line react-hooks/exhaustive-deps
119
+ [router, _options, currentSearch],
120
+ )
415
121
 
416
- const next = router.buildLocation(dest as any)
417
- const preload = userPreload ?? router.options.defaultPreload
122
+ const preload = React.useMemo(() => {
123
+ if (_options.reloadDocument) {
124
+ return false
125
+ }
126
+ return userPreload ?? router.options.defaultPreload
127
+ }, [router.options.defaultPreload, userPreload, _options.reloadDocument])
418
128
  const preloadDelay =
419
129
  userPreloadDelay ?? router.options.defaultPreloadDelay ?? 0
420
130
 
421
131
  const isActive = useRouterState({
422
132
  select: (s) => {
423
- // Compare path/hash for matches
424
- const currentPathSplit = s.location.pathname.split('/')
425
- const nextPathSplit = next.pathname.split('/')
426
- const pathIsFuzzyEqual = nextPathSplit.every(
427
- (d, i) => d === currentPathSplit[i],
428
- )
429
- // Combine the matches based on user router.options
430
- const pathTest = activeOptions?.exact
431
- ? s.location.pathname === next.pathname
432
- : pathIsFuzzyEqual
433
- const hashTest = activeOptions?.includeHash
434
- ? s.location.hash === next.hash
435
- : true
436
- const searchTest =
437
- activeOptions?.includeSearch ?? true
438
- ? deepEqual(s.location.search, next.search, !activeOptions?.exact)
439
- : true
440
-
441
- // The final "active" test
442
- return pathTest && hashTest && searchTest
133
+ if (activeOptions?.exact) {
134
+ const testExact = exactPathTest(
135
+ s.location.pathname,
136
+ next.pathname,
137
+ router.basepath,
138
+ )
139
+ if (!testExact) {
140
+ return false
141
+ }
142
+ } else {
143
+ const currentPathSplit = removeTrailingSlash(
144
+ s.location.pathname,
145
+ router.basepath,
146
+ ).split('/')
147
+ const nextPathSplit = removeTrailingSlash(
148
+ next.pathname,
149
+ router.basepath,
150
+ ).split('/')
151
+
152
+ const pathIsFuzzyEqual = nextPathSplit.every(
153
+ (d, i) => d === currentPathSplit[i],
154
+ )
155
+ if (!pathIsFuzzyEqual) {
156
+ return false
157
+ }
158
+ }
159
+
160
+ if (activeOptions?.includeSearch ?? true) {
161
+ const searchTest = deepEqual(s.location.search, next.search, {
162
+ partial: !activeOptions?.exact,
163
+ ignoreUndefined: !activeOptions?.explicitUndefined,
164
+ })
165
+ if (!searchTest) {
166
+ return false
167
+ }
168
+ }
169
+
170
+ if (activeOptions?.includeHash) {
171
+ return s.location.hash === next.hash
172
+ }
173
+ return true
443
174
  },
444
175
  })
445
176
 
177
+ const doPreload = React.useCallback(() => {
178
+ router.preloadRoute(_options as any).catch((err) => {
179
+ console.warn(err)
180
+ console.warn(preloadWarning)
181
+ })
182
+ }, [_options, router])
183
+
184
+ const preloadViewportIoCallback = React.useCallback(
185
+ (entry: IntersectionObserverEntry | undefined) => {
186
+ if (entry?.isIntersecting) {
187
+ doPreload()
188
+ }
189
+ },
190
+ [doPreload],
191
+ )
192
+
193
+ useIntersectionObserver(
194
+ innerRef,
195
+ preloadViewportIoCallback,
196
+ { rootMargin: '100px' },
197
+ { disabled: !!disabled || !(preload === 'viewport') },
198
+ )
199
+
200
+ useLayoutEffect(() => {
201
+ if (hasRenderFetched.current) {
202
+ return
203
+ }
204
+ if (!disabled && preload === 'render') {
205
+ doPreload()
206
+ hasRenderFetched.current = true
207
+ }
208
+ }, [disabled, doPreload, preload])
209
+
446
210
  if (type === 'external') {
447
211
  return {
448
- ...rest,
212
+ ...propsSafeToSpread,
213
+ ref: innerRef as React.ComponentPropsWithRef<'a'>['ref'],
449
214
  type,
450
215
  href: to,
451
- children,
452
- target,
453
- disabled,
454
- style,
455
- className,
456
- onClick,
457
- onFocus,
458
- onMouseEnter,
459
- onMouseLeave,
460
- onTouchStart,
216
+ ...(children && { children }),
217
+ ...(target && { target }),
218
+ ...(disabled && { disabled }),
219
+ ...(style && { style }),
220
+ ...(className && { className }),
221
+ ...(onClick && { onClick }),
222
+ ...(onFocus && { onFocus }),
223
+ ...(onMouseEnter && { onMouseEnter }),
224
+ ...(onMouseLeave && { onMouseLeave }),
225
+ ...(onTouchStart && { onTouchStart }),
461
226
  }
462
227
  }
463
228
 
@@ -472,22 +237,31 @@ export function useLinkProps<
472
237
  ) {
473
238
  e.preventDefault()
474
239
 
475
- // All is well? Navigate!
476
- router.commitLocation({ ...next, replace, resetScroll, startTransition })
477
- }
478
- }
240
+ flushSync(() => {
241
+ setIsTransitioning(true)
242
+ })
479
243
 
480
- const doPreload = () => {
481
- React.startTransition(() => {
482
- router.preloadRoute(dest as any).catch((err) => {
483
- console.warn(err)
484
- console.warn(preloadWarning)
244
+ const unsub = router.subscribe('onResolved', () => {
245
+ unsub()
246
+ setIsTransitioning(false)
485
247
  })
486
- })
248
+
249
+ // All is well? Navigate!
250
+ // N.B. we don't call `router.commitLocation(next) here because we want to run `validateSearch` before committing
251
+ return router.navigate({
252
+ ..._options,
253
+ replace,
254
+ resetScroll,
255
+ hashScrollIntoView,
256
+ startTransition,
257
+ viewTransition,
258
+ ignoreBlocker,
259
+ } as any)
260
+ }
487
261
  }
488
262
 
489
263
  // The click handler
490
- const handleFocus = (e: MouseEvent) => {
264
+ const handleFocus = (_: MouseEvent) => {
491
265
  if (disabled) return
492
266
  if (preload) {
493
267
  doPreload()
@@ -498,15 +272,15 @@ export function useLinkProps<
498
272
 
499
273
  const handleEnter = (e: MouseEvent) => {
500
274
  if (disabled) return
501
- const target = (e.target || {}) as LinkCurrentTargetElement
275
+ const eventTarget = (e.target || {}) as LinkCurrentTargetElement
502
276
 
503
277
  if (preload) {
504
- if (target.preloadTimeout) {
278
+ if (eventTarget.preloadTimeout) {
505
279
  return
506
280
  }
507
281
 
508
- target.preloadTimeout = setTimeout(() => {
509
- target.preloadTimeout = null
282
+ eventTarget.preloadTimeout = setTimeout(() => {
283
+ eventTarget.preloadTimeout = null
510
284
  doPreload()
511
285
  }, preloadDelay)
512
286
  }
@@ -514,18 +288,18 @@ export function useLinkProps<
514
288
 
515
289
  const handleLeave = (e: MouseEvent) => {
516
290
  if (disabled) return
517
- const target = (e.target || {}) as LinkCurrentTargetElement
291
+ const eventTarget = (e.target || {}) as LinkCurrentTargetElement
518
292
 
519
- if (target.preloadTimeout) {
520
- clearTimeout(target.preloadTimeout)
521
- target.preloadTimeout = null
293
+ if (eventTarget.preloadTimeout) {
294
+ clearTimeout(eventTarget.preloadTimeout)
295
+ eventTarget.preloadTimeout = null
522
296
  }
523
297
  }
524
298
 
525
299
  const composeHandlers =
526
- (handlers: (undefined | ((e: any) => void))[]) =>
527
- (e: React.SyntheticEvent) => {
528
- if (e.persist) e.persist()
300
+ (handlers: Array<undefined | ((e: any) => void)>) =>
301
+ (e: { persist?: () => void; defaultPrevented: boolean }) => {
302
+ e.persist?.()
529
303
  handlers.filter(Boolean).forEach((handler) => {
530
304
  if (e.defaultPrevented) return
531
305
  handler!(e)
@@ -534,143 +308,244 @@ export function useLinkProps<
534
308
 
535
309
  // Get the active props
536
310
  const resolvedActiveProps: React.HTMLAttributes<HTMLAnchorElement> = isActive
537
- ? functionalUpdate(activeProps as any, {}) ?? {}
311
+ ? (functionalUpdate(activeProps as any, {}) ?? {})
538
312
  : {}
539
313
 
540
314
  // Get the inactive props
541
315
  const resolvedInactiveProps: React.HTMLAttributes<HTMLAnchorElement> =
542
- isActive ? {} : functionalUpdate(inactiveProps, {}) ?? {}
316
+ isActive ? {} : functionalUpdate(inactiveProps, {})
317
+
318
+ const resolvedClassName = [
319
+ className,
320
+ resolvedActiveProps.className,
321
+ resolvedInactiveProps.className,
322
+ ]
323
+ .filter(Boolean)
324
+ .join(' ')
325
+
326
+ const resolvedStyle = {
327
+ ...style,
328
+ ...resolvedActiveProps.style,
329
+ ...resolvedInactiveProps.style,
330
+ }
543
331
 
544
332
  return {
333
+ ...propsSafeToSpread,
545
334
  ...resolvedActiveProps,
546
335
  ...resolvedInactiveProps,
547
- ...rest,
548
336
  href: disabled
549
337
  ? undefined
550
338
  : next.maskedLocation
551
- ? next.maskedLocation.href
552
- : next.href,
339
+ ? router.history.createHref(next.maskedLocation.href)
340
+ : router.history.createHref(next.href),
341
+ ref: innerRef as React.ComponentPropsWithRef<'a'>['ref'],
553
342
  onClick: composeHandlers([onClick, handleClick]),
554
343
  onFocus: composeHandlers([onFocus, handleFocus]),
555
344
  onMouseEnter: composeHandlers([onMouseEnter, handleEnter]),
556
345
  onMouseLeave: composeHandlers([onMouseLeave, handleLeave]),
557
346
  onTouchStart: composeHandlers([onTouchStart, handleTouchStart]),
347
+ disabled: !!disabled,
558
348
  target,
559
- style: {
560
- ...style,
561
- ...resolvedActiveProps.style,
562
- ...resolvedInactiveProps.style,
563
- },
564
- className:
565
- [
566
- className,
567
- resolvedActiveProps.className,
568
- resolvedInactiveProps.className,
569
- ]
570
- .filter(Boolean)
571
- .join(' ') || undefined,
572
- ...(disabled
573
- ? {
574
- role: 'link',
575
- 'aria-disabled': true,
576
- }
577
- : undefined),
578
- ['data-status']: isActive ? 'active' : undefined,
349
+ ...(Object.keys(resolvedStyle).length && { style: resolvedStyle }),
350
+ ...(resolvedClassName && { className: resolvedClassName }),
351
+ ...(disabled && {
352
+ role: 'link',
353
+ 'aria-disabled': true,
354
+ }),
355
+ ...(isActive && { 'data-status': 'active', 'aria-current': 'page' }),
356
+ ...(isTransitioning && { 'data-transitioning': 'transitioning' }),
579
357
  }
580
358
  }
581
359
 
360
+ type UseLinkReactProps<TComp> = TComp extends keyof React.JSX.IntrinsicElements
361
+ ? React.JSX.IntrinsicElements[TComp]
362
+ : React.PropsWithoutRef<
363
+ TComp extends React.ComponentType<infer TProps> ? TProps : never
364
+ > &
365
+ React.RefAttributes<
366
+ TComp extends
367
+ | React.FC<{ ref: infer TRef }>
368
+ | React.Component<{ ref: infer TRef }>
369
+ ? TRef
370
+ : never
371
+ >
372
+
582
373
  export type UseLinkPropsOptions<
583
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
584
- TFrom extends RoutePaths<TRouteTree> | string = string,
585
- TTo extends string = '',
586
- TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
587
- TMaskTo extends string = '',
588
- > = ActiveLinkOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> &
589
- React.AnchorHTMLAttributes<HTMLAnchorElement>
374
+ TRouter extends AnyRouter = RegisteredRouter,
375
+ TFrom extends RoutePaths<TRouter['routeTree']> | string = string,
376
+ TTo extends string | undefined = '.',
377
+ TMaskFrom extends RoutePaths<TRouter['routeTree']> | string = TFrom,
378
+ TMaskTo extends string = '.',
379
+ > = ActiveLinkOptions<'a', TRouter, TFrom, TTo, TMaskFrom, TMaskTo> &
380
+ UseLinkReactProps<'a'>
590
381
 
591
382
  export type ActiveLinkOptions<
592
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
593
- TFrom extends RoutePaths<TRouteTree> | string = string,
594
- TTo extends string = '',
595
- TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
596
- TMaskTo extends string = '',
597
- > = LinkOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> & {
598
- // A function that returns additional props for the `active` state of this link. These props override other props passed to the link (`style`'s are merged, `className`'s are concatenated)
599
- activeProps?:
600
- | React.AnchorHTMLAttributes<HTMLAnchorElement>
601
- | (() => React.AnchorHTMLAttributes<HTMLAnchorElement>)
602
- // A function that returns additional props for the `inactive` state of this link. These props override other props passed to the link (`style`'s are merged, `className`'s are concatenated)
603
- inactiveProps?:
604
- | React.AnchorHTMLAttributes<HTMLAnchorElement>
605
- | (() => React.AnchorHTMLAttributes<HTMLAnchorElement>)
383
+ TComp = 'a',
384
+ TRouter extends AnyRouter = RegisteredRouter,
385
+ TFrom extends string = string,
386
+ TTo extends string | undefined = '.',
387
+ TMaskFrom extends string = TFrom,
388
+ TMaskTo extends string = '.',
389
+ > = LinkOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo> &
390
+ ActiveLinkOptionProps<TComp>
391
+
392
+ type ActiveLinkProps<TComp> = Partial<
393
+ LinkComponentReactProps<TComp> & {
394
+ [key: `data-${string}`]: unknown
395
+ }
396
+ >
397
+
398
+ export interface ActiveLinkOptionProps<TComp = 'a'> {
399
+ /**
400
+ * A function that returns additional props for the `active` state of this link.
401
+ * These props override other props passed to the link (`style`'s are merged, `className`'s are concatenated)
402
+ */
403
+ activeProps?: ActiveLinkProps<TComp> | (() => ActiveLinkProps<TComp>)
404
+ /**
405
+ * A function that returns additional props for the `inactive` state of this link.
406
+ * These props override other props passed to the link (`style`'s are merged, `className`'s are concatenated)
407
+ */
408
+ inactiveProps?: ActiveLinkProps<TComp> | (() => ActiveLinkProps<TComp>)
606
409
  }
607
410
 
608
411
  export type LinkProps<
609
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
610
- TFrom extends RoutePaths<TRouteTree> | string = string,
611
- TTo extends string = '',
612
- TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
613
- TMaskTo extends string = '',
614
- > = ActiveLinkOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> & {
412
+ TComp = 'a',
413
+ TRouter extends AnyRouter = RegisteredRouter,
414
+ TFrom extends string = string,
415
+ TTo extends string | undefined = '.',
416
+ TMaskFrom extends string = TFrom,
417
+ TMaskTo extends string = '.',
418
+ > = ActiveLinkOptions<TComp, TRouter, TFrom, TTo, TMaskFrom, TMaskTo> &
419
+ LinkPropsChildren
420
+
421
+ export interface LinkPropsChildren {
615
422
  // If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
616
423
  children?:
617
424
  | React.ReactNode
618
- | ((state: { isActive: boolean }) => React.ReactNode)
425
+ | ((state: {
426
+ isActive: boolean
427
+ isTransitioning: boolean
428
+ }) => React.ReactNode)
619
429
  }
620
430
 
621
- type LinkComponent<TComp> = <
622
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
623
- TFrom extends RoutePaths<TRouteTree> | string = string,
624
- TTo extends string = '',
625
- TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
626
- TMaskTo extends string = '',
431
+ type LinkComponentReactProps<TComp> = Omit<
432
+ UseLinkReactProps<TComp>,
433
+ keyof CreateLinkProps
434
+ >
435
+
436
+ export type LinkComponentProps<
437
+ TComp = 'a',
438
+ TRouter extends AnyRouter = RegisteredRouter,
439
+ TFrom extends string = string,
440
+ TTo extends string | undefined = '.',
441
+ TMaskFrom extends string = TFrom,
442
+ TMaskTo extends string = '.',
443
+ > = LinkComponentReactProps<TComp> &
444
+ LinkProps<TComp, TRouter, TFrom, TTo, TMaskFrom, TMaskTo>
445
+
446
+ export type CreateLinkProps = LinkProps<
447
+ any,
448
+ any,
449
+ string,
450
+ string,
451
+ string,
452
+ string
453
+ >
454
+
455
+ export type LinkComponent<
456
+ in out TComp,
457
+ in out TDefaultFrom extends string = string,
458
+ > = <
459
+ TRouter extends AnyRouter = RegisteredRouter,
460
+ const TFrom extends string = TDefaultFrom,
461
+ const TTo extends string | undefined = undefined,
462
+ const TMaskFrom extends string = TFrom,
463
+ const TMaskTo extends string = '',
627
464
  >(
628
- props: React.PropsWithoutRef<
629
- LinkProps<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> &
630
- (TComp extends React.FC<infer TProps> | React.Component<infer TProps>
631
- ? TProps
632
- : TComp extends keyof JSX.IntrinsicElements
633
- ? Omit<React.HTMLProps<TComp>, 'children' | 'preload'>
634
- : never)
635
- > &
636
- React.RefAttributes<
637
- TComp extends
638
- | React.FC<{ ref: infer TRef }>
639
- | React.Component<{ ref: infer TRef }>
640
- ? TRef
641
- : TComp extends keyof JSX.IntrinsicElements
642
- ? React.ComponentRef<TComp>
643
- : never
644
- >,
465
+ props: LinkComponentProps<TComp, TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
645
466
  ) => React.ReactElement
646
467
 
647
- export function createLink<const TComp>(Comp: TComp): LinkComponent<TComp> {
648
- return React.forwardRef(function Link(props, ref) {
468
+ export interface LinkComponentRoute<
469
+ in out TDefaultFrom extends string = string,
470
+ > {
471
+ defaultFrom: TDefaultFrom
472
+ <
473
+ TRouter extends AnyRouter = RegisteredRouter,
474
+ const TTo extends string | undefined = undefined,
475
+ const TMaskTo extends string = '',
476
+ >(
477
+ props: LinkComponentProps<
478
+ 'a',
479
+ TRouter,
480
+ this['defaultFrom'],
481
+ TTo,
482
+ this['defaultFrom'],
483
+ TMaskTo
484
+ >,
485
+ ): React.ReactElement
486
+ }
487
+
488
+ export function createLink<const TComp>(
489
+ Comp: Constrain<TComp, any, (props: CreateLinkProps) => ReactNode>,
490
+ ): LinkComponent<TComp> {
491
+ return React.forwardRef(function CreatedLink(props, ref) {
649
492
  return <Link {...(props as any)} _asChild={Comp} ref={ref} />
650
493
  }) as any
651
494
  }
652
495
 
653
- export const Link: LinkComponent<'a'> = React.forwardRef((props: any, ref) => {
654
- const { _asChild, ...rest } = props
655
- const { type, ...linkProps } = useLinkProps(rest as any)
656
-
657
- const children =
658
- typeof rest.children === 'function'
659
- ? rest.children({
660
- isActive: (linkProps as any)['data-status'] === 'active',
661
- })
662
- : rest.children
496
+ export const Link: LinkComponent<'a'> = React.forwardRef<Element, any>(
497
+ (props, ref) => {
498
+ const { _asChild, ...rest } = props
499
+ const {
500
+ type: _type,
501
+ ref: innerRef,
502
+ ...linkProps
503
+ } = useLinkProps(rest as any, ref)
504
+
505
+ const children =
506
+ typeof rest.children === 'function'
507
+ ? rest.children({
508
+ isActive: (linkProps as any)['data-status'] === 'active',
509
+ })
510
+ : rest.children
511
+
512
+ if (typeof _asChild === 'undefined') {
513
+ // the ReturnType of useLinkProps returns the correct type for a <a> element, not a general component that has a disabled prop
514
+ // @ts-expect-error
515
+ delete linkProps.disabled
516
+ }
663
517
 
664
- return React.createElement(
665
- _asChild ? _asChild : 'a',
666
- {
667
- ...linkProps,
668
- ref,
669
- },
670
- children as any,
671
- )
672
- }) as any
518
+ return React.createElement(
519
+ _asChild ? _asChild : 'a',
520
+ {
521
+ ...linkProps,
522
+ ref: innerRef,
523
+ },
524
+ children,
525
+ )
526
+ },
527
+ ) as any
673
528
 
674
529
  function isCtrlEvent(e: MouseEvent) {
675
530
  return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
676
531
  }
532
+
533
+ export type LinkOptionsFnOptions<
534
+ TOptions,
535
+ TComp,
536
+ TRouter extends AnyRouter = RegisteredRouter,
537
+ > =
538
+ TOptions extends ReadonlyArray<any>
539
+ ? ValidateLinkOptionsArray<TRouter, TOptions, string, TComp>
540
+ : ValidateLinkOptions<TRouter, TOptions, string, TComp>
541
+
542
+ export type LinkOptionsFn<TComp> = <
543
+ const TOptions,
544
+ TRouter extends AnyRouter = RegisteredRouter,
545
+ >(
546
+ options: LinkOptionsFnOptions<TOptions, TComp, TRouter>,
547
+ ) => TOptions
548
+
549
+ export const linkOptions: LinkOptionsFn<'a'> = (options) => {
550
+ return options as any
551
+ }