@tanstack/history 1.15.13 → 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.
- package/dist/cjs/index.cjs +253 -106
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +50 -15
- package/dist/esm/index.d.ts +50 -15
- package/dist/esm/index.js +254 -107
- package/dist/esm/index.js.map +1 -1
- package/package.json +11 -16
- package/src/index.ts +363 -136
package/src/index.ts
CHANGED
|
@@ -2,23 +2,45 @@
|
|
|
2
2
|
// This implementation attempts to be more lightweight by
|
|
3
3
|
// making assumptions about the way TanStack Router works
|
|
4
4
|
|
|
5
|
+
export interface NavigateOptions {
|
|
6
|
+
ignoreBlocker?: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type SubscriberHistoryAction =
|
|
10
|
+
| {
|
|
11
|
+
type: Exclude<HistoryAction, 'GO'>
|
|
12
|
+
}
|
|
13
|
+
| {
|
|
14
|
+
type: 'GO'
|
|
15
|
+
index: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type SubscriberArgs = {
|
|
19
|
+
location: HistoryLocation
|
|
20
|
+
action: SubscriberHistoryAction
|
|
21
|
+
}
|
|
22
|
+
|
|
5
23
|
export interface RouterHistory {
|
|
6
24
|
location: HistoryLocation
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
25
|
+
length: number
|
|
26
|
+
subscribers: Set<(opts: SubscriberArgs) => void>
|
|
27
|
+
subscribe: (cb: (opts: SubscriberArgs) => void) => () => void
|
|
28
|
+
push: (path: string, state?: any, navigateOpts?: NavigateOptions) => void
|
|
29
|
+
replace: (path: string, state?: any, navigateOpts?: NavigateOptions) => void
|
|
30
|
+
go: (index: number, navigateOpts?: NavigateOptions) => void
|
|
31
|
+
back: (navigateOpts?: NavigateOptions) => void
|
|
32
|
+
forward: (navigateOpts?: NavigateOptions) => void
|
|
33
|
+
canGoBack: () => boolean
|
|
13
34
|
createHref: (href: string) => string
|
|
14
|
-
block: (blocker:
|
|
35
|
+
block: (blocker: NavigationBlocker) => () => void
|
|
15
36
|
flush: () => void
|
|
16
37
|
destroy: () => void
|
|
17
|
-
notify: () => void
|
|
38
|
+
notify: (action: SubscriberHistoryAction) => void
|
|
39
|
+
_ignoreSubscribers?: boolean
|
|
18
40
|
}
|
|
19
41
|
|
|
20
42
|
export interface HistoryLocation extends ParsedPath {
|
|
21
|
-
state:
|
|
43
|
+
state: ParsedHistoryState
|
|
22
44
|
}
|
|
23
45
|
|
|
24
46
|
export interface ParsedPath {
|
|
@@ -28,59 +50,105 @@ export interface ParsedPath {
|
|
|
28
50
|
hash: string
|
|
29
51
|
}
|
|
30
52
|
|
|
31
|
-
export interface HistoryState {
|
|
53
|
+
export interface HistoryState {}
|
|
54
|
+
|
|
55
|
+
export type ParsedHistoryState = HistoryState & {
|
|
32
56
|
key?: string
|
|
57
|
+
__TSR_index: number
|
|
33
58
|
}
|
|
34
59
|
|
|
35
60
|
type ShouldAllowNavigation = any
|
|
36
61
|
|
|
37
|
-
export type
|
|
38
|
-
| Promise<ShouldAllowNavigation>
|
|
39
|
-
| ShouldAllowNavigation
|
|
40
|
-
|
|
41
|
-
const pushStateEvent = 'pushstate'
|
|
42
|
-
const popStateEvent = 'popstate'
|
|
43
|
-
const beforeUnloadEvent = 'beforeunload'
|
|
62
|
+
export type HistoryAction = 'PUSH' | 'REPLACE' | 'FORWARD' | 'BACK' | 'GO'
|
|
44
63
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
64
|
+
export type BlockerFnArgs = {
|
|
65
|
+
currentLocation: HistoryLocation
|
|
66
|
+
nextLocation: HistoryLocation
|
|
67
|
+
action: HistoryAction
|
|
49
68
|
}
|
|
50
69
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
70
|
+
export type BlockerFn = (
|
|
71
|
+
args: BlockerFnArgs,
|
|
72
|
+
) => Promise<ShouldAllowNavigation> | ShouldAllowNavigation
|
|
73
|
+
|
|
74
|
+
export type NavigationBlocker = {
|
|
75
|
+
blockerFn: BlockerFn
|
|
76
|
+
enableBeforeUnload?: (() => boolean) | boolean
|
|
55
77
|
}
|
|
56
78
|
|
|
79
|
+
type TryNavigateArgs = {
|
|
80
|
+
task: () => void
|
|
81
|
+
type: 'PUSH' | 'REPLACE' | 'BACK' | 'FORWARD' | 'GO'
|
|
82
|
+
navigateOpts?: NavigateOptions
|
|
83
|
+
} & (
|
|
84
|
+
| {
|
|
85
|
+
type: 'PUSH' | 'REPLACE'
|
|
86
|
+
path: string
|
|
87
|
+
state: any
|
|
88
|
+
}
|
|
89
|
+
| {
|
|
90
|
+
type: 'BACK' | 'FORWARD' | 'GO'
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
const stateIndexKey = '__TSR_index'
|
|
95
|
+
const popStateEvent = 'popstate'
|
|
96
|
+
const beforeUnloadEvent = 'beforeunload'
|
|
97
|
+
|
|
57
98
|
export function createHistory(opts: {
|
|
58
99
|
getLocation: () => HistoryLocation
|
|
100
|
+
getLength: () => number
|
|
59
101
|
pushState: (path: string, state: any) => void
|
|
60
102
|
replaceState: (path: string, state: any) => void
|
|
61
103
|
go: (n: number) => void
|
|
62
|
-
back: () => void
|
|
63
|
-
forward: () => void
|
|
104
|
+
back: (ignoreBlocker: boolean) => void
|
|
105
|
+
forward: (ignoreBlocker: boolean) => void
|
|
64
106
|
createHref: (path: string) => string
|
|
65
107
|
flush?: () => void
|
|
66
108
|
destroy?: () => void
|
|
67
|
-
onBlocked?: (
|
|
109
|
+
onBlocked?: () => void
|
|
110
|
+
getBlockers?: () => Array<NavigationBlocker>
|
|
111
|
+
setBlockers?: (blockers: Array<NavigationBlocker>) => void
|
|
112
|
+
// Avoid notifying on forward/back/go, used for browser history as we already get notified by the popstate event
|
|
113
|
+
notifyOnIndexChange?: boolean
|
|
68
114
|
}): RouterHistory {
|
|
69
115
|
let location = opts.getLocation()
|
|
70
|
-
|
|
71
|
-
let blockers: BlockerFn[] = []
|
|
116
|
+
const subscribers = new Set<(opts: SubscriberArgs) => void>()
|
|
72
117
|
|
|
73
|
-
const
|
|
118
|
+
const notify = (action: SubscriberHistoryAction) => {
|
|
74
119
|
location = opts.getLocation()
|
|
75
|
-
subscribers.forEach((subscriber) => subscriber())
|
|
120
|
+
subscribers.forEach((subscriber) => subscriber({ location, action }))
|
|
76
121
|
}
|
|
77
122
|
|
|
78
|
-
const
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
123
|
+
const handleIndexChange = (action: SubscriberHistoryAction) => {
|
|
124
|
+
if (opts.notifyOnIndexChange ?? true) notify(action)
|
|
125
|
+
else location = opts.getLocation()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const tryNavigation = async ({
|
|
129
|
+
task,
|
|
130
|
+
navigateOpts,
|
|
131
|
+
...actionInfo
|
|
132
|
+
}: TryNavigateArgs) => {
|
|
133
|
+
const ignoreBlocker = navigateOpts?.ignoreBlocker ?? false
|
|
134
|
+
if (ignoreBlocker) {
|
|
135
|
+
task()
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const blockers = opts.getBlockers?.() ?? []
|
|
140
|
+
const isPushOrReplace =
|
|
141
|
+
actionInfo.type === 'PUSH' || actionInfo.type === 'REPLACE'
|
|
142
|
+
if (typeof document !== 'undefined' && blockers.length && isPushOrReplace) {
|
|
143
|
+
for (const blocker of blockers) {
|
|
144
|
+
const nextLocation = parseHref(actionInfo.path, actionInfo.state)
|
|
145
|
+
const isBlocked = await blocker.blockerFn({
|
|
146
|
+
currentLocation: location,
|
|
147
|
+
nextLocation,
|
|
148
|
+
action: actionInfo.type,
|
|
149
|
+
})
|
|
150
|
+
if (isBlocked) {
|
|
151
|
+
opts.onBlocked?.()
|
|
84
152
|
return
|
|
85
153
|
}
|
|
86
154
|
}
|
|
@@ -93,74 +161,102 @@ export function createHistory(opts: {
|
|
|
93
161
|
get location() {
|
|
94
162
|
return location
|
|
95
163
|
},
|
|
96
|
-
|
|
164
|
+
get length() {
|
|
165
|
+
return opts.getLength()
|
|
166
|
+
},
|
|
167
|
+
subscribers,
|
|
168
|
+
subscribe: (cb: (opts: SubscriberArgs) => void) => {
|
|
97
169
|
subscribers.add(cb)
|
|
98
170
|
|
|
99
171
|
return () => {
|
|
100
172
|
subscribers.delete(cb)
|
|
101
173
|
}
|
|
102
174
|
},
|
|
103
|
-
push: (path
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
175
|
+
push: (path, state, navigateOpts) => {
|
|
176
|
+
const currentIndex = location.state[stateIndexKey]
|
|
177
|
+
state = assignKeyAndIndex(currentIndex + 1, state)
|
|
178
|
+
tryNavigation({
|
|
179
|
+
task: () => {
|
|
180
|
+
opts.pushState(path, state)
|
|
181
|
+
notify({ type: 'PUSH' })
|
|
182
|
+
},
|
|
183
|
+
navigateOpts,
|
|
184
|
+
type: 'PUSH',
|
|
185
|
+
path,
|
|
186
|
+
state,
|
|
108
187
|
})
|
|
109
188
|
},
|
|
110
|
-
replace: (path
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
189
|
+
replace: (path, state, navigateOpts) => {
|
|
190
|
+
const currentIndex = location.state[stateIndexKey]
|
|
191
|
+
state = assignKeyAndIndex(currentIndex, state)
|
|
192
|
+
tryNavigation({
|
|
193
|
+
task: () => {
|
|
194
|
+
opts.replaceState(path, state)
|
|
195
|
+
notify({ type: 'REPLACE' })
|
|
196
|
+
},
|
|
197
|
+
navigateOpts,
|
|
198
|
+
type: 'REPLACE',
|
|
199
|
+
path,
|
|
200
|
+
state,
|
|
115
201
|
})
|
|
116
202
|
},
|
|
117
|
-
go: (index) => {
|
|
118
|
-
tryNavigation(
|
|
119
|
-
|
|
203
|
+
go: (index, navigateOpts) => {
|
|
204
|
+
tryNavigation({
|
|
205
|
+
task: () => {
|
|
206
|
+
opts.go(index)
|
|
207
|
+
handleIndexChange({ type: 'GO', index })
|
|
208
|
+
},
|
|
209
|
+
navigateOpts,
|
|
210
|
+
type: 'GO',
|
|
120
211
|
})
|
|
121
212
|
},
|
|
122
|
-
back: () => {
|
|
123
|
-
tryNavigation(
|
|
124
|
-
|
|
213
|
+
back: (navigateOpts) => {
|
|
214
|
+
tryNavigation({
|
|
215
|
+
task: () => {
|
|
216
|
+
opts.back(navigateOpts?.ignoreBlocker ?? false)
|
|
217
|
+
handleIndexChange({ type: 'BACK' })
|
|
218
|
+
},
|
|
219
|
+
navigateOpts,
|
|
220
|
+
type: 'BACK',
|
|
125
221
|
})
|
|
126
222
|
},
|
|
127
|
-
forward: () => {
|
|
128
|
-
tryNavigation(
|
|
129
|
-
|
|
223
|
+
forward: (navigateOpts) => {
|
|
224
|
+
tryNavigation({
|
|
225
|
+
task: () => {
|
|
226
|
+
opts.forward(navigateOpts?.ignoreBlocker ?? false)
|
|
227
|
+
handleIndexChange({ type: 'FORWARD' })
|
|
228
|
+
},
|
|
229
|
+
navigateOpts,
|
|
230
|
+
type: 'FORWARD',
|
|
130
231
|
})
|
|
131
232
|
},
|
|
233
|
+
canGoBack: () => location.state[stateIndexKey] !== 0,
|
|
132
234
|
createHref: (str) => opts.createHref(str),
|
|
133
235
|
block: (blocker) => {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
addEventListener(beforeUnloadEvent, beforeUnloadListener, {
|
|
138
|
-
capture: true,
|
|
139
|
-
})
|
|
140
|
-
}
|
|
236
|
+
if (!opts.setBlockers) return () => {}
|
|
237
|
+
const blockers = opts.getBlockers?.() ?? []
|
|
238
|
+
opts.setBlockers([...blockers, blocker])
|
|
141
239
|
|
|
142
240
|
return () => {
|
|
143
|
-
blockers =
|
|
144
|
-
|
|
145
|
-
if (!blockers.length) {
|
|
146
|
-
stopBlocking()
|
|
147
|
-
}
|
|
241
|
+
const blockers = opts.getBlockers?.() ?? []
|
|
242
|
+
opts.setBlockers?.(blockers.filter((b) => b !== blocker))
|
|
148
243
|
}
|
|
149
244
|
},
|
|
150
245
|
flush: () => opts.flush?.(),
|
|
151
246
|
destroy: () => opts.destroy?.(),
|
|
152
|
-
notify
|
|
247
|
+
notify,
|
|
153
248
|
}
|
|
154
249
|
}
|
|
155
250
|
|
|
156
|
-
function
|
|
251
|
+
function assignKeyAndIndex(index: number, state: HistoryState | undefined) {
|
|
157
252
|
if (!state) {
|
|
158
253
|
state = {} as HistoryState
|
|
159
254
|
}
|
|
160
255
|
return {
|
|
161
256
|
...state,
|
|
162
257
|
key: createRandomKey(),
|
|
163
|
-
|
|
258
|
+
[stateIndexKey]: index,
|
|
259
|
+
} as ParsedHistoryState
|
|
164
260
|
}
|
|
165
261
|
|
|
166
262
|
/**
|
|
@@ -188,6 +284,14 @@ export function createBrowserHistory(opts?: {
|
|
|
188
284
|
opts?.window ??
|
|
189
285
|
(typeof document !== 'undefined' ? window : (undefined as any))
|
|
190
286
|
|
|
287
|
+
const originalPushState = win.history.pushState
|
|
288
|
+
const originalReplaceState = win.history.replaceState
|
|
289
|
+
|
|
290
|
+
let blockers: Array<NavigationBlocker> = []
|
|
291
|
+
const _getBlockers = () => blockers
|
|
292
|
+
const _setBlockers = (newBlockers: Array<NavigationBlocker>) =>
|
|
293
|
+
(blockers = newBlockers)
|
|
294
|
+
|
|
191
295
|
const createHref = opts?.createHref ?? ((path) => path)
|
|
192
296
|
const parseLocation =
|
|
193
297
|
opts?.parseLocation ??
|
|
@@ -197,9 +301,25 @@ export function createBrowserHistory(opts?: {
|
|
|
197
301
|
win.history.state,
|
|
198
302
|
))
|
|
199
303
|
|
|
304
|
+
// Ensure there is always a key to start
|
|
305
|
+
if (!win.history.state?.key) {
|
|
306
|
+
win.history.replaceState(
|
|
307
|
+
{
|
|
308
|
+
[stateIndexKey]: 0,
|
|
309
|
+
key: createRandomKey(),
|
|
310
|
+
},
|
|
311
|
+
'',
|
|
312
|
+
)
|
|
313
|
+
}
|
|
314
|
+
|
|
200
315
|
let currentLocation = parseLocation()
|
|
201
316
|
let rollbackLocation: HistoryLocation | undefined
|
|
202
317
|
|
|
318
|
+
let nextPopIsGo = false
|
|
319
|
+
let ignoreNextPop = false
|
|
320
|
+
let skipBlockerNextPop = false
|
|
321
|
+
let ignoreNextBeforeUnload = false
|
|
322
|
+
|
|
203
323
|
const getLocation = () => currentLocation
|
|
204
324
|
|
|
205
325
|
let next:
|
|
@@ -213,39 +333,33 @@ export function createBrowserHistory(opts?: {
|
|
|
213
333
|
isPush: boolean
|
|
214
334
|
}
|
|
215
335
|
|
|
216
|
-
// Because we are proactively updating the location
|
|
217
|
-
// in memory before actually updating the browser history,
|
|
218
|
-
// we need to track when we are doing this so we don't
|
|
219
|
-
// notify subscribers twice on the last update.
|
|
220
|
-
let tracking = true
|
|
221
|
-
|
|
222
336
|
// We need to track the current scheduled update to prevent
|
|
223
337
|
// multiple updates from being scheduled at the same time.
|
|
224
338
|
let scheduled: Promise<void> | undefined
|
|
225
339
|
|
|
226
|
-
// This function is a wrapper to prevent any of the callback's
|
|
227
|
-
// side effects from causing a subscriber notification
|
|
228
|
-
const untrack = (fn: () => void) => {
|
|
229
|
-
tracking = false
|
|
230
|
-
fn()
|
|
231
|
-
tracking = true
|
|
232
|
-
}
|
|
233
|
-
|
|
234
340
|
// This function flushes the next update to the browser history
|
|
235
341
|
const flush = () => {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
next
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
342
|
+
if (!next) {
|
|
343
|
+
return
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// We need to ignore any updates to the subscribers while we update the browser history
|
|
347
|
+
history._ignoreSubscribers = true
|
|
348
|
+
|
|
349
|
+
// Update the browser history
|
|
350
|
+
;(next.isPush ? win.history.pushState : win.history.replaceState)(
|
|
351
|
+
next.state,
|
|
352
|
+
'',
|
|
353
|
+
next.href,
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
// Stop ignoring subscriber updates
|
|
357
|
+
history._ignoreSubscribers = false
|
|
358
|
+
|
|
359
|
+
// Reset the nextIsPush flag and clear the scheduled update
|
|
360
|
+
next = undefined
|
|
361
|
+
scheduled = undefined
|
|
362
|
+
rollbackLocation = undefined
|
|
249
363
|
}
|
|
250
364
|
|
|
251
365
|
// This function queues up a call to update the browser history
|
|
@@ -276,52 +390,149 @@ export function createBrowserHistory(opts?: {
|
|
|
276
390
|
}
|
|
277
391
|
}
|
|
278
392
|
|
|
279
|
-
|
|
393
|
+
// NOTE: this function can probably be removed
|
|
394
|
+
const onPushPop = (type: 'PUSH' | 'REPLACE') => {
|
|
395
|
+
currentLocation = parseLocation()
|
|
396
|
+
history.notify({ type })
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const onPushPopEvent = async () => {
|
|
400
|
+
if (ignoreNextPop) {
|
|
401
|
+
ignoreNextPop = false
|
|
402
|
+
return
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const nextLocation = parseLocation()
|
|
406
|
+
const delta =
|
|
407
|
+
nextLocation.state[stateIndexKey] - currentLocation.state[stateIndexKey]
|
|
408
|
+
const isForward = delta === 1
|
|
409
|
+
const isBack = delta === -1
|
|
410
|
+
const isGo = (!isForward && !isBack) || nextPopIsGo
|
|
411
|
+
nextPopIsGo = false
|
|
412
|
+
|
|
413
|
+
const action = isGo ? 'GO' : isBack ? 'BACK' : 'FORWARD'
|
|
414
|
+
const notify: SubscriberHistoryAction = isGo
|
|
415
|
+
? {
|
|
416
|
+
type: 'GO',
|
|
417
|
+
index: delta,
|
|
418
|
+
}
|
|
419
|
+
: {
|
|
420
|
+
type: isBack ? 'BACK' : 'FORWARD',
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (skipBlockerNextPop) {
|
|
424
|
+
skipBlockerNextPop = false
|
|
425
|
+
} else {
|
|
426
|
+
const blockers = _getBlockers()
|
|
427
|
+
if (typeof document !== 'undefined' && blockers.length) {
|
|
428
|
+
for (const blocker of blockers) {
|
|
429
|
+
const isBlocked = await blocker.blockerFn({
|
|
430
|
+
currentLocation,
|
|
431
|
+
nextLocation,
|
|
432
|
+
action,
|
|
433
|
+
})
|
|
434
|
+
if (isBlocked) {
|
|
435
|
+
ignoreNextPop = true
|
|
436
|
+
win.history.go(1)
|
|
437
|
+
history.notify(notify)
|
|
438
|
+
return
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
280
444
|
currentLocation = parseLocation()
|
|
281
|
-
history.notify()
|
|
445
|
+
history.notify(notify)
|
|
282
446
|
}
|
|
283
447
|
|
|
284
|
-
|
|
285
|
-
|
|
448
|
+
const onBeforeUnload = (e: BeforeUnloadEvent) => {
|
|
449
|
+
if (ignoreNextBeforeUnload) {
|
|
450
|
+
ignoreNextBeforeUnload = false
|
|
451
|
+
return
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
let shouldBlock = false
|
|
455
|
+
|
|
456
|
+
// If one blocker has a non-disabled beforeUnload, we should block
|
|
457
|
+
const blockers = _getBlockers()
|
|
458
|
+
if (typeof document !== 'undefined' && blockers.length) {
|
|
459
|
+
for (const blocker of blockers) {
|
|
460
|
+
const shouldHaveBeforeUnload = blocker.enableBeforeUnload ?? true
|
|
461
|
+
if (shouldHaveBeforeUnload === true) {
|
|
462
|
+
shouldBlock = true
|
|
463
|
+
break
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (
|
|
467
|
+
typeof shouldHaveBeforeUnload === 'function' &&
|
|
468
|
+
shouldHaveBeforeUnload() === true
|
|
469
|
+
) {
|
|
470
|
+
shouldBlock = true
|
|
471
|
+
break
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (shouldBlock) {
|
|
477
|
+
e.preventDefault()
|
|
478
|
+
return (e.returnValue = '')
|
|
479
|
+
}
|
|
480
|
+
return
|
|
481
|
+
}
|
|
286
482
|
|
|
287
483
|
const history = createHistory({
|
|
288
484
|
getLocation,
|
|
485
|
+
getLength: () => win.history.length,
|
|
289
486
|
pushState: (href, state) => queueHistoryAction('push', href, state),
|
|
290
487
|
replaceState: (href, state) => queueHistoryAction('replace', href, state),
|
|
291
|
-
back: () =>
|
|
292
|
-
|
|
293
|
-
|
|
488
|
+
back: (ignoreBlocker) => {
|
|
489
|
+
if (ignoreBlocker) skipBlockerNextPop = true
|
|
490
|
+
ignoreNextBeforeUnload = true
|
|
491
|
+
return win.history.back()
|
|
492
|
+
},
|
|
493
|
+
forward: (ignoreBlocker) => {
|
|
494
|
+
if (ignoreBlocker) skipBlockerNextPop = true
|
|
495
|
+
ignoreNextBeforeUnload = true
|
|
496
|
+
win.history.forward()
|
|
497
|
+
},
|
|
498
|
+
go: (n) => {
|
|
499
|
+
nextPopIsGo = true
|
|
500
|
+
win.history.go(n)
|
|
501
|
+
},
|
|
294
502
|
createHref: (href) => createHref(href),
|
|
295
503
|
flush,
|
|
296
504
|
destroy: () => {
|
|
297
505
|
win.history.pushState = originalPushState
|
|
298
506
|
win.history.replaceState = originalReplaceState
|
|
299
|
-
win.removeEventListener(
|
|
300
|
-
|
|
507
|
+
win.removeEventListener(beforeUnloadEvent, onBeforeUnload, {
|
|
508
|
+
capture: true,
|
|
509
|
+
})
|
|
510
|
+
win.removeEventListener(popStateEvent, onPushPopEvent)
|
|
301
511
|
},
|
|
302
|
-
onBlocked: (
|
|
512
|
+
onBlocked: () => {
|
|
303
513
|
// If a navigation is blocked, we need to rollback the location
|
|
304
514
|
// that we optimistically updated in memory.
|
|
305
515
|
if (rollbackLocation && currentLocation !== rollbackLocation) {
|
|
306
516
|
currentLocation = rollbackLocation
|
|
307
|
-
// Notify subscribers
|
|
308
|
-
onUpdate()
|
|
309
517
|
}
|
|
310
518
|
},
|
|
519
|
+
getBlockers: _getBlockers,
|
|
520
|
+
setBlockers: _setBlockers,
|
|
521
|
+
notifyOnIndexChange: false,
|
|
311
522
|
})
|
|
312
523
|
|
|
313
|
-
win.addEventListener(
|
|
314
|
-
win.addEventListener(popStateEvent,
|
|
524
|
+
win.addEventListener(beforeUnloadEvent, onBeforeUnload, { capture: true })
|
|
525
|
+
win.addEventListener(popStateEvent, onPushPopEvent)
|
|
315
526
|
|
|
316
|
-
win.history.pushState = function () {
|
|
317
|
-
|
|
318
|
-
if (
|
|
527
|
+
win.history.pushState = function (...args: Array<any>) {
|
|
528
|
+
const res = originalPushState.apply(win.history, args as any)
|
|
529
|
+
if (!history._ignoreSubscribers) onPushPop('PUSH')
|
|
319
530
|
return res
|
|
320
531
|
}
|
|
321
532
|
|
|
322
|
-
win.history.replaceState = function () {
|
|
323
|
-
|
|
324
|
-
if (
|
|
533
|
+
win.history.replaceState = function (...args: Array<any>) {
|
|
534
|
+
const res = originalReplaceState.apply(win.history, args as any)
|
|
535
|
+
if (!history._ignoreSubscribers) onPushPop('REPLACE')
|
|
325
536
|
return res
|
|
326
537
|
}
|
|
327
538
|
|
|
@@ -335,7 +546,13 @@ export function createHashHistory(opts?: { window?: any }): RouterHistory {
|
|
|
335
546
|
return createBrowserHistory({
|
|
336
547
|
window: win,
|
|
337
548
|
parseLocation: () => {
|
|
338
|
-
const
|
|
549
|
+
const hashSplit = win.location.hash.split('#').slice(1)
|
|
550
|
+
const pathPart = hashSplit[0] ?? '/'
|
|
551
|
+
const searchPart = win.location.search
|
|
552
|
+
const hashEntries = hashSplit.slice(1)
|
|
553
|
+
const hashPart =
|
|
554
|
+
hashEntries.length === 0 ? '' : `#${hashEntries.join('#')}`
|
|
555
|
+
const hashHref = `${pathPart}${searchPart}${hashPart}`
|
|
339
556
|
return parseHref(hashHref, win.history.state)
|
|
340
557
|
},
|
|
341
558
|
createHref: (href) =>
|
|
@@ -345,34 +562,41 @@ export function createHashHistory(opts?: { window?: any }): RouterHistory {
|
|
|
345
562
|
|
|
346
563
|
export function createMemoryHistory(
|
|
347
564
|
opts: {
|
|
348
|
-
initialEntries: string
|
|
565
|
+
initialEntries: Array<string>
|
|
349
566
|
initialIndex?: number
|
|
350
567
|
} = {
|
|
351
568
|
initialEntries: ['/'],
|
|
352
569
|
},
|
|
353
570
|
): RouterHistory {
|
|
354
571
|
const entries = opts.initialEntries
|
|
355
|
-
let index = opts.initialIndex
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
572
|
+
let index = opts.initialIndex
|
|
573
|
+
? Math.min(Math.max(opts.initialIndex, 0), entries.length - 1)
|
|
574
|
+
: entries.length - 1
|
|
575
|
+
const states = entries.map((_entry, index) =>
|
|
576
|
+
assignKeyAndIndex(index, undefined),
|
|
577
|
+
)
|
|
359
578
|
|
|
360
|
-
const getLocation = () => parseHref(entries[index]!,
|
|
579
|
+
const getLocation = () => parseHref(entries[index]!, states[index])
|
|
361
580
|
|
|
362
581
|
return createHistory({
|
|
363
582
|
getLocation,
|
|
364
|
-
|
|
583
|
+
getLength: () => entries.length,
|
|
365
584
|
pushState: (path, state) => {
|
|
366
|
-
|
|
585
|
+
// Removes all subsequent entries after the current index to start a new branch
|
|
586
|
+
if (index < entries.length - 1) {
|
|
587
|
+
entries.splice(index + 1)
|
|
588
|
+
states.splice(index + 1)
|
|
589
|
+
}
|
|
590
|
+
states.push(state)
|
|
367
591
|
entries.push(path)
|
|
368
|
-
index
|
|
592
|
+
index = Math.max(entries.length - 1, 0)
|
|
369
593
|
},
|
|
370
594
|
replaceState: (path, state) => {
|
|
371
|
-
|
|
595
|
+
states[index] = state
|
|
372
596
|
entries[index] = path
|
|
373
597
|
},
|
|
374
598
|
back: () => {
|
|
375
|
-
index
|
|
599
|
+
index = Math.max(index - 1, 0)
|
|
376
600
|
},
|
|
377
601
|
forward: () => {
|
|
378
602
|
index = Math.min(index + 1, entries.length - 1)
|
|
@@ -384,9 +608,12 @@ export function createMemoryHistory(
|
|
|
384
608
|
})
|
|
385
609
|
}
|
|
386
610
|
|
|
387
|
-
function parseHref(
|
|
388
|
-
|
|
389
|
-
|
|
611
|
+
export function parseHref(
|
|
612
|
+
href: string,
|
|
613
|
+
state: ParsedHistoryState | undefined,
|
|
614
|
+
): HistoryLocation {
|
|
615
|
+
const hashIndex = href.indexOf('#')
|
|
616
|
+
const searchIndex = href.indexOf('?')
|
|
390
617
|
|
|
391
618
|
return {
|
|
392
619
|
href,
|
|
@@ -405,7 +632,7 @@ function parseHref(href: string, state: HistoryState): HistoryLocation {
|
|
|
405
632
|
searchIndex > -1
|
|
406
633
|
? href.slice(searchIndex, hashIndex === -1 ? undefined : hashIndex)
|
|
407
634
|
: '',
|
|
408
|
-
state: state || {},
|
|
635
|
+
state: state || { [stateIndexKey]: 0, key: createRandomKey() },
|
|
409
636
|
}
|
|
410
637
|
}
|
|
411
638
|
|