@tanstack/history 1.87.6 → 1.90.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.cjs +162 -57
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +29 -11
- package/dist/esm/index.d.ts +29 -11
- package/dist/esm/index.js +162 -57
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +238 -75
package/src/index.ts
CHANGED
|
@@ -5,21 +5,36 @@
|
|
|
5
5
|
export interface NavigateOptions {
|
|
6
6
|
ignoreBlocker?: boolean
|
|
7
7
|
}
|
|
8
|
+
|
|
9
|
+
type SubscriberHistoryAction =
|
|
10
|
+
| {
|
|
11
|
+
type: HistoryAction | 'ROLLBACK'
|
|
12
|
+
}
|
|
13
|
+
| {
|
|
14
|
+
type: 'GO'
|
|
15
|
+
index: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type SubscriberArgs = {
|
|
19
|
+
location: HistoryLocation
|
|
20
|
+
action: SubscriberHistoryAction
|
|
21
|
+
}
|
|
22
|
+
|
|
8
23
|
export interface RouterHistory {
|
|
9
24
|
location: HistoryLocation
|
|
10
25
|
length: number
|
|
11
|
-
subscribers: Set<(opts:
|
|
12
|
-
subscribe: (cb: (opts:
|
|
26
|
+
subscribers: Set<(opts: SubscriberArgs) => void>
|
|
27
|
+
subscribe: (cb: (opts: SubscriberArgs) => void) => () => void
|
|
13
28
|
push: (path: string, state?: any, navigateOpts?: NavigateOptions) => void
|
|
14
29
|
replace: (path: string, state?: any, navigateOpts?: NavigateOptions) => void
|
|
15
30
|
go: (index: number, navigateOpts?: NavigateOptions) => void
|
|
16
31
|
back: (navigateOpts?: NavigateOptions) => void
|
|
17
32
|
forward: (navigateOpts?: NavigateOptions) => void
|
|
18
33
|
createHref: (href: string) => string
|
|
19
|
-
block: (blocker:
|
|
34
|
+
block: (blocker: NavigationBlocker) => () => void
|
|
20
35
|
flush: () => void
|
|
21
36
|
destroy: () => void
|
|
22
|
-
notify: () => void
|
|
37
|
+
notify: (action: SubscriberHistoryAction) => void
|
|
23
38
|
_ignoreSubscribers?: boolean
|
|
24
39
|
}
|
|
25
40
|
|
|
@@ -40,25 +55,46 @@ export interface HistoryState {
|
|
|
40
55
|
|
|
41
56
|
type ShouldAllowNavigation = any
|
|
42
57
|
|
|
43
|
-
export type
|
|
44
|
-
|
|
|
45
|
-
|
|
|
58
|
+
export type HistoryAction =
|
|
59
|
+
| 'PUSH'
|
|
60
|
+
| 'POP'
|
|
61
|
+
| 'REPLACE'
|
|
62
|
+
| 'FORWARD'
|
|
63
|
+
| 'BACK'
|
|
64
|
+
| 'GO'
|
|
65
|
+
|
|
66
|
+
export type BlockerFnArgs = {
|
|
67
|
+
currentLocation: HistoryLocation
|
|
68
|
+
nextLocation: HistoryLocation
|
|
69
|
+
action: HistoryAction
|
|
70
|
+
}
|
|
46
71
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
72
|
+
export type BlockerFn = (
|
|
73
|
+
args: BlockerFnArgs,
|
|
74
|
+
) => Promise<ShouldAllowNavigation> | ShouldAllowNavigation
|
|
50
75
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return (event.returnValue = '')
|
|
76
|
+
export type NavigationBlocker = {
|
|
77
|
+
blockerFn: BlockerFn
|
|
78
|
+
enableBeforeUnload?: (() => boolean) | boolean
|
|
55
79
|
}
|
|
56
80
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
81
|
+
type TryNavigateArgs = {
|
|
82
|
+
task: () => void
|
|
83
|
+
type: 'PUSH' | 'REPLACE' | 'BACK' | 'FORWARD' | 'GO'
|
|
84
|
+
navigateOpts?: NavigateOptions
|
|
85
|
+
} & (
|
|
86
|
+
| {
|
|
87
|
+
type: 'PUSH' | 'REPLACE'
|
|
88
|
+
path: string
|
|
89
|
+
state: any
|
|
90
|
+
}
|
|
91
|
+
| {
|
|
92
|
+
type: 'BACK' | 'FORWARD' | 'GO'
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
const popStateEvent = 'popstate'
|
|
97
|
+
const beforeUnloadEvent = 'beforeunload'
|
|
62
98
|
|
|
63
99
|
export function createHistory(opts: {
|
|
64
100
|
getLocation: () => HistoryLocation
|
|
@@ -66,32 +102,54 @@ export function createHistory(opts: {
|
|
|
66
102
|
pushState: (path: string, state: any) => void
|
|
67
103
|
replaceState: (path: string, state: any) => void
|
|
68
104
|
go: (n: number) => void
|
|
69
|
-
back: () => void
|
|
70
|
-
forward: () => void
|
|
105
|
+
back: (ignoreBlocker: boolean) => void
|
|
106
|
+
forward: (ignoreBlocker: boolean) => void
|
|
71
107
|
createHref: (path: string) => string
|
|
72
108
|
flush?: () => void
|
|
73
109
|
destroy?: () => void
|
|
74
110
|
onBlocked?: (onUpdate: () => void) => void
|
|
111
|
+
getBlockers?: () => Array<NavigationBlocker>
|
|
112
|
+
setBlockers?: (blockers: Array<NavigationBlocker>) => void
|
|
75
113
|
}): RouterHistory {
|
|
76
114
|
let location = opts.getLocation()
|
|
77
|
-
const subscribers = new Set<(opts:
|
|
78
|
-
let blockers: Array<BlockerFn> = []
|
|
115
|
+
const subscribers = new Set<(opts: SubscriberArgs) => void>()
|
|
79
116
|
|
|
80
|
-
const notify = () => {
|
|
117
|
+
const notify = (action: SubscriberHistoryAction) => {
|
|
81
118
|
location = opts.getLocation()
|
|
82
|
-
subscribers.forEach((subscriber) => subscriber({ location }))
|
|
119
|
+
subscribers.forEach((subscriber) => subscriber({ location, action }))
|
|
83
120
|
}
|
|
84
121
|
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
122
|
+
const _notifyRollback = () => {
|
|
123
|
+
location = opts.getLocation()
|
|
124
|
+
subscribers.forEach((subscriber) =>
|
|
125
|
+
subscriber({ location, action: { type: 'ROLLBACK' } }),
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const tryNavigation = async ({
|
|
130
|
+
task,
|
|
131
|
+
navigateOpts,
|
|
132
|
+
...actionInfo
|
|
133
|
+
}: TryNavigateArgs) => {
|
|
89
134
|
const ignoreBlocker = navigateOpts?.ignoreBlocker ?? false
|
|
90
|
-
if (
|
|
135
|
+
if (ignoreBlocker) {
|
|
136
|
+
task()
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const blockers = opts.getBlockers?.() ?? []
|
|
141
|
+
const isPushOrReplace =
|
|
142
|
+
actionInfo.type === 'PUSH' || actionInfo.type === 'REPLACE'
|
|
143
|
+
if (typeof document !== 'undefined' && blockers.length && isPushOrReplace) {
|
|
91
144
|
for (const blocker of blockers) {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
145
|
+
const nextLocation = parseHref(actionInfo.path, actionInfo.state)
|
|
146
|
+
const isBlocked = await blocker.blockerFn({
|
|
147
|
+
currentLocation: location,
|
|
148
|
+
nextLocation,
|
|
149
|
+
action: actionInfo.type,
|
|
150
|
+
})
|
|
151
|
+
if (isBlocked) {
|
|
152
|
+
opts.onBlocked?.(_notifyRollback)
|
|
95
153
|
return
|
|
96
154
|
}
|
|
97
155
|
}
|
|
@@ -108,7 +166,7 @@ export function createHistory(opts: {
|
|
|
108
166
|
return opts.getLength()
|
|
109
167
|
},
|
|
110
168
|
subscribers,
|
|
111
|
-
subscribe: (cb: (opts:
|
|
169
|
+
subscribe: (cb: (opts: SubscriberArgs) => void) => {
|
|
112
170
|
subscribers.add(cb)
|
|
113
171
|
|
|
114
172
|
return () => {
|
|
@@ -117,52 +175,69 @@ export function createHistory(opts: {
|
|
|
117
175
|
},
|
|
118
176
|
push: (path, state, navigateOpts) => {
|
|
119
177
|
state = assignKey(state)
|
|
120
|
-
tryNavigation(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
178
|
+
tryNavigation({
|
|
179
|
+
task: () => {
|
|
180
|
+
opts.pushState(path, state)
|
|
181
|
+
notify({ type: 'PUSH' })
|
|
182
|
+
},
|
|
183
|
+
navigateOpts,
|
|
184
|
+
type: 'PUSH',
|
|
185
|
+
path,
|
|
186
|
+
state,
|
|
187
|
+
})
|
|
124
188
|
},
|
|
125
189
|
replace: (path, state, navigateOpts) => {
|
|
126
190
|
state = assignKey(state)
|
|
127
|
-
tryNavigation(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
191
|
+
tryNavigation({
|
|
192
|
+
task: () => {
|
|
193
|
+
opts.replaceState(path, state)
|
|
194
|
+
notify({ type: 'REPLACE' })
|
|
195
|
+
},
|
|
196
|
+
navigateOpts,
|
|
197
|
+
type: 'REPLACE',
|
|
198
|
+
path,
|
|
199
|
+
state,
|
|
200
|
+
})
|
|
131
201
|
},
|
|
132
202
|
go: (index, navigateOpts) => {
|
|
133
|
-
tryNavigation(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
203
|
+
tryNavigation({
|
|
204
|
+
task: () => {
|
|
205
|
+
opts.go(index)
|
|
206
|
+
notify({ type: 'GO', index })
|
|
207
|
+
},
|
|
208
|
+
navigateOpts,
|
|
209
|
+
type: 'GO',
|
|
210
|
+
})
|
|
137
211
|
},
|
|
138
212
|
back: (navigateOpts) => {
|
|
139
|
-
tryNavigation(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
213
|
+
tryNavigation({
|
|
214
|
+
task: () => {
|
|
215
|
+
opts.back(navigateOpts?.ignoreBlocker ?? false)
|
|
216
|
+
notify({ type: 'BACK' })
|
|
217
|
+
},
|
|
218
|
+
navigateOpts,
|
|
219
|
+
type: 'BACK',
|
|
220
|
+
})
|
|
143
221
|
},
|
|
144
222
|
forward: (navigateOpts) => {
|
|
145
|
-
tryNavigation(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
223
|
+
tryNavigation({
|
|
224
|
+
task: () => {
|
|
225
|
+
opts.forward(navigateOpts?.ignoreBlocker ?? false)
|
|
226
|
+
notify({ type: 'FORWARD' })
|
|
227
|
+
},
|
|
228
|
+
navigateOpts,
|
|
229
|
+
type: 'FORWARD',
|
|
230
|
+
})
|
|
149
231
|
},
|
|
150
232
|
createHref: (str) => opts.createHref(str),
|
|
151
233
|
block: (blocker) => {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
addEventListener(beforeUnloadEvent, beforeUnloadListener, {
|
|
156
|
-
capture: true,
|
|
157
|
-
})
|
|
158
|
-
}
|
|
234
|
+
if (!opts.setBlockers) return () => {}
|
|
235
|
+
const blockers = opts.getBlockers?.() ?? []
|
|
236
|
+
opts.setBlockers([...blockers, blocker])
|
|
159
237
|
|
|
160
238
|
return () => {
|
|
161
|
-
blockers =
|
|
162
|
-
|
|
163
|
-
if (!blockers.length) {
|
|
164
|
-
stopBlocking()
|
|
165
|
-
}
|
|
239
|
+
const blockers = opts.getBlockers?.() ?? []
|
|
240
|
+
opts.setBlockers?.(blockers.filter((b) => b !== blocker))
|
|
166
241
|
}
|
|
167
242
|
},
|
|
168
243
|
flush: () => opts.flush?.(),
|
|
@@ -209,6 +284,11 @@ export function createBrowserHistory(opts?: {
|
|
|
209
284
|
const originalPushState = win.history.pushState
|
|
210
285
|
const originalReplaceState = win.history.replaceState
|
|
211
286
|
|
|
287
|
+
let blockers: Array<NavigationBlocker> = []
|
|
288
|
+
const _getBlockers = () => blockers
|
|
289
|
+
const _setBlockers = (newBlockers: Array<NavigationBlocker>) =>
|
|
290
|
+
(blockers = newBlockers)
|
|
291
|
+
|
|
212
292
|
const createHref = opts?.createHref ?? ((path) => path)
|
|
213
293
|
const parseLocation =
|
|
214
294
|
opts?.parseLocation ??
|
|
@@ -221,6 +301,10 @@ export function createBrowserHistory(opts?: {
|
|
|
221
301
|
let currentLocation = parseLocation()
|
|
222
302
|
let rollbackLocation: HistoryLocation | undefined
|
|
223
303
|
|
|
304
|
+
let ignoreNextPop = false
|
|
305
|
+
let skipBlockerNextPop = false
|
|
306
|
+
let ignoreNextBeforeUnload = false
|
|
307
|
+
|
|
224
308
|
const getLocation = () => currentLocation
|
|
225
309
|
|
|
226
310
|
let next:
|
|
@@ -293,7 +377,74 @@ export function createBrowserHistory(opts?: {
|
|
|
293
377
|
|
|
294
378
|
const onPushPop = () => {
|
|
295
379
|
currentLocation = parseLocation()
|
|
296
|
-
history.notify()
|
|
380
|
+
history.notify({ type: 'POP' })
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const onPushPopEvent = async () => {
|
|
384
|
+
if (ignoreNextPop) {
|
|
385
|
+
ignoreNextPop = false
|
|
386
|
+
return
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (skipBlockerNextPop) {
|
|
390
|
+
skipBlockerNextPop = false
|
|
391
|
+
} else {
|
|
392
|
+
const blockers = _getBlockers()
|
|
393
|
+
if (typeof document !== 'undefined' && blockers.length) {
|
|
394
|
+
for (const blocker of blockers) {
|
|
395
|
+
const nextLocation = parseLocation()
|
|
396
|
+
const isBlocked = await blocker.blockerFn({
|
|
397
|
+
currentLocation,
|
|
398
|
+
nextLocation,
|
|
399
|
+
action: 'POP',
|
|
400
|
+
})
|
|
401
|
+
if (isBlocked) {
|
|
402
|
+
ignoreNextPop = true
|
|
403
|
+
win.history.go(1)
|
|
404
|
+
history.notify({ type: 'POP' })
|
|
405
|
+
return
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
currentLocation = parseLocation()
|
|
412
|
+
history.notify({ type: 'POP' })
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const onBeforeUnload = (e: BeforeUnloadEvent) => {
|
|
416
|
+
if (ignoreNextBeforeUnload) {
|
|
417
|
+
ignoreNextBeforeUnload = false
|
|
418
|
+
return
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
let shouldBlock = false
|
|
422
|
+
|
|
423
|
+
// If one blocker has a non-disabled beforeUnload, we should block
|
|
424
|
+
const blockers = _getBlockers()
|
|
425
|
+
if (typeof document !== 'undefined' && blockers.length) {
|
|
426
|
+
for (const blocker of blockers) {
|
|
427
|
+
const shouldHaveBeforeUnload = blocker.enableBeforeUnload ?? true
|
|
428
|
+
if (shouldHaveBeforeUnload === true) {
|
|
429
|
+
shouldBlock = true
|
|
430
|
+
break
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (
|
|
434
|
+
typeof shouldHaveBeforeUnload === 'function' &&
|
|
435
|
+
shouldHaveBeforeUnload() === true
|
|
436
|
+
) {
|
|
437
|
+
shouldBlock = true
|
|
438
|
+
break
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (shouldBlock) {
|
|
444
|
+
e.preventDefault()
|
|
445
|
+
return (e.returnValue = '')
|
|
446
|
+
}
|
|
447
|
+
return
|
|
297
448
|
}
|
|
298
449
|
|
|
299
450
|
const history = createHistory({
|
|
@@ -301,16 +452,26 @@ export function createBrowserHistory(opts?: {
|
|
|
301
452
|
getLength: () => win.history.length,
|
|
302
453
|
pushState: (href, state) => queueHistoryAction('push', href, state),
|
|
303
454
|
replaceState: (href, state) => queueHistoryAction('replace', href, state),
|
|
304
|
-
back: () =>
|
|
305
|
-
|
|
455
|
+
back: (ignoreBlocker) => {
|
|
456
|
+
if (ignoreBlocker) skipBlockerNextPop = true
|
|
457
|
+
ignoreNextBeforeUnload = true
|
|
458
|
+
return win.history.back()
|
|
459
|
+
},
|
|
460
|
+
forward: (ignoreBlocker) => {
|
|
461
|
+
if (ignoreBlocker) skipBlockerNextPop = true
|
|
462
|
+
ignoreNextBeforeUnload = true
|
|
463
|
+
win.history.forward()
|
|
464
|
+
},
|
|
306
465
|
go: (n) => win.history.go(n),
|
|
307
466
|
createHref: (href) => createHref(href),
|
|
308
467
|
flush,
|
|
309
468
|
destroy: () => {
|
|
310
469
|
win.history.pushState = originalPushState
|
|
311
470
|
win.history.replaceState = originalReplaceState
|
|
312
|
-
win.removeEventListener(
|
|
313
|
-
|
|
471
|
+
win.removeEventListener(beforeUnloadEvent, onBeforeUnload, {
|
|
472
|
+
capture: true,
|
|
473
|
+
})
|
|
474
|
+
win.removeEventListener(popStateEvent, onPushPopEvent)
|
|
314
475
|
},
|
|
315
476
|
onBlocked: (onUpdate) => {
|
|
316
477
|
// If a navigation is blocked, we need to rollback the location
|
|
@@ -321,19 +482,21 @@ export function createBrowserHistory(opts?: {
|
|
|
321
482
|
onUpdate()
|
|
322
483
|
}
|
|
323
484
|
},
|
|
485
|
+
getBlockers: _getBlockers,
|
|
486
|
+
setBlockers: _setBlockers,
|
|
324
487
|
})
|
|
325
488
|
|
|
326
|
-
win.addEventListener(
|
|
327
|
-
win.addEventListener(popStateEvent,
|
|
489
|
+
win.addEventListener(beforeUnloadEvent, onBeforeUnload, { capture: true })
|
|
490
|
+
win.addEventListener(popStateEvent, onPushPopEvent)
|
|
328
491
|
|
|
329
492
|
win.history.pushState = function (...args: Array<any>) {
|
|
330
|
-
const res = originalPushState.apply(win.history, args)
|
|
493
|
+
const res = originalPushState.apply(win.history, args as any)
|
|
331
494
|
if (!history._ignoreSubscribers) onPushPop()
|
|
332
495
|
return res
|
|
333
496
|
}
|
|
334
497
|
|
|
335
498
|
win.history.replaceState = function (...args: Array<any>) {
|
|
336
|
-
const res = originalReplaceState.apply(win.history, args)
|
|
499
|
+
const res = originalReplaceState.apply(win.history, args as any)
|
|
337
500
|
if (!history._ignoreSubscribers) onPushPop()
|
|
338
501
|
return res
|
|
339
502
|
}
|