@tanstack/react-router 1.157.0 → 1.157.2
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/link.cjs +237 -36
- package/dist/cjs/link.cjs.map +1 -1
- package/dist/esm/link.js +238 -37
- package/dist/esm/link.js.map +1 -1
- package/package.json +2 -2
- package/src/link.tsx +384 -63
package/src/link.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
exactPathTest,
|
|
6
6
|
functionalUpdate,
|
|
7
7
|
isDangerousProtocol,
|
|
8
|
+
isServer,
|
|
8
9
|
preloadWarning,
|
|
9
10
|
removeTrailingSlash,
|
|
10
11
|
} from '@tanstack/router-core'
|
|
@@ -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,30 +406,35 @@ 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],
|
|
127
413
|
)
|
|
128
414
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
415
|
+
// Use publicHref - it contains the correct href for display
|
|
416
|
+
// When a rewrite changes the origin, publicHref is the full URL
|
|
417
|
+
// Otherwise it's the origin-stripped path
|
|
418
|
+
// This avoids constructing URL objects in the hot path
|
|
419
|
+
const hrefOptionPublicHref = next.maskedLocation
|
|
420
|
+
? next.maskedLocation.publicHref
|
|
421
|
+
: next.publicHref
|
|
422
|
+
const hrefOptionExternal = next.maskedLocation
|
|
423
|
+
? next.maskedLocation.external
|
|
424
|
+
: next.external
|
|
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
|
+
)
|
|
147
436
|
|
|
437
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
148
438
|
const externalLink = React.useMemo(() => {
|
|
149
439
|
if (hrefOption?.external) {
|
|
150
440
|
// Block dangerous protocols for external links
|
|
@@ -158,15 +448,13 @@ export function useLinkProps<
|
|
|
158
448
|
}
|
|
159
449
|
return hrefOption.href
|
|
160
450
|
}
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
to.charCodeAt(1) !== 47 // but not '//'
|
|
165
|
-
if (isSafeInternal) return undefined
|
|
451
|
+
const safeInternal = isSafeInternal(to)
|
|
452
|
+
if (safeInternal) return undefined
|
|
453
|
+
if (typeof to !== 'string' || to.indexOf(':') === -1) return undefined
|
|
166
454
|
try {
|
|
167
455
|
new URL(to as any)
|
|
168
456
|
// Block dangerous protocols like javascript:, data:, vbscript:
|
|
169
|
-
if (isDangerousProtocol(to
|
|
457
|
+
if (isDangerousProtocol(to)) {
|
|
170
458
|
if (process.env.NODE_ENV !== 'production') {
|
|
171
459
|
console.warn(`Blocked Link with dangerous protocol: ${to}`)
|
|
172
460
|
}
|
|
@@ -177,13 +465,7 @@ export function useLinkProps<
|
|
|
177
465
|
return undefined
|
|
178
466
|
}, [to, hrefOption])
|
|
179
467
|
|
|
180
|
-
|
|
181
|
-
options.reloadDocument || externalLink
|
|
182
|
-
? false
|
|
183
|
-
: (userPreload ?? router.options.defaultPreload)
|
|
184
|
-
const preloadDelay =
|
|
185
|
-
userPreloadDelay ?? router.options.defaultPreloadDelay ?? 0
|
|
186
|
-
|
|
468
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
187
469
|
const isActive = useRouterState({
|
|
188
470
|
select: (s) => {
|
|
189
471
|
if (externalLink) return false
|
|
@@ -233,6 +515,46 @@ export function useLinkProps<
|
|
|
233
515
|
},
|
|
234
516
|
})
|
|
235
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
|
|
236
558
|
const doPreload = React.useCallback(() => {
|
|
237
559
|
router.preloadRoute({ ..._options } as any).catch((err) => {
|
|
238
560
|
console.warn(err)
|
|
@@ -240,6 +562,7 @@ export function useLinkProps<
|
|
|
240
562
|
})
|
|
241
563
|
}, [router, _options])
|
|
242
564
|
|
|
565
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
243
566
|
const preloadViewportIoCallback = React.useCallback(
|
|
244
567
|
(entry: IntersectionObserverEntry | undefined) => {
|
|
245
568
|
if (entry?.isIntersecting) {
|
|
@@ -249,6 +572,7 @@ export function useLinkProps<
|
|
|
249
572
|
[doPreload],
|
|
250
573
|
)
|
|
251
574
|
|
|
575
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
252
576
|
useIntersectionObserver(
|
|
253
577
|
innerRef,
|
|
254
578
|
preloadViewportIoCallback,
|
|
@@ -256,6 +580,7 @@ export function useLinkProps<
|
|
|
256
580
|
{ disabled: !!disabled || !(preload === 'viewport') },
|
|
257
581
|
)
|
|
258
582
|
|
|
583
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
259
584
|
React.useEffect(() => {
|
|
260
585
|
if (hasRenderFetched.current) {
|
|
261
586
|
return
|
|
@@ -324,7 +649,6 @@ export function useLinkProps<
|
|
|
324
649
|
}
|
|
325
650
|
}
|
|
326
651
|
|
|
327
|
-
// The click handler
|
|
328
652
|
const handleFocus = (_: React.MouseEvent) => {
|
|
329
653
|
if (disabled) return
|
|
330
654
|
if (preload) {
|
|
@@ -362,33 +686,6 @@ export function useLinkProps<
|
|
|
362
686
|
}
|
|
363
687
|
}
|
|
364
688
|
|
|
365
|
-
// Get the active props
|
|
366
|
-
const resolvedActiveProps: React.HTMLAttributes<HTMLAnchorElement> = isActive
|
|
367
|
-
? (functionalUpdate(activeProps as any, {}) ?? STATIC_ACTIVE_OBJECT)
|
|
368
|
-
: STATIC_EMPTY_OBJECT
|
|
369
|
-
|
|
370
|
-
// Get the inactive props
|
|
371
|
-
const resolvedInactiveProps: React.HTMLAttributes<HTMLAnchorElement> =
|
|
372
|
-
isActive
|
|
373
|
-
? STATIC_EMPTY_OBJECT
|
|
374
|
-
: (functionalUpdate(inactiveProps, {}) ?? STATIC_EMPTY_OBJECT)
|
|
375
|
-
|
|
376
|
-
const resolvedClassName = [
|
|
377
|
-
className,
|
|
378
|
-
resolvedActiveProps.className,
|
|
379
|
-
resolvedInactiveProps.className,
|
|
380
|
-
]
|
|
381
|
-
.filter(Boolean)
|
|
382
|
-
.join(' ')
|
|
383
|
-
|
|
384
|
-
const resolvedStyle = (style ||
|
|
385
|
-
resolvedActiveProps.style ||
|
|
386
|
-
resolvedInactiveProps.style) && {
|
|
387
|
-
...style,
|
|
388
|
-
...resolvedActiveProps.style,
|
|
389
|
-
...resolvedInactiveProps.style,
|
|
390
|
-
}
|
|
391
|
-
|
|
392
689
|
return {
|
|
393
690
|
...propsSafeToSpread,
|
|
394
691
|
...resolvedActiveProps,
|
|
@@ -406,7 +703,7 @@ export function useLinkProps<
|
|
|
406
703
|
...(resolvedClassName && { className: resolvedClassName }),
|
|
407
704
|
...(disabled && STATIC_DISABLED_PROPS),
|
|
408
705
|
...(isActive && STATIC_ACTIVE_PROPS),
|
|
409
|
-
...(isTransitioning && STATIC_TRANSITIONING_PROPS),
|
|
706
|
+
...(isHydrated && isTransitioning && STATIC_TRANSITIONING_PROPS),
|
|
410
707
|
}
|
|
411
708
|
}
|
|
412
709
|
|
|
@@ -432,6 +729,30 @@ const composeHandlers =
|
|
|
432
729
|
}
|
|
433
730
|
}
|
|
434
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
|
+
|
|
435
756
|
type UseLinkReactProps<TComp> = TComp extends keyof React.JSX.IntrinsicElements
|
|
436
757
|
? React.JSX.IntrinsicElements[TComp]
|
|
437
758
|
: TComp extends React.ComponentType<any>
|