@tanstack/react-router 1.157.1 → 1.157.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/Asset.cjs +2 -2
- package/dist/cjs/Asset.cjs.map +1 -1
- package/dist/cjs/Match.cjs +3 -2
- package/dist/cjs/Match.cjs.map +1 -1
- package/dist/cjs/Matches.cjs +3 -2
- package/dist/cjs/Matches.cjs.map +1 -1
- package/dist/cjs/ScriptOnce.cjs +2 -2
- package/dist/cjs/ScriptOnce.cjs.map +1 -1
- package/dist/cjs/link.cjs +236 -31
- package/dist/cjs/link.cjs.map +1 -1
- package/dist/cjs/scroll-restoration.cjs +2 -1
- package/dist/cjs/scroll-restoration.cjs.map +1 -1
- package/dist/esm/Asset.js +1 -1
- package/dist/esm/Asset.js.map +1 -1
- package/dist/esm/Match.js +2 -1
- package/dist/esm/Match.js.map +1 -1
- package/dist/esm/Matches.js +2 -1
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/ScriptOnce.js +1 -1
- package/dist/esm/ScriptOnce.js.map +1 -1
- package/dist/esm/link.js +237 -32
- package/dist/esm/link.js.map +1 -1
- package/dist/esm/scroll-restoration.js +2 -1
- package/dist/esm/scroll-restoration.js.map +1 -1
- package/dist/llms/rules/api.d.ts +1 -1
- package/dist/llms/rules/api.js +18 -0
- package/package.json +2 -2
- package/src/Asset.tsx +1 -1
- package/src/Match.tsx +1 -1
- package/src/Matches.tsx +2 -1
- package/src/ScriptOnce.tsx +1 -1
- package/src/link.tsx +374 -58
- package/src/scroll-restoration.tsx +1 -1
package/dist/llms/rules/api.js
CHANGED
|
@@ -2967,6 +2967,24 @@ function Component() {
|
|
|
2967
2967
|
}
|
|
2968
2968
|
\`\`\`
|
|
2969
2969
|
|
|
2970
|
+
By default, param values with characters such as \`@\` will be encoded in the URL:
|
|
2971
|
+
|
|
2972
|
+
\`\`\`tsx
|
|
2973
|
+
// url path will be \`/%40foo\`
|
|
2974
|
+
<Link to="/$username" params={{ username: '@foo' }} />
|
|
2975
|
+
\`\`\`
|
|
2976
|
+
|
|
2977
|
+
To opt-out, update the [pathParamsAllowedCharacters](../router/RouterOptionsType#pathparamsallowedcharacters-property) config on the router
|
|
2978
|
+
|
|
2979
|
+
\`\`\`tsx
|
|
2980
|
+
import { createRouter } from '@tanstack/react-router'
|
|
2981
|
+
|
|
2982
|
+
const router = createRouter({
|
|
2983
|
+
routeTree,
|
|
2984
|
+
pathParamsAllowedCharacters: ['@'],
|
|
2985
|
+
})
|
|
2986
|
+
\`\`\`
|
|
2987
|
+
|
|
2970
2988
|
# Link options
|
|
2971
2989
|
|
|
2972
2990
|
\`linkOptions\` is a function which type checks an object literal with the intention of being used for \`Link\`, \`navigate\` or \`redirect\`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/react-router",
|
|
3
|
-
"version": "1.157.
|
|
3
|
+
"version": "1.157.3",
|
|
4
4
|
"description": "Modern and scalable routing for React applications",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"tiny-invariant": "^1.3.3",
|
|
83
83
|
"tiny-warning": "^1.0.3",
|
|
84
84
|
"@tanstack/history": "1.154.14",
|
|
85
|
-
"@tanstack/router-core": "1.157.
|
|
85
|
+
"@tanstack/router-core": "1.157.3"
|
|
86
86
|
},
|
|
87
87
|
"devDependencies": {
|
|
88
88
|
"@testing-library/jest-dom": "^6.6.3",
|
package/src/Asset.tsx
CHANGED
package/src/Match.tsx
CHANGED
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
getLocationChangeInfo,
|
|
7
7
|
isNotFound,
|
|
8
8
|
isRedirect,
|
|
9
|
-
isServer,
|
|
10
9
|
rootRouteId,
|
|
11
10
|
} from '@tanstack/router-core'
|
|
11
|
+
import { isServer } from '@tanstack/router-core/isServer'
|
|
12
12
|
import { CatchBoundary, ErrorComponent } from './CatchBoundary'
|
|
13
13
|
import { useRouterState } from './useRouterState'
|
|
14
14
|
import { useRouter } from './useRouter'
|
package/src/Matches.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as React from 'react'
|
|
2
2
|
import warning from 'tiny-warning'
|
|
3
|
-
import {
|
|
3
|
+
import { rootRouteId } from '@tanstack/router-core'
|
|
4
|
+
import { isServer } from '@tanstack/router-core/isServer'
|
|
4
5
|
import { CatchBoundary, ErrorComponent } from './CatchBoundary'
|
|
5
6
|
import { useRouterState } from './useRouterState'
|
|
6
7
|
import { useRouter } from './useRouter'
|
package/src/ScriptOnce.tsx
CHANGED
package/src/link.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
preloadWarning,
|
|
9
9
|
removeTrailingSlash,
|
|
10
10
|
} from '@tanstack/router-core'
|
|
11
|
+
import { isServer } from '@tanstack/router-core/isServer'
|
|
11
12
|
import { useRouterState } from './useRouterState'
|
|
12
13
|
import { useRouter } from './useRouter'
|
|
13
14
|
|
|
@@ -50,10 +51,10 @@ export function useLinkProps<
|
|
|
50
51
|
forwardedRef?: React.ForwardedRef<Element>,
|
|
51
52
|
): React.ComponentPropsWithRef<'a'> {
|
|
52
53
|
const router = useRouter()
|
|
53
|
-
const [isTransitioning, setIsTransitioning] = React.useState(false)
|
|
54
|
-
const hasRenderFetched = React.useRef(false)
|
|
55
54
|
const innerRef = useForwardedRef(forwardedRef)
|
|
56
|
-
|
|
55
|
+
|
|
56
|
+
// Determine if we're on the server - used for tree-shaking client-only code
|
|
57
|
+
const _isServer = isServer ?? router.isServer
|
|
57
58
|
|
|
58
59
|
const {
|
|
59
60
|
// custom props
|
|
@@ -93,7 +94,290 @@ export function useLinkProps<
|
|
|
93
94
|
...propsSafeToSpread
|
|
94
95
|
} = options
|
|
95
96
|
|
|
97
|
+
// ==========================================================================
|
|
98
|
+
// SERVER EARLY RETURN
|
|
99
|
+
// On the server, we return static props without any event handlers,
|
|
100
|
+
// effects, or client-side interactivity.
|
|
101
|
+
//
|
|
102
|
+
// For SSR parity (to avoid hydration errors), we still compute the link's
|
|
103
|
+
// active status on the server, but we avoid creating any router-state
|
|
104
|
+
// subscriptions by reading from `router.state` directly.
|
|
105
|
+
//
|
|
106
|
+
// Note: `location.hash` is not available on the server.
|
|
107
|
+
// ==========================================================================
|
|
108
|
+
if (_isServer) {
|
|
109
|
+
const safeInternal = isSafeInternal(to)
|
|
110
|
+
|
|
111
|
+
// If `to` is obviously an absolute URL, treat as external and avoid
|
|
112
|
+
// computing the internal location via `buildLocation`.
|
|
113
|
+
if (
|
|
114
|
+
typeof to === 'string' &&
|
|
115
|
+
!safeInternal &&
|
|
116
|
+
// Quick checks to avoid `new URL` in common internal-like cases
|
|
117
|
+
to.indexOf(':') > -1
|
|
118
|
+
) {
|
|
119
|
+
try {
|
|
120
|
+
new URL(to)
|
|
121
|
+
if (isDangerousProtocol(to)) {
|
|
122
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
123
|
+
console.warn(`Blocked Link with dangerous protocol: ${to}`)
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
...propsSafeToSpread,
|
|
127
|
+
ref: innerRef as React.ComponentPropsWithRef<'a'>['ref'],
|
|
128
|
+
href: undefined,
|
|
129
|
+
...(children && { children }),
|
|
130
|
+
...(target && { target }),
|
|
131
|
+
...(disabled && { disabled }),
|
|
132
|
+
...(style && { style }),
|
|
133
|
+
...(className && { className }),
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
...propsSafeToSpread,
|
|
139
|
+
ref: innerRef as React.ComponentPropsWithRef<'a'>['ref'],
|
|
140
|
+
href: to,
|
|
141
|
+
...(children && { children }),
|
|
142
|
+
...(target && { target }),
|
|
143
|
+
...(disabled && { disabled }),
|
|
144
|
+
...(style && { style }),
|
|
145
|
+
...(className && { className }),
|
|
146
|
+
}
|
|
147
|
+
} catch {
|
|
148
|
+
// Not an absolute URL
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const next = router.buildLocation({ ...options, from: options.from } as any)
|
|
153
|
+
|
|
154
|
+
// Use publicHref - it contains the correct href for display
|
|
155
|
+
// When a rewrite changes the origin, publicHref is the full URL
|
|
156
|
+
// Otherwise it's the origin-stripped path
|
|
157
|
+
// This avoids constructing URL objects in the hot path
|
|
158
|
+
const hrefOptionPublicHref = next.maskedLocation
|
|
159
|
+
? next.maskedLocation.publicHref
|
|
160
|
+
: next.publicHref
|
|
161
|
+
const hrefOptionExternal = next.maskedLocation
|
|
162
|
+
? next.maskedLocation.external
|
|
163
|
+
: next.external
|
|
164
|
+
const hrefOption = getHrefOption(
|
|
165
|
+
hrefOptionPublicHref,
|
|
166
|
+
hrefOptionExternal,
|
|
167
|
+
router.history,
|
|
168
|
+
disabled,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
const externalLink = (() => {
|
|
172
|
+
if (hrefOption?.external) {
|
|
173
|
+
if (isDangerousProtocol(hrefOption.href)) {
|
|
174
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
175
|
+
console.warn(
|
|
176
|
+
`Blocked Link with dangerous protocol: ${hrefOption.href}`,
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
return undefined
|
|
180
|
+
}
|
|
181
|
+
return hrefOption.href
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (safeInternal) return undefined
|
|
185
|
+
|
|
186
|
+
// Only attempt URL parsing when it looks like an absolute URL.
|
|
187
|
+
if (typeof to === 'string' && to.indexOf(':') > -1) {
|
|
188
|
+
try {
|
|
189
|
+
new URL(to)
|
|
190
|
+
if (isDangerousProtocol(to)) {
|
|
191
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
192
|
+
console.warn(`Blocked Link with dangerous protocol: ${to}`)
|
|
193
|
+
}
|
|
194
|
+
return undefined
|
|
195
|
+
}
|
|
196
|
+
return to
|
|
197
|
+
} catch {}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return undefined
|
|
201
|
+
})()
|
|
202
|
+
|
|
203
|
+
const isActive = (() => {
|
|
204
|
+
if (externalLink) return false
|
|
205
|
+
|
|
206
|
+
const currentLocation = router.state.location
|
|
207
|
+
|
|
208
|
+
const exact = activeOptions?.exact ?? false
|
|
209
|
+
|
|
210
|
+
if (exact) {
|
|
211
|
+
const testExact = exactPathTest(
|
|
212
|
+
currentLocation.pathname,
|
|
213
|
+
next.pathname,
|
|
214
|
+
router.basepath,
|
|
215
|
+
)
|
|
216
|
+
if (!testExact) {
|
|
217
|
+
return false
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
const currentPathSplit = removeTrailingSlash(
|
|
221
|
+
currentLocation.pathname,
|
|
222
|
+
router.basepath,
|
|
223
|
+
)
|
|
224
|
+
const nextPathSplit = removeTrailingSlash(
|
|
225
|
+
next.pathname,
|
|
226
|
+
router.basepath,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
const pathIsFuzzyEqual =
|
|
230
|
+
currentPathSplit.startsWith(nextPathSplit) &&
|
|
231
|
+
(currentPathSplit.length === nextPathSplit.length ||
|
|
232
|
+
currentPathSplit[nextPathSplit.length] === '/')
|
|
233
|
+
|
|
234
|
+
if (!pathIsFuzzyEqual) {
|
|
235
|
+
return false
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const includeSearch = activeOptions?.includeSearch ?? true
|
|
240
|
+
if (includeSearch) {
|
|
241
|
+
if (currentLocation.search !== next.search) {
|
|
242
|
+
const currentSearchEmpty =
|
|
243
|
+
!currentLocation.search ||
|
|
244
|
+
(typeof currentLocation.search === 'object' &&
|
|
245
|
+
Object.keys(currentLocation.search).length === 0)
|
|
246
|
+
const nextSearchEmpty =
|
|
247
|
+
!next.search ||
|
|
248
|
+
(typeof next.search === 'object' &&
|
|
249
|
+
Object.keys(next.search).length === 0)
|
|
250
|
+
|
|
251
|
+
if (!(currentSearchEmpty && nextSearchEmpty)) {
|
|
252
|
+
const searchTest = deepEqual(currentLocation.search, next.search, {
|
|
253
|
+
partial: !exact,
|
|
254
|
+
ignoreUndefined: !activeOptions?.explicitUndefined,
|
|
255
|
+
})
|
|
256
|
+
if (!searchTest) {
|
|
257
|
+
return false
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Hash is not available on the server
|
|
264
|
+
if (activeOptions?.includeHash) {
|
|
265
|
+
return false
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return true
|
|
269
|
+
})()
|
|
270
|
+
|
|
271
|
+
if (externalLink) {
|
|
272
|
+
return {
|
|
273
|
+
...propsSafeToSpread,
|
|
274
|
+
ref: innerRef as React.ComponentPropsWithRef<'a'>['ref'],
|
|
275
|
+
href: externalLink,
|
|
276
|
+
...(children && { children }),
|
|
277
|
+
...(target && { target }),
|
|
278
|
+
...(disabled && { disabled }),
|
|
279
|
+
...(style && { style }),
|
|
280
|
+
...(className && { className }),
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const resolvedActiveProps: React.HTMLAttributes<HTMLAnchorElement> =
|
|
285
|
+
isActive
|
|
286
|
+
? (functionalUpdate(activeProps as any, {}) ?? STATIC_ACTIVE_OBJECT)
|
|
287
|
+
: STATIC_EMPTY_OBJECT
|
|
288
|
+
|
|
289
|
+
const resolvedInactiveProps: React.HTMLAttributes<HTMLAnchorElement> =
|
|
290
|
+
isActive
|
|
291
|
+
? STATIC_EMPTY_OBJECT
|
|
292
|
+
: (functionalUpdate(inactiveProps, {}) ?? STATIC_EMPTY_OBJECT)
|
|
293
|
+
|
|
294
|
+
const resolvedStyle = (() => {
|
|
295
|
+
const baseStyle = style
|
|
296
|
+
const activeStyle = resolvedActiveProps.style
|
|
297
|
+
const inactiveStyle = resolvedInactiveProps.style
|
|
298
|
+
|
|
299
|
+
if (!baseStyle && !activeStyle && !inactiveStyle) {
|
|
300
|
+
return undefined
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (baseStyle && !activeStyle && !inactiveStyle) {
|
|
304
|
+
return baseStyle
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (!baseStyle && activeStyle && !inactiveStyle) {
|
|
308
|
+
return activeStyle
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (!baseStyle && !activeStyle && inactiveStyle) {
|
|
312
|
+
return inactiveStyle
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
...baseStyle,
|
|
317
|
+
...activeStyle,
|
|
318
|
+
...inactiveStyle,
|
|
319
|
+
}
|
|
320
|
+
})()
|
|
321
|
+
|
|
322
|
+
const resolvedClassName = (() => {
|
|
323
|
+
const baseClassName = className
|
|
324
|
+
const activeClassName = resolvedActiveProps.className
|
|
325
|
+
const inactiveClassName = resolvedInactiveProps.className
|
|
326
|
+
|
|
327
|
+
if (!baseClassName && !activeClassName && !inactiveClassName) {
|
|
328
|
+
return ''
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
let out = ''
|
|
332
|
+
|
|
333
|
+
if (baseClassName) {
|
|
334
|
+
out = baseClassName
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (activeClassName) {
|
|
338
|
+
out = out ? `${out} ${activeClassName}` : activeClassName
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (inactiveClassName) {
|
|
342
|
+
out = out ? `${out} ${inactiveClassName}` : inactiveClassName
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return out
|
|
346
|
+
})()
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
...propsSafeToSpread,
|
|
350
|
+
...resolvedActiveProps,
|
|
351
|
+
...resolvedInactiveProps,
|
|
352
|
+
href: hrefOption?.href,
|
|
353
|
+
ref: innerRef as React.ComponentPropsWithRef<'a'>['ref'],
|
|
354
|
+
disabled: !!disabled,
|
|
355
|
+
target,
|
|
356
|
+
...(resolvedStyle && { style: resolvedStyle }),
|
|
357
|
+
...(resolvedClassName && { className: resolvedClassName }),
|
|
358
|
+
...(disabled && STATIC_DISABLED_PROPS),
|
|
359
|
+
...(isActive && STATIC_ACTIVE_PROPS),
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ==========================================================================
|
|
364
|
+
// CLIENT-ONLY CODE
|
|
365
|
+
// Everything below this point only runs on the client. The `isServer` check
|
|
366
|
+
// above is a compile-time constant that bundlers use for dead code elimination,
|
|
367
|
+
// so this entire section is removed from server bundles.
|
|
368
|
+
//
|
|
369
|
+
// We disable the rules-of-hooks lint rule because these hooks appear after
|
|
370
|
+
// an early return. This is safe because:
|
|
371
|
+
// 1. `isServer` is a compile-time constant from conditional exports
|
|
372
|
+
// 2. In server bundles, this code is completely eliminated by the bundler
|
|
373
|
+
// 3. In client bundles, `isServer` is `false`, so the early return never executes
|
|
374
|
+
// ==========================================================================
|
|
375
|
+
|
|
376
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
377
|
+
const isHydrated = useHydrated()
|
|
378
|
+
|
|
96
379
|
// subscribe to search params to re-build location if it changes
|
|
380
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
97
381
|
const currentSearch = useRouterState({
|
|
98
382
|
select: (s) => s.location.search,
|
|
99
383
|
structuralSharing: true as any,
|
|
@@ -101,6 +385,7 @@ export function useLinkProps<
|
|
|
101
385
|
|
|
102
386
|
const from = options.from
|
|
103
387
|
|
|
388
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
104
389
|
const _options = React.useMemo(
|
|
105
390
|
() => {
|
|
106
391
|
return { ...options, from }
|
|
@@ -121,6 +406,7 @@ export function useLinkProps<
|
|
|
121
406
|
],
|
|
122
407
|
)
|
|
123
408
|
|
|
409
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
124
410
|
const next = React.useMemo(
|
|
125
411
|
() => router.buildLocation({ ..._options } as any),
|
|
126
412
|
[router, _options],
|
|
@@ -136,20 +422,19 @@ export function useLinkProps<
|
|
|
136
422
|
const hrefOptionExternal = next.maskedLocation
|
|
137
423
|
? next.maskedLocation.external
|
|
138
424
|
: next.external
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
}, [disabled, hrefOptionExternal, hrefOptionPublicHref, router.history])
|
|
425
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
426
|
+
const hrefOption = React.useMemo(
|
|
427
|
+
() =>
|
|
428
|
+
getHrefOption(
|
|
429
|
+
hrefOptionPublicHref,
|
|
430
|
+
hrefOptionExternal,
|
|
431
|
+
router.history,
|
|
432
|
+
disabled,
|
|
433
|
+
),
|
|
434
|
+
[disabled, hrefOptionExternal, hrefOptionPublicHref, router.history],
|
|
435
|
+
)
|
|
152
436
|
|
|
437
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
153
438
|
const externalLink = React.useMemo(() => {
|
|
154
439
|
if (hrefOption?.external) {
|
|
155
440
|
// Block dangerous protocols for external links
|
|
@@ -163,15 +448,13 @@ export function useLinkProps<
|
|
|
163
448
|
}
|
|
164
449
|
return hrefOption.href
|
|
165
450
|
}
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
to.charCodeAt(1) !== 47 // but not '//'
|
|
170
|
-
if (isSafeInternal) return undefined
|
|
451
|
+
const safeInternal = isSafeInternal(to)
|
|
452
|
+
if (safeInternal) return undefined
|
|
453
|
+
if (typeof to !== 'string' || to.indexOf(':') === -1) return undefined
|
|
171
454
|
try {
|
|
172
455
|
new URL(to as any)
|
|
173
456
|
// Block dangerous protocols like javascript:, data:, vbscript:
|
|
174
|
-
if (isDangerousProtocol(to
|
|
457
|
+
if (isDangerousProtocol(to)) {
|
|
175
458
|
if (process.env.NODE_ENV !== 'production') {
|
|
176
459
|
console.warn(`Blocked Link with dangerous protocol: ${to}`)
|
|
177
460
|
}
|
|
@@ -182,13 +465,7 @@ export function useLinkProps<
|
|
|
182
465
|
return undefined
|
|
183
466
|
}, [to, hrefOption])
|
|
184
467
|
|
|
185
|
-
|
|
186
|
-
options.reloadDocument || externalLink
|
|
187
|
-
? false
|
|
188
|
-
: (userPreload ?? router.options.defaultPreload)
|
|
189
|
-
const preloadDelay =
|
|
190
|
-
userPreloadDelay ?? router.options.defaultPreloadDelay ?? 0
|
|
191
|
-
|
|
468
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
192
469
|
const isActive = useRouterState({
|
|
193
470
|
select: (s) => {
|
|
194
471
|
if (externalLink) return false
|
|
@@ -238,6 +515,46 @@ export function useLinkProps<
|
|
|
238
515
|
},
|
|
239
516
|
})
|
|
240
517
|
|
|
518
|
+
// Get the active props
|
|
519
|
+
const resolvedActiveProps: React.HTMLAttributes<HTMLAnchorElement> = isActive
|
|
520
|
+
? (functionalUpdate(activeProps as any, {}) ?? STATIC_ACTIVE_OBJECT)
|
|
521
|
+
: STATIC_EMPTY_OBJECT
|
|
522
|
+
|
|
523
|
+
// Get the inactive props
|
|
524
|
+
const resolvedInactiveProps: React.HTMLAttributes<HTMLAnchorElement> =
|
|
525
|
+
isActive
|
|
526
|
+
? STATIC_EMPTY_OBJECT
|
|
527
|
+
: (functionalUpdate(inactiveProps, {}) ?? STATIC_EMPTY_OBJECT)
|
|
528
|
+
|
|
529
|
+
const resolvedClassName = [
|
|
530
|
+
className,
|
|
531
|
+
resolvedActiveProps.className,
|
|
532
|
+
resolvedInactiveProps.className,
|
|
533
|
+
]
|
|
534
|
+
.filter(Boolean)
|
|
535
|
+
.join(' ')
|
|
536
|
+
|
|
537
|
+
const resolvedStyle = (style ||
|
|
538
|
+
resolvedActiveProps.style ||
|
|
539
|
+
resolvedInactiveProps.style) && {
|
|
540
|
+
...style,
|
|
541
|
+
...resolvedActiveProps.style,
|
|
542
|
+
...resolvedInactiveProps.style,
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
546
|
+
const [isTransitioning, setIsTransitioning] = React.useState(false)
|
|
547
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
548
|
+
const hasRenderFetched = React.useRef(false)
|
|
549
|
+
|
|
550
|
+
const preload =
|
|
551
|
+
options.reloadDocument || externalLink
|
|
552
|
+
? false
|
|
553
|
+
: (userPreload ?? router.options.defaultPreload)
|
|
554
|
+
const preloadDelay =
|
|
555
|
+
userPreloadDelay ?? router.options.defaultPreloadDelay ?? 0
|
|
556
|
+
|
|
557
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
241
558
|
const doPreload = React.useCallback(() => {
|
|
242
559
|
router.preloadRoute({ ..._options } as any).catch((err) => {
|
|
243
560
|
console.warn(err)
|
|
@@ -245,6 +562,7 @@ export function useLinkProps<
|
|
|
245
562
|
})
|
|
246
563
|
}, [router, _options])
|
|
247
564
|
|
|
565
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
248
566
|
const preloadViewportIoCallback = React.useCallback(
|
|
249
567
|
(entry: IntersectionObserverEntry | undefined) => {
|
|
250
568
|
if (entry?.isIntersecting) {
|
|
@@ -254,6 +572,7 @@ export function useLinkProps<
|
|
|
254
572
|
[doPreload],
|
|
255
573
|
)
|
|
256
574
|
|
|
575
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
257
576
|
useIntersectionObserver(
|
|
258
577
|
innerRef,
|
|
259
578
|
preloadViewportIoCallback,
|
|
@@ -261,6 +580,7 @@ export function useLinkProps<
|
|
|
261
580
|
{ disabled: !!disabled || !(preload === 'viewport') },
|
|
262
581
|
)
|
|
263
582
|
|
|
583
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
264
584
|
React.useEffect(() => {
|
|
265
585
|
if (hasRenderFetched.current) {
|
|
266
586
|
return
|
|
@@ -329,7 +649,6 @@ export function useLinkProps<
|
|
|
329
649
|
}
|
|
330
650
|
}
|
|
331
651
|
|
|
332
|
-
// The click handler
|
|
333
652
|
const handleFocus = (_: React.MouseEvent) => {
|
|
334
653
|
if (disabled) return
|
|
335
654
|
if (preload) {
|
|
@@ -367,33 +686,6 @@ export function useLinkProps<
|
|
|
367
686
|
}
|
|
368
687
|
}
|
|
369
688
|
|
|
370
|
-
// Get the active props
|
|
371
|
-
const resolvedActiveProps: React.HTMLAttributes<HTMLAnchorElement> = isActive
|
|
372
|
-
? (functionalUpdate(activeProps as any, {}) ?? STATIC_ACTIVE_OBJECT)
|
|
373
|
-
: STATIC_EMPTY_OBJECT
|
|
374
|
-
|
|
375
|
-
// Get the inactive props
|
|
376
|
-
const resolvedInactiveProps: React.HTMLAttributes<HTMLAnchorElement> =
|
|
377
|
-
isActive
|
|
378
|
-
? STATIC_EMPTY_OBJECT
|
|
379
|
-
: (functionalUpdate(inactiveProps, {}) ?? STATIC_EMPTY_OBJECT)
|
|
380
|
-
|
|
381
|
-
const resolvedClassName = [
|
|
382
|
-
className,
|
|
383
|
-
resolvedActiveProps.className,
|
|
384
|
-
resolvedInactiveProps.className,
|
|
385
|
-
]
|
|
386
|
-
.filter(Boolean)
|
|
387
|
-
.join(' ')
|
|
388
|
-
|
|
389
|
-
const resolvedStyle = (style ||
|
|
390
|
-
resolvedActiveProps.style ||
|
|
391
|
-
resolvedInactiveProps.style) && {
|
|
392
|
-
...style,
|
|
393
|
-
...resolvedActiveProps.style,
|
|
394
|
-
...resolvedInactiveProps.style,
|
|
395
|
-
}
|
|
396
|
-
|
|
397
689
|
return {
|
|
398
690
|
...propsSafeToSpread,
|
|
399
691
|
...resolvedActiveProps,
|
|
@@ -411,7 +703,7 @@ export function useLinkProps<
|
|
|
411
703
|
...(resolvedClassName && { className: resolvedClassName }),
|
|
412
704
|
...(disabled && STATIC_DISABLED_PROPS),
|
|
413
705
|
...(isActive && STATIC_ACTIVE_PROPS),
|
|
414
|
-
...(isTransitioning && STATIC_TRANSITIONING_PROPS),
|
|
706
|
+
...(isHydrated && isTransitioning && STATIC_TRANSITIONING_PROPS),
|
|
415
707
|
}
|
|
416
708
|
}
|
|
417
709
|
|
|
@@ -437,6 +729,30 @@ const composeHandlers =
|
|
|
437
729
|
}
|
|
438
730
|
}
|
|
439
731
|
|
|
732
|
+
function getHrefOption(
|
|
733
|
+
publicHref: string,
|
|
734
|
+
external: boolean,
|
|
735
|
+
history: AnyRouter['history'],
|
|
736
|
+
disabled: boolean | undefined,
|
|
737
|
+
) {
|
|
738
|
+
if (disabled) return undefined
|
|
739
|
+
// Full URL means rewrite changed the origin - treat as external-like
|
|
740
|
+
if (external) {
|
|
741
|
+
return { href: publicHref, external: true }
|
|
742
|
+
}
|
|
743
|
+
return {
|
|
744
|
+
href: history.createHref(publicHref) || '/',
|
|
745
|
+
external: false,
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function isSafeInternal(to: unknown) {
|
|
750
|
+
if (typeof to !== 'string') return false
|
|
751
|
+
const zero = to.charCodeAt(0)
|
|
752
|
+
if (zero === 47) return to.charCodeAt(1) !== 47 // '/' but not '//'
|
|
753
|
+
return zero === 46 // '.', '..', './', '../'
|
|
754
|
+
}
|
|
755
|
+
|
|
440
756
|
type UseLinkReactProps<TComp> = TComp extends keyof React.JSX.IntrinsicElements
|
|
441
757
|
? React.JSX.IntrinsicElements[TComp]
|
|
442
758
|
: TComp extends React.ComponentType<any>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defaultGetScrollRestorationKey,
|
|
3
3
|
escapeHtml,
|
|
4
|
-
isServer,
|
|
5
4
|
restoreScroll,
|
|
6
5
|
storageKey,
|
|
7
6
|
} from '@tanstack/router-core'
|
|
7
|
+
import { isServer } from '@tanstack/router-core/isServer'
|
|
8
8
|
import { useRouter } from './useRouter'
|
|
9
9
|
import { ScriptOnce } from './ScriptOnce'
|
|
10
10
|
|