@tanstack/react-router 1.89.2 → 1.91.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.d.cts +3 -1
- package/dist/cjs/link.cjs.map +1 -1
- package/dist/cjs/link.d.cts +19 -23
- package/dist/cjs/route.cjs.map +1 -1
- package/dist/cjs/route.d.cts +3 -3
- package/dist/cjs/routeInfo.d.cts +3 -3
- package/dist/cjs/router.cjs +28 -24
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +3 -2
- package/dist/cjs/typePrimitives.d.cts +62 -0
- package/dist/cjs/useBlocker.cjs +111 -23
- package/dist/cjs/useBlocker.cjs.map +1 -1
- package/dist/cjs/useBlocker.d.cts +58 -13
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +3 -2
- package/dist/esm/index.d.ts +3 -1
- package/dist/esm/link.d.ts +19 -23
- package/dist/esm/link.js.map +1 -1
- package/dist/esm/route.d.ts +3 -3
- package/dist/esm/route.js.map +1 -1
- package/dist/esm/routeInfo.d.ts +3 -3
- package/dist/esm/router.d.ts +3 -2
- package/dist/esm/router.js +28 -24
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/typePrimitives.d.ts +62 -0
- package/dist/esm/useBlocker.d.ts +58 -13
- package/dist/esm/useBlocker.js +111 -23
- package/dist/esm/useBlocker.js.map +1 -1
- package/dist/esm/utils.d.ts +3 -2
- package/dist/esm/utils.js.map +1 -1
- package/package.json +2 -2
- package/src/index.tsx +3 -6
- package/src/link.tsx +107 -144
- package/src/route.ts +14 -7
- package/src/routeInfo.ts +6 -13
- package/src/router.ts +34 -32
- package/src/typePrimitives.ts +168 -0
- package/src/useBlocker.tsx +245 -41
- package/src/utils.ts +9 -5
package/src/useBlocker.tsx
CHANGED
|
@@ -1,92 +1,296 @@
|
|
|
1
1
|
import * as React from 'react'
|
|
2
2
|
import { useRouter } from './useRouter'
|
|
3
|
-
import type {
|
|
4
|
-
|
|
3
|
+
import type {
|
|
4
|
+
BlockerFnArgs,
|
|
5
|
+
HistoryAction,
|
|
6
|
+
HistoryLocation,
|
|
7
|
+
} from '@tanstack/history'
|
|
8
|
+
import type { AnyRoute } from './route'
|
|
9
|
+
import type { ParseRoute } from './routeInfo'
|
|
10
|
+
import type { AnyRouter, RegisteredRouter } from './router'
|
|
5
11
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
interface ShouldBlockFnLocation<
|
|
13
|
+
out TRouteId,
|
|
14
|
+
out TFullPath,
|
|
15
|
+
out TAllParams,
|
|
16
|
+
out TFullSearchSchema,
|
|
17
|
+
> {
|
|
18
|
+
routeId: TRouteId
|
|
19
|
+
fullPath: TFullPath
|
|
20
|
+
pathname: string
|
|
21
|
+
params: TAllParams
|
|
22
|
+
search: TFullSearchSchema
|
|
10
23
|
}
|
|
11
24
|
|
|
12
|
-
type
|
|
13
|
-
|
|
25
|
+
type AnyShouldBlockFnLocation = ShouldBlockFnLocation<any, any, any, any>
|
|
26
|
+
type MakeShouldBlockFnLocationUnion<
|
|
27
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
28
|
+
TRoute extends AnyRoute = ParseRoute<TRouter['routeTree']>,
|
|
29
|
+
> = TRoute extends any
|
|
30
|
+
? ShouldBlockFnLocation<
|
|
31
|
+
TRoute['id'],
|
|
32
|
+
TRoute['fullPath'],
|
|
33
|
+
TRoute['types']['allParams'],
|
|
34
|
+
TRoute['types']['fullSearchSchema']
|
|
35
|
+
>
|
|
36
|
+
: never
|
|
37
|
+
|
|
38
|
+
type BlockerResolver<TRouter extends AnyRouter = RegisteredRouter> =
|
|
39
|
+
| {
|
|
40
|
+
status: 'blocked'
|
|
41
|
+
current: MakeShouldBlockFnLocationUnion<TRouter>
|
|
42
|
+
next: MakeShouldBlockFnLocationUnion<TRouter>
|
|
43
|
+
action: HistoryAction
|
|
44
|
+
proceed: () => void
|
|
45
|
+
reset: () => void
|
|
46
|
+
}
|
|
47
|
+
| {
|
|
48
|
+
status: 'idle'
|
|
49
|
+
current: undefined
|
|
50
|
+
next: undefined
|
|
51
|
+
action: undefined
|
|
52
|
+
proceed: undefined
|
|
53
|
+
reset: undefined
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type ShouldBlockFnArgs<TRouter extends AnyRouter = RegisteredRouter> = {
|
|
57
|
+
current: MakeShouldBlockFnLocationUnion<TRouter>
|
|
58
|
+
next: MakeShouldBlockFnLocationUnion<TRouter>
|
|
59
|
+
action: HistoryAction
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type ShouldBlockFn<TRouter extends AnyRouter = RegisteredRouter> = (
|
|
63
|
+
args: ShouldBlockFnArgs<TRouter>,
|
|
64
|
+
) => boolean | Promise<boolean>
|
|
65
|
+
export type UseBlockerOpts<
|
|
66
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
67
|
+
TWithResolver extends boolean = boolean,
|
|
68
|
+
> = {
|
|
69
|
+
shouldBlockFn: ShouldBlockFn<TRouter>
|
|
70
|
+
enableBeforeUnload?: boolean | (() => boolean)
|
|
71
|
+
disabled?: boolean
|
|
72
|
+
withResolver?: TWithResolver
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
type LegacyBlockerFn = () => Promise<any> | any
|
|
76
|
+
type LegacyBlockerOpts = {
|
|
77
|
+
blockerFn?: LegacyBlockerFn
|
|
14
78
|
condition?: boolean | any
|
|
15
79
|
}
|
|
16
80
|
|
|
17
|
-
|
|
81
|
+
function _resolveBlockerOpts(
|
|
82
|
+
opts?: UseBlockerOpts | LegacyBlockerOpts | LegacyBlockerFn,
|
|
83
|
+
condition?: boolean | any,
|
|
84
|
+
): UseBlockerOpts {
|
|
85
|
+
if (opts === undefined) {
|
|
86
|
+
return {
|
|
87
|
+
shouldBlockFn: () => true,
|
|
88
|
+
withResolver: false,
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if ('shouldBlockFn' in opts) {
|
|
93
|
+
return opts
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (typeof opts === 'function') {
|
|
97
|
+
const shouldBlock = Boolean(condition ?? true)
|
|
98
|
+
|
|
99
|
+
const _customBlockerFn = async () => {
|
|
100
|
+
if (shouldBlock) return await opts()
|
|
101
|
+
return false
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
shouldBlockFn: _customBlockerFn,
|
|
106
|
+
enableBeforeUnload: shouldBlock,
|
|
107
|
+
withResolver: false,
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const shouldBlock = Boolean(opts.condition ?? true)
|
|
112
|
+
const fn = opts.blockerFn
|
|
113
|
+
|
|
114
|
+
const _customBlockerFn = async () => {
|
|
115
|
+
if (shouldBlock && fn !== undefined) {
|
|
116
|
+
return await fn()
|
|
117
|
+
}
|
|
118
|
+
return shouldBlock
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
shouldBlockFn: _customBlockerFn,
|
|
123
|
+
enableBeforeUnload: shouldBlock,
|
|
124
|
+
withResolver: fn === undefined,
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function useBlocker<
|
|
129
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
130
|
+
TWithResolver extends boolean = false,
|
|
131
|
+
>(
|
|
132
|
+
opts: UseBlockerOpts<TRouter, TWithResolver>,
|
|
133
|
+
): TWithResolver extends true ? BlockerResolver<TRouter> : void
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @deprecated Use the shouldBlockFn property instead
|
|
137
|
+
*/
|
|
138
|
+
export function useBlocker(blockerFnOrOpts?: LegacyBlockerOpts): BlockerResolver
|
|
18
139
|
|
|
19
140
|
/**
|
|
20
|
-
* @deprecated Use the
|
|
141
|
+
* @deprecated Use the UseBlockerOpts object syntax instead
|
|
21
142
|
*/
|
|
22
143
|
export function useBlocker(
|
|
23
|
-
blockerFn?:
|
|
144
|
+
blockerFn?: LegacyBlockerFn,
|
|
24
145
|
condition?: boolean | any,
|
|
25
146
|
): BlockerResolver
|
|
26
147
|
|
|
27
148
|
export function useBlocker(
|
|
28
|
-
|
|
149
|
+
opts?: UseBlockerOpts | LegacyBlockerOpts | LegacyBlockerFn,
|
|
29
150
|
condition?: boolean | any,
|
|
30
|
-
): BlockerResolver {
|
|
31
|
-
const {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const { history } =
|
|
151
|
+
): BlockerResolver | void {
|
|
152
|
+
const {
|
|
153
|
+
shouldBlockFn,
|
|
154
|
+
enableBeforeUnload = true,
|
|
155
|
+
disabled = false,
|
|
156
|
+
withResolver = false,
|
|
157
|
+
} = _resolveBlockerOpts(opts, condition)
|
|
158
|
+
|
|
159
|
+
const router = useRouter()
|
|
160
|
+
const { history } = router
|
|
40
161
|
|
|
41
162
|
const [resolver, setResolver] = React.useState<BlockerResolver>({
|
|
42
163
|
status: 'idle',
|
|
43
|
-
|
|
44
|
-
|
|
164
|
+
current: undefined,
|
|
165
|
+
next: undefined,
|
|
166
|
+
action: undefined,
|
|
167
|
+
proceed: undefined,
|
|
168
|
+
reset: undefined,
|
|
45
169
|
})
|
|
46
170
|
|
|
47
171
|
React.useEffect(() => {
|
|
48
|
-
const blockerFnComposed = async () => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
172
|
+
const blockerFnComposed = async (blockerFnArgs: BlockerFnArgs) => {
|
|
173
|
+
function getLocation(
|
|
174
|
+
location: HistoryLocation,
|
|
175
|
+
): AnyShouldBlockFnLocation {
|
|
176
|
+
const parsedLocation = router.parseLocation(undefined, location)
|
|
177
|
+
const matchedRoutes = router.getMatchedRoutes(parsedLocation)
|
|
178
|
+
if (matchedRoutes.foundRoute === undefined) {
|
|
179
|
+
throw new Error(`No route found for location ${location.href}`)
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
routeId: matchedRoutes.foundRoute.id,
|
|
183
|
+
fullPath: matchedRoutes.foundRoute.fullPath,
|
|
184
|
+
pathname: parsedLocation.pathname,
|
|
185
|
+
params: matchedRoutes.routeParams,
|
|
186
|
+
search: parsedLocation.search,
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const current = getLocation(blockerFnArgs.currentLocation)
|
|
191
|
+
const next = getLocation(blockerFnArgs.nextLocation)
|
|
192
|
+
|
|
193
|
+
const shouldBlock = await shouldBlockFn({
|
|
194
|
+
action: blockerFnArgs.action,
|
|
195
|
+
current,
|
|
196
|
+
next,
|
|
197
|
+
})
|
|
198
|
+
if (!withResolver) {
|
|
199
|
+
return shouldBlock
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!shouldBlock) {
|
|
203
|
+
return false
|
|
52
204
|
}
|
|
53
205
|
|
|
54
206
|
const promise = new Promise<boolean>((resolve) => {
|
|
55
207
|
setResolver({
|
|
56
208
|
status: 'blocked',
|
|
57
|
-
|
|
58
|
-
|
|
209
|
+
current,
|
|
210
|
+
next,
|
|
211
|
+
action: blockerFnArgs.action,
|
|
212
|
+
proceed: () => resolve(false),
|
|
213
|
+
reset: () => resolve(true),
|
|
59
214
|
})
|
|
60
215
|
})
|
|
61
216
|
|
|
62
217
|
const canNavigateAsync = await promise
|
|
63
|
-
|
|
64
218
|
setResolver({
|
|
65
219
|
status: 'idle',
|
|
66
|
-
|
|
67
|
-
|
|
220
|
+
current: undefined,
|
|
221
|
+
next: undefined,
|
|
222
|
+
action: undefined,
|
|
223
|
+
proceed: undefined,
|
|
224
|
+
reset: undefined,
|
|
68
225
|
})
|
|
69
226
|
|
|
70
227
|
return canNavigateAsync
|
|
71
228
|
}
|
|
72
229
|
|
|
73
|
-
return
|
|
74
|
-
|
|
230
|
+
return disabled
|
|
231
|
+
? undefined
|
|
232
|
+
: history.block({ blockerFn: blockerFnComposed, enableBeforeUnload })
|
|
233
|
+
}, [shouldBlockFn, enableBeforeUnload, disabled, withResolver, history])
|
|
75
234
|
|
|
76
235
|
return resolver
|
|
77
236
|
}
|
|
78
237
|
|
|
79
|
-
|
|
80
|
-
|
|
238
|
+
const _resolvePromptBlockerArgs = (
|
|
239
|
+
props: PromptProps | LegacyPromptProps,
|
|
240
|
+
): UseBlockerOpts => {
|
|
241
|
+
if ('shouldBlockFn' in props) {
|
|
242
|
+
return { ...props }
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const shouldBlock = Boolean(props.condition ?? true)
|
|
246
|
+
const fn = props.blockerFn
|
|
247
|
+
|
|
248
|
+
const _customBlockerFn = async () => {
|
|
249
|
+
if (shouldBlock && fn !== undefined) {
|
|
250
|
+
return await fn()
|
|
251
|
+
}
|
|
252
|
+
return shouldBlock
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
shouldBlockFn: _customBlockerFn,
|
|
257
|
+
enableBeforeUnload: shouldBlock,
|
|
258
|
+
withResolver: fn === undefined,
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function Block<
|
|
263
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
264
|
+
TWithResolver extends boolean = boolean,
|
|
265
|
+
>(opts: PromptProps<TRouter, TWithResolver>): React.ReactNode
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* @deprecated Use the UseBlockerOpts property instead
|
|
269
|
+
*/
|
|
270
|
+
export function Block(opts: LegacyPromptProps): React.ReactNode
|
|
271
|
+
|
|
272
|
+
export function Block(opts: PromptProps | LegacyPromptProps): React.ReactNode {
|
|
273
|
+
const { children, ...rest } = opts
|
|
274
|
+
const args = _resolvePromptBlockerArgs(rest)
|
|
275
|
+
|
|
276
|
+
const resolver = useBlocker(args)
|
|
81
277
|
return children
|
|
82
278
|
? typeof children === 'function'
|
|
83
|
-
? children(resolver)
|
|
279
|
+
? children(resolver as any)
|
|
84
280
|
: children
|
|
85
281
|
: null
|
|
86
282
|
}
|
|
87
283
|
|
|
88
|
-
|
|
89
|
-
blockerFn?:
|
|
284
|
+
type LegacyPromptProps = {
|
|
285
|
+
blockerFn?: LegacyBlockerFn
|
|
90
286
|
condition?: boolean | any
|
|
91
|
-
children?: ReactNode | ((
|
|
287
|
+
children?: React.ReactNode | ((params: BlockerResolver) => React.ReactNode)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
type PromptProps<
|
|
291
|
+
TRouter extends AnyRouter = RegisteredRouter,
|
|
292
|
+
TWithResolver extends boolean = boolean,
|
|
293
|
+
TParams = TWithResolver extends true ? BlockerResolver<TRouter> : void,
|
|
294
|
+
> = UseBlockerOpts<TRouter, TWithResolver> & {
|
|
295
|
+
children?: React.ReactNode | ((params: TParams) => React.ReactNode)
|
|
92
296
|
}
|
package/src/utils.ts
CHANGED
|
@@ -111,6 +111,14 @@ export type PartialMergeAll<TUnion> =
|
|
|
111
111
|
| ExtractPrimitives<TUnion>
|
|
112
112
|
| PartialMergeAllObject<TUnion>
|
|
113
113
|
|
|
114
|
+
export type Constrain<T, TConstraint, TDefault = TConstraint> =
|
|
115
|
+
| (T extends TConstraint ? T : never)
|
|
116
|
+
| TDefault
|
|
117
|
+
|
|
118
|
+
export type ConstrainLiteral<T, TConstraint, TDefault = TConstraint> =
|
|
119
|
+
| (T & TConstraint)
|
|
120
|
+
| TDefault
|
|
121
|
+
|
|
114
122
|
/**
|
|
115
123
|
* To be added to router types
|
|
116
124
|
*/
|
|
@@ -139,10 +147,6 @@ export type MergeAll<TUnion> =
|
|
|
139
147
|
| MergeAllObjects<TUnion>
|
|
140
148
|
| ExtractPrimitives<TUnion>
|
|
141
149
|
|
|
142
|
-
export type Constrain<T, TConstraint, TDefault = TConstraint> =
|
|
143
|
-
| (T extends TConstraint ? T : never)
|
|
144
|
-
| TDefault
|
|
145
|
-
|
|
146
150
|
export type ValidateJSON<T> = ((...args: Array<any>) => any) extends T
|
|
147
151
|
? unknown extends T
|
|
148
152
|
? never
|
|
@@ -359,7 +363,7 @@ export type StrictOrFrom<
|
|
|
359
363
|
strict: TStrict
|
|
360
364
|
}
|
|
361
365
|
: {
|
|
362
|
-
from:
|
|
366
|
+
from: ConstrainLiteral<TFrom, RouteIds<TRouter['routeTree']>>
|
|
363
367
|
strict?: TStrict
|
|
364
368
|
}
|
|
365
369
|
|