@tanstack/router-core 0.0.1-beta.45 → 0.0.1-beta.49
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/build/cjs/actions.js +94 -0
- package/build/cjs/actions.js.map +1 -0
- package/build/cjs/history.js +163 -0
- package/build/cjs/history.js.map +1 -0
- package/build/cjs/index.js +18 -20
- package/build/cjs/index.js.map +1 -1
- package/build/cjs/interop.js +175 -0
- package/build/cjs/interop.js.map +1 -0
- package/build/cjs/path.js +4 -5
- package/build/cjs/path.js.map +1 -1
- package/build/cjs/route.js +16 -138
- package/build/cjs/route.js.map +1 -1
- package/build/cjs/routeConfig.js +1 -7
- package/build/cjs/routeConfig.js.map +1 -1
- package/build/cjs/routeMatch.js +194 -199
- package/build/cjs/routeMatch.js.map +1 -1
- package/build/cjs/router.js +726 -703
- package/build/cjs/router.js.map +1 -1
- package/build/cjs/store.js +54 -0
- package/build/cjs/store.js.map +1 -0
- package/build/esm/index.js +1305 -1114
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +1 -1
- package/build/stats-react.json +229 -192
- package/build/types/index.d.ts +172 -109
- package/build/umd/index.development.js +1381 -2331
- package/build/umd/index.development.js.map +1 -1
- package/build/umd/index.production.js +1 -1
- package/build/umd/index.production.js.map +1 -1
- package/package.json +3 -3
- package/src/actions.ts +157 -0
- package/src/history.ts +199 -0
- package/src/index.ts +4 -7
- package/src/interop.ts +169 -0
- package/src/link.ts +2 -2
- package/src/route.ts +34 -239
- package/src/routeConfig.ts +3 -34
- package/src/routeInfo.ts +6 -21
- package/src/routeMatch.ts +270 -285
- package/src/router.ts +967 -963
- package/src/store.ts +52 -0
- package/build/cjs/sharedClone.js +0 -122
- package/build/cjs/sharedClone.js.map +0 -1
- package/src/sharedClone.ts +0 -118
package/build/esm/index.js
CHANGED
|
@@ -8,11 +8,154 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @license MIT
|
|
10
10
|
*/
|
|
11
|
-
import { createMemoryHistory, createBrowserHistory } from 'history';
|
|
12
|
-
export { createBrowserHistory, createHashHistory, createMemoryHistory } from 'history';
|
|
13
11
|
import invariant from 'tiny-invariant';
|
|
14
12
|
export { default as invariant } from 'tiny-invariant';
|
|
15
|
-
import {
|
|
13
|
+
import { setAutoFreeze, produce } from 'immer';
|
|
14
|
+
|
|
15
|
+
// While the public API was clearly inspired by the "history" npm package,
|
|
16
|
+
// This implementation attempts to be more lightweight by
|
|
17
|
+
// making assumptions about the way TanStack Router works
|
|
18
|
+
|
|
19
|
+
const popStateEvent = 'popstate';
|
|
20
|
+
function createHistory(opts) {
|
|
21
|
+
let currentLocation = opts.getLocation();
|
|
22
|
+
let unsub = () => {};
|
|
23
|
+
let listeners = new Set();
|
|
24
|
+
const onUpdate = () => {
|
|
25
|
+
currentLocation = opts.getLocation();
|
|
26
|
+
listeners.forEach(listener => listener());
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
get location() {
|
|
30
|
+
return currentLocation;
|
|
31
|
+
},
|
|
32
|
+
listen: cb => {
|
|
33
|
+
if (listeners.size === 0) {
|
|
34
|
+
unsub = opts.listener(onUpdate);
|
|
35
|
+
}
|
|
36
|
+
listeners.add(cb);
|
|
37
|
+
return () => {
|
|
38
|
+
listeners.delete(cb);
|
|
39
|
+
if (listeners.size === 0) {
|
|
40
|
+
unsub();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
push: (path, state) => {
|
|
45
|
+
opts.pushState(path, state);
|
|
46
|
+
onUpdate();
|
|
47
|
+
},
|
|
48
|
+
replace: (path, state) => {
|
|
49
|
+
opts.replaceState(path, state);
|
|
50
|
+
onUpdate();
|
|
51
|
+
},
|
|
52
|
+
go: index => {
|
|
53
|
+
opts.go(index);
|
|
54
|
+
onUpdate();
|
|
55
|
+
},
|
|
56
|
+
back: () => {
|
|
57
|
+
opts.back();
|
|
58
|
+
onUpdate();
|
|
59
|
+
},
|
|
60
|
+
forward: () => {
|
|
61
|
+
opts.forward();
|
|
62
|
+
onUpdate();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function createBrowserHistory(opts) {
|
|
67
|
+
const getHref = opts?.getHref ?? (() => `${window.location.pathname}${window.location.hash}${window.location.search}`);
|
|
68
|
+
const createHref = opts?.createHref ?? (path => path);
|
|
69
|
+
const getLocation = () => parseLocation(getHref(), history.state);
|
|
70
|
+
return createHistory({
|
|
71
|
+
getLocation,
|
|
72
|
+
listener: onUpdate => {
|
|
73
|
+
window.addEventListener(popStateEvent, onUpdate);
|
|
74
|
+
return () => {
|
|
75
|
+
window.removeEventListener(popStateEvent, onUpdate);
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
pushState: (path, state) => {
|
|
79
|
+
window.history.pushState({
|
|
80
|
+
...state,
|
|
81
|
+
key: createRandomKey()
|
|
82
|
+
}, '', createHref(path));
|
|
83
|
+
},
|
|
84
|
+
replaceState: (path, state) => {
|
|
85
|
+
window.history.replaceState({
|
|
86
|
+
...state,
|
|
87
|
+
key: createRandomKey()
|
|
88
|
+
}, '', createHref(path));
|
|
89
|
+
},
|
|
90
|
+
back: () => window.history.back(),
|
|
91
|
+
forward: () => window.history.forward(),
|
|
92
|
+
go: n => window.history.go(n)
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function createHashHistory() {
|
|
96
|
+
return createBrowserHistory({
|
|
97
|
+
getHref: () => window.location.hash.substring(1),
|
|
98
|
+
createHref: path => `#${path}`
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function createMemoryHistory(opts = {
|
|
102
|
+
initialEntries: ['/']
|
|
103
|
+
}) {
|
|
104
|
+
const entries = opts.initialEntries;
|
|
105
|
+
let index = opts.initialIndex ?? entries.length - 1;
|
|
106
|
+
let currentState = {};
|
|
107
|
+
const getLocation = () => parseLocation(entries[index], currentState);
|
|
108
|
+
return createHistory({
|
|
109
|
+
getLocation,
|
|
110
|
+
listener: onUpdate => {
|
|
111
|
+
window.addEventListener(popStateEvent, onUpdate);
|
|
112
|
+
// We might need to handle the hashchange event in the future
|
|
113
|
+
// window.addEventListener(hashChangeEvent, onUpdate)
|
|
114
|
+
return () => {
|
|
115
|
+
window.removeEventListener(popStateEvent, onUpdate);
|
|
116
|
+
};
|
|
117
|
+
},
|
|
118
|
+
pushState: (path, state) => {
|
|
119
|
+
currentState = {
|
|
120
|
+
...state,
|
|
121
|
+
key: createRandomKey()
|
|
122
|
+
};
|
|
123
|
+
entries.push(path);
|
|
124
|
+
index++;
|
|
125
|
+
},
|
|
126
|
+
replaceState: (path, state) => {
|
|
127
|
+
currentState = {
|
|
128
|
+
...state,
|
|
129
|
+
key: createRandomKey()
|
|
130
|
+
};
|
|
131
|
+
entries[index] = path;
|
|
132
|
+
},
|
|
133
|
+
back: () => {
|
|
134
|
+
index--;
|
|
135
|
+
},
|
|
136
|
+
forward: () => {
|
|
137
|
+
index = Math.min(index + 1, entries.length - 1);
|
|
138
|
+
},
|
|
139
|
+
go: n => window.history.go(n)
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function parseLocation(href, state) {
|
|
143
|
+
let hashIndex = href.indexOf('#');
|
|
144
|
+
let searchIndex = href.indexOf('?');
|
|
145
|
+
const pathEnd = Math.min(hashIndex, searchIndex);
|
|
146
|
+
return {
|
|
147
|
+
href,
|
|
148
|
+
pathname: pathEnd > -1 ? href.substring(0, pathEnd) : href,
|
|
149
|
+
hash: hashIndex > -1 ? href.substring(hashIndex, searchIndex) : '',
|
|
150
|
+
search: searchIndex > -1 ? href.substring(searchIndex) : '',
|
|
151
|
+
state
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Thanks co-pilot!
|
|
156
|
+
function createRandomKey() {
|
|
157
|
+
return (Math.random() + 1).toString(36).substring(7);
|
|
158
|
+
}
|
|
16
159
|
|
|
17
160
|
function last(arr) {
|
|
18
161
|
return arr[arr.length - 1];
|
|
@@ -73,9 +216,8 @@ function resolvePath(basepath, base, to) {
|
|
|
73
216
|
baseSegments.push(toSegment);
|
|
74
217
|
} else ;
|
|
75
218
|
} else if (toSegment.value === '..') {
|
|
76
|
-
var _last;
|
|
77
219
|
// Extra trailing slash? pop it off
|
|
78
|
-
if (baseSegments.length > 1 &&
|
|
220
|
+
if (baseSegments.length > 1 && last(baseSegments)?.value === '/') {
|
|
79
221
|
baseSegments.pop();
|
|
80
222
|
}
|
|
81
223
|
baseSegments.pop();
|
|
@@ -172,14 +314,14 @@ function matchByPath(basepath, from, matchLocation) {
|
|
|
172
314
|
const isLastBaseSegment = i === baseSegments.length - 1;
|
|
173
315
|
if (routeSegment) {
|
|
174
316
|
if (routeSegment.type === 'wildcard') {
|
|
175
|
-
if (baseSegment
|
|
317
|
+
if (baseSegment?.value) {
|
|
176
318
|
params['*'] = joinPaths(baseSegments.slice(i).map(d => d.value));
|
|
177
319
|
return true;
|
|
178
320
|
}
|
|
179
321
|
return false;
|
|
180
322
|
}
|
|
181
323
|
if (routeSegment.type === 'pathname') {
|
|
182
|
-
if (routeSegment.value === '/' && !
|
|
324
|
+
if (routeSegment.value === '/' && !baseSegment?.value) {
|
|
183
325
|
return true;
|
|
184
326
|
}
|
|
185
327
|
if (baseSegment) {
|
|
@@ -196,7 +338,7 @@ function matchByPath(basepath, from, matchLocation) {
|
|
|
196
338
|
return false;
|
|
197
339
|
}
|
|
198
340
|
if (routeSegment.type === 'param') {
|
|
199
|
-
if (
|
|
341
|
+
if (baseSegment?.value === '/') {
|
|
200
342
|
return false;
|
|
201
343
|
}
|
|
202
344
|
if (baseSegment.value.charAt(0) !== '$') {
|
|
@@ -262,151 +404,25 @@ function decode(str) {
|
|
|
262
404
|
return out;
|
|
263
405
|
}
|
|
264
406
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
router,
|
|
281
|
-
childRoutes: undefined,
|
|
282
|
-
parentRoute: parent,
|
|
283
|
-
get action() {
|
|
284
|
-
let action = router.store.actions[id] || (() => {
|
|
285
|
-
router.setStore(s => {
|
|
286
|
-
s.actions[id] = {
|
|
287
|
-
submissions: [],
|
|
288
|
-
submit: async (submission, actionOpts) => {
|
|
289
|
-
if (!route) {
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
const invalidate = (actionOpts == null ? void 0 : actionOpts.invalidate) ?? true;
|
|
293
|
-
const [actionStore, setActionStore] = createStore({
|
|
294
|
-
submittedAt: Date.now(),
|
|
295
|
-
status: 'pending',
|
|
296
|
-
submission,
|
|
297
|
-
isMulti: !!(actionOpts != null && actionOpts.multi)
|
|
298
|
-
});
|
|
299
|
-
router.setStore(s => {
|
|
300
|
-
if (!(actionOpts != null && actionOpts.multi)) {
|
|
301
|
-
s.actions[id].submissions = action.submissions.filter(d => d.isMulti);
|
|
302
|
-
}
|
|
303
|
-
s.actions[id].current = actionStore;
|
|
304
|
-
s.actions[id].latest = actionStore;
|
|
305
|
-
s.actions[id].submissions.push(actionStore);
|
|
306
|
-
});
|
|
307
|
-
try {
|
|
308
|
-
const res = await (route.options.action == null ? void 0 : route.options.action(submission));
|
|
309
|
-
setActionStore(s => {
|
|
310
|
-
s.data = res;
|
|
311
|
-
});
|
|
312
|
-
if (invalidate) {
|
|
313
|
-
router.invalidateRoute({
|
|
314
|
-
to: '.',
|
|
315
|
-
fromCurrent: true
|
|
316
|
-
});
|
|
317
|
-
await router.reload();
|
|
318
|
-
}
|
|
319
|
-
setActionStore(s => {
|
|
320
|
-
s.status = 'success';
|
|
321
|
-
});
|
|
322
|
-
return res;
|
|
323
|
-
} catch (err) {
|
|
324
|
-
console.error(err);
|
|
325
|
-
setActionStore(s => {
|
|
326
|
-
s.error = err;
|
|
327
|
-
s.status = 'error';
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
};
|
|
332
|
-
});
|
|
333
|
-
return router.store.actions[id];
|
|
334
|
-
})();
|
|
335
|
-
return action;
|
|
336
|
-
},
|
|
337
|
-
get loader() {
|
|
338
|
-
let loader = router.store.loaders[id] || (() => {
|
|
339
|
-
router.setStore(s => {
|
|
340
|
-
s.loaders[id] = {
|
|
341
|
-
pending: [],
|
|
342
|
-
fetch: async loaderContext => {
|
|
343
|
-
if (!route) {
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
const loaderState = {
|
|
347
|
-
loadedAt: Date.now(),
|
|
348
|
-
loaderContext
|
|
349
|
-
};
|
|
350
|
-
router.setStore(s => {
|
|
351
|
-
s.loaders[id].current = loaderState;
|
|
352
|
-
s.loaders[id].latest = loaderState;
|
|
353
|
-
s.loaders[id].pending.push(loaderState);
|
|
354
|
-
});
|
|
355
|
-
try {
|
|
356
|
-
return await (route.options.loader == null ? void 0 : route.options.loader(loaderContext));
|
|
357
|
-
} finally {
|
|
358
|
-
router.setStore(s => {
|
|
359
|
-
s.loaders[id].pending = s.loaders[id].pending.filter(d => d !== loaderState);
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
};
|
|
364
|
-
});
|
|
365
|
-
return router.store.loaders[id];
|
|
366
|
-
})();
|
|
367
|
-
return loader;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// buildLink: (options) => {
|
|
371
|
-
// return router.buildLink({
|
|
372
|
-
// ...options,
|
|
373
|
-
// from: fullPath,
|
|
374
|
-
// } as any) as any
|
|
375
|
-
// },
|
|
376
|
-
|
|
377
|
-
// navigate: (options) => {
|
|
378
|
-
// return router.navigate({
|
|
379
|
-
// ...options,
|
|
380
|
-
// from: fullPath,
|
|
381
|
-
// } as any) as any
|
|
382
|
-
// },
|
|
383
|
-
|
|
384
|
-
// matchRoute: (matchLocation, opts) => {
|
|
385
|
-
// return router.matchRoute(
|
|
386
|
-
// {
|
|
387
|
-
// ...matchLocation,
|
|
388
|
-
// from: fullPath,
|
|
389
|
-
// } as any,
|
|
390
|
-
// opts,
|
|
391
|
-
// ) as any
|
|
392
|
-
// },
|
|
393
|
-
};
|
|
394
|
-
|
|
395
|
-
router.options.createRoute == null ? void 0 : router.options.createRoute({
|
|
396
|
-
router,
|
|
397
|
-
route
|
|
398
|
-
});
|
|
399
|
-
return route;
|
|
407
|
+
class Route {
|
|
408
|
+
constructor(routeConfig, options, originalIndex, parent, router) {
|
|
409
|
+
Object.assign(this, {
|
|
410
|
+
...routeConfig,
|
|
411
|
+
originalIndex,
|
|
412
|
+
options,
|
|
413
|
+
getRouter: () => router,
|
|
414
|
+
childRoutes: undefined,
|
|
415
|
+
getParentRoute: () => parent
|
|
416
|
+
});
|
|
417
|
+
router.options.createRoute?.({
|
|
418
|
+
router,
|
|
419
|
+
route: this
|
|
420
|
+
});
|
|
421
|
+
}
|
|
400
422
|
}
|
|
401
423
|
|
|
402
424
|
const rootRouteId = '__root__';
|
|
403
|
-
const createRouteConfig =
|
|
404
|
-
if (options === void 0) {
|
|
405
|
-
options = {};
|
|
406
|
-
}
|
|
407
|
-
if (isRoot === void 0) {
|
|
408
|
-
isRoot = true;
|
|
409
|
-
}
|
|
425
|
+
const createRouteConfig = (options = {}, children = [], isRoot = true, parentId, parentPath) => {
|
|
410
426
|
if (isRoot) {
|
|
411
427
|
options.path = rootRouteId;
|
|
412
428
|
}
|
|
@@ -445,80 +461,153 @@ const createRouteConfig = function (options, children, isRoot, parentId, parentP
|
|
|
445
461
|
};
|
|
446
462
|
};
|
|
447
463
|
|
|
464
|
+
setAutoFreeze(false);
|
|
465
|
+
let queue = [];
|
|
466
|
+
let batching = false;
|
|
467
|
+
function flush() {
|
|
468
|
+
if (batching) return;
|
|
469
|
+
queue.forEach(cb => cb());
|
|
470
|
+
queue = [];
|
|
471
|
+
}
|
|
472
|
+
function createStore(initialState, debug) {
|
|
473
|
+
const listeners = new Set();
|
|
474
|
+
const store = {
|
|
475
|
+
state: initialState,
|
|
476
|
+
subscribe: listener => {
|
|
477
|
+
listeners.add(listener);
|
|
478
|
+
return () => listeners.delete(listener);
|
|
479
|
+
},
|
|
480
|
+
setState: updater => {
|
|
481
|
+
const previous = store.state;
|
|
482
|
+
store.state = produce(d => {
|
|
483
|
+
updater(d);
|
|
484
|
+
})(previous);
|
|
485
|
+
if (debug) console.log(store.state);
|
|
486
|
+
queue.push(() => listeners.forEach(listener => listener(store.state, previous)));
|
|
487
|
+
flush();
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
return store;
|
|
491
|
+
}
|
|
492
|
+
function batch(cb) {
|
|
493
|
+
batching = true;
|
|
494
|
+
cb();
|
|
495
|
+
batching = false;
|
|
496
|
+
flush();
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// /**
|
|
500
|
+
// * This function converts a store to an immutable value, which is
|
|
501
|
+
// * more complex than you think. On first read, (when prev is undefined)
|
|
502
|
+
// * every value must be recursively touched so tracking is "deep".
|
|
503
|
+
// * Every object/array structure must also be cloned to
|
|
504
|
+
// * have a new reference, otherwise it will get mutated by subsequent
|
|
505
|
+
// * store updates.
|
|
506
|
+
// *
|
|
507
|
+
// * In the case that prev is supplied, we have to do deep comparisons
|
|
508
|
+
// * between prev and next objects/array references and if they are deeply
|
|
509
|
+
// * equal, we can return the prev version for referential equality.
|
|
510
|
+
// */
|
|
511
|
+
// export function storeToImmutable<T>(prev: any, next: T): T {
|
|
512
|
+
// const cache = new Map()
|
|
513
|
+
|
|
514
|
+
// // Visit all nodes
|
|
515
|
+
// // clone all next structures
|
|
516
|
+
// // from bottom up, if prev === next, return prev
|
|
517
|
+
|
|
518
|
+
// function recurse(prev: any, next: any) {
|
|
519
|
+
// if (cache.has(next)) {
|
|
520
|
+
// return cache.get(next)
|
|
521
|
+
// }
|
|
522
|
+
|
|
523
|
+
// const prevIsArray = Array.isArray(prev)
|
|
524
|
+
// const nextIsArray = Array.isArray(next)
|
|
525
|
+
// const prevIsObj = isPlainObject(prev)
|
|
526
|
+
// const nextIsObj = isPlainObject(next)
|
|
527
|
+
// const nextIsComplex = nextIsArray || nextIsObj
|
|
528
|
+
|
|
529
|
+
// const isArray = prevIsArray && nextIsArray
|
|
530
|
+
// const isObj = prevIsObj && nextIsObj
|
|
531
|
+
|
|
532
|
+
// const isSameStructure = isArray || isObj
|
|
533
|
+
|
|
534
|
+
// if (nextIsComplex) {
|
|
535
|
+
// const prevSize = isArray
|
|
536
|
+
// ? prev.length
|
|
537
|
+
// : isObj
|
|
538
|
+
// ? Object.keys(prev).length
|
|
539
|
+
// : -1
|
|
540
|
+
// const nextKeys = isArray ? next : Object.keys(next)
|
|
541
|
+
// const nextSize = nextKeys.length
|
|
542
|
+
|
|
543
|
+
// let changed = false
|
|
544
|
+
// const copy: any = nextIsArray ? [] : {}
|
|
545
|
+
|
|
546
|
+
// for (let i = 0; i < nextSize; i++) {
|
|
547
|
+
// const key = isArray ? i : nextKeys[i]
|
|
548
|
+
// const prevValue = isSameStructure ? prev[key] : undefined
|
|
549
|
+
// const nextValue = next[key]
|
|
550
|
+
|
|
551
|
+
// // Recurse the new value
|
|
552
|
+
// try {
|
|
553
|
+
// console.count(key)
|
|
554
|
+
// copy[key] = recurse(prevValue, nextValue)
|
|
555
|
+
// } catch {}
|
|
556
|
+
|
|
557
|
+
// // If the new value has changed reference,
|
|
558
|
+
// // mark the obj/array as changed
|
|
559
|
+
// if (!changed && copy[key] !== prevValue) {
|
|
560
|
+
// changed = true
|
|
561
|
+
// }
|
|
562
|
+
// }
|
|
563
|
+
|
|
564
|
+
// // No items have changed!
|
|
565
|
+
// // If something has changed, return a clone of the next obj/array
|
|
566
|
+
// if (changed || prevSize !== nextSize) {
|
|
567
|
+
// cache.set(next, copy)
|
|
568
|
+
// return copy
|
|
569
|
+
// }
|
|
570
|
+
|
|
571
|
+
// // If they are exactly the same, return the prev obj/array
|
|
572
|
+
// cache.set(next, prev)
|
|
573
|
+
// return prev
|
|
574
|
+
// }
|
|
575
|
+
|
|
576
|
+
// cache.set(next, next)
|
|
577
|
+
// return next
|
|
578
|
+
// }
|
|
579
|
+
|
|
580
|
+
// return recurse(prev, next)
|
|
581
|
+
// }
|
|
582
|
+
|
|
448
583
|
/**
|
|
449
584
|
* This function returns `a` if `b` is deeply equal.
|
|
450
585
|
* If not, it will replace any deeply equal children of `b` with those of `a`.
|
|
451
|
-
* This can be used for structural sharing between JSON values for example.
|
|
586
|
+
* This can be used for structural sharing between immutable JSON values for example.
|
|
587
|
+
* Do not use this with signals
|
|
452
588
|
*/
|
|
453
|
-
function
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
const
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
// Both are arrays or objects
|
|
471
|
-
if (isSameStructure) {
|
|
472
|
-
const aSize = isArray ? prev.length : Object.keys(prev).length;
|
|
473
|
-
const bItems = isArray ? next : Object.keys(next);
|
|
474
|
-
const bSize = bItems.length;
|
|
475
|
-
const copy = isArray ? [] : {};
|
|
476
|
-
let equalItems = 0;
|
|
477
|
-
for (let i = 0; i < bSize; i++) {
|
|
478
|
-
const key = isArray ? i : bItems[i];
|
|
479
|
-
if (copy[key] === prev[key]) {
|
|
480
|
-
equalItems++;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
if (aSize === bSize && equalItems === aSize) {
|
|
484
|
-
things.set(next, prev);
|
|
485
|
-
return prev;
|
|
486
|
-
}
|
|
487
|
-
things.set(next, copy);
|
|
488
|
-
for (let i = 0; i < bSize; i++) {
|
|
489
|
-
const key = isArray ? i : bItems[i];
|
|
490
|
-
if (typeof bItems[i] === 'function') {
|
|
491
|
-
copy[key] = prev[key];
|
|
492
|
-
} else {
|
|
493
|
-
copy[key] = recurse(prev[key], next[key]);
|
|
494
|
-
}
|
|
495
|
-
if (copy[key] === prev[key]) {
|
|
496
|
-
equalItems++;
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
return copy;
|
|
500
|
-
}
|
|
501
|
-
if (nextIsArray) {
|
|
502
|
-
const copy = [];
|
|
503
|
-
things.set(next, copy);
|
|
504
|
-
for (let i = 0; i < next.length; i++) {
|
|
505
|
-
copy[i] = recurse(undefined, next[i]);
|
|
506
|
-
}
|
|
507
|
-
return copy;
|
|
508
|
-
}
|
|
509
|
-
if (nextIsObj) {
|
|
510
|
-
const copy = {};
|
|
511
|
-
things.set(next, copy);
|
|
512
|
-
const nextKeys = Object.keys(next);
|
|
513
|
-
for (let i = 0; i < nextKeys.length; i++) {
|
|
514
|
-
const key = nextKeys[i];
|
|
515
|
-
copy[key] = recurse(undefined, next[key]);
|
|
589
|
+
function replaceEqualDeep(prev, _next) {
|
|
590
|
+
if (prev === _next) {
|
|
591
|
+
return prev;
|
|
592
|
+
}
|
|
593
|
+
const next = _next;
|
|
594
|
+
const array = Array.isArray(prev) && Array.isArray(next);
|
|
595
|
+
if (array || isPlainObject(prev) && isPlainObject(next)) {
|
|
596
|
+
const prevSize = array ? prev.length : Object.keys(prev).length;
|
|
597
|
+
const nextItems = array ? next : Object.keys(next);
|
|
598
|
+
const nextSize = nextItems.length;
|
|
599
|
+
const copy = array ? [] : {};
|
|
600
|
+
let equalItems = 0;
|
|
601
|
+
for (let i = 0; i < nextSize; i++) {
|
|
602
|
+
const key = array ? i : nextItems[i];
|
|
603
|
+
copy[key] = replaceEqualDeep(prev[key], next[key]);
|
|
604
|
+
if (copy[key] === prev[key]) {
|
|
605
|
+
equalItems++;
|
|
516
606
|
}
|
|
517
|
-
return copy;
|
|
518
607
|
}
|
|
519
|
-
return
|
|
608
|
+
return prevSize === nextSize && equalItems === prevSize ? prev : copy;
|
|
520
609
|
}
|
|
521
|
-
return
|
|
610
|
+
return next;
|
|
522
611
|
}
|
|
523
612
|
|
|
524
613
|
// Copied from: https://github.com/jonschlinkert/is-plain-object
|
|
@@ -550,228 +639,237 @@ function isPlainObject(o) {
|
|
|
550
639
|
function hasObjectPrototype(o) {
|
|
551
640
|
return Object.prototype.toString.call(o) === '[object Object]';
|
|
552
641
|
}
|
|
642
|
+
function trackDeep(obj) {
|
|
643
|
+
const seen = new Set();
|
|
644
|
+
JSON.stringify(obj, (_, value) => {
|
|
645
|
+
if (typeof value === 'function') {
|
|
646
|
+
return undefined;
|
|
647
|
+
}
|
|
648
|
+
if (typeof value === 'object' && value !== null) {
|
|
649
|
+
if (seen.has(value)) return;
|
|
650
|
+
seen.add(value);
|
|
651
|
+
}
|
|
652
|
+
return value;
|
|
653
|
+
});
|
|
654
|
+
return obj;
|
|
655
|
+
}
|
|
553
656
|
|
|
554
657
|
const componentTypes = ['component', 'errorComponent', 'pendingComponent'];
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
658
|
+
class RouteMatch {
|
|
659
|
+
abortController = new AbortController();
|
|
660
|
+
#latestId = '';
|
|
661
|
+
#resolve = () => {};
|
|
662
|
+
onLoaderDataListeners = new Set();
|
|
663
|
+
constructor(router, route, opts) {
|
|
664
|
+
Object.assign(this, {
|
|
665
|
+
route,
|
|
666
|
+
router,
|
|
667
|
+
matchId: opts.matchId,
|
|
668
|
+
pathname: opts.pathname,
|
|
669
|
+
params: opts.params,
|
|
670
|
+
store: createStore({
|
|
671
|
+
routeSearch: {},
|
|
672
|
+
search: {},
|
|
673
|
+
status: 'idle',
|
|
674
|
+
routeLoaderData: {},
|
|
675
|
+
loaderData: {},
|
|
676
|
+
isFetching: false,
|
|
677
|
+
invalid: false,
|
|
678
|
+
invalidAt: Infinity
|
|
679
|
+
})
|
|
566
680
|
});
|
|
681
|
+
if (!this.__hasLoaders()) {
|
|
682
|
+
this.store.setState(s => s.status = 'success');
|
|
683
|
+
}
|
|
567
684
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
...((_store$parentMatch = store.parentMatch) == null ? void 0 : _store$parentMatch.store.loaderData),
|
|
573
|
-
...s.routeLoaderData
|
|
685
|
+
#setLoaderData = loaderData => {
|
|
686
|
+
batch(() => {
|
|
687
|
+
this.store.setState(s => {
|
|
688
|
+
s.routeLoaderData = loaderData;
|
|
574
689
|
});
|
|
690
|
+
this.#updateLoaderData();
|
|
575
691
|
});
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
isFetching: false,
|
|
584
|
-
invalid: false,
|
|
585
|
-
invalidAt: Infinity,
|
|
586
|
-
get isInvalid() {
|
|
587
|
-
const now = Date.now();
|
|
588
|
-
return this.invalid || this.invalidAt < now;
|
|
589
|
-
}
|
|
590
|
-
});
|
|
591
|
-
const routeMatch = {
|
|
592
|
-
...route,
|
|
593
|
-
...opts,
|
|
594
|
-
store,
|
|
595
|
-
// setStore,
|
|
596
|
-
router,
|
|
597
|
-
childMatches: [],
|
|
598
|
-
__: {
|
|
599
|
-
setParentMatch: parentMatch => {
|
|
600
|
-
batch(() => {
|
|
601
|
-
setStore(s => {
|
|
602
|
-
s.parentMatch = parentMatch;
|
|
603
|
-
});
|
|
604
|
-
updateLoaderData();
|
|
605
|
-
});
|
|
606
|
-
},
|
|
607
|
-
abortController: new AbortController(),
|
|
608
|
-
validate: () => {
|
|
609
|
-
var _store$parentMatch2;
|
|
610
|
-
// Validate the search params and stabilize them
|
|
611
|
-
const parentSearch = ((_store$parentMatch2 = store.parentMatch) == null ? void 0 : _store$parentMatch2.store.search) ?? router.store.currentLocation.search;
|
|
612
|
-
try {
|
|
613
|
-
const prevSearch = store.routeSearch;
|
|
614
|
-
const validator = typeof routeMatch.options.validateSearch === 'object' ? routeMatch.options.validateSearch.parse : routeMatch.options.validateSearch;
|
|
615
|
-
let nextSearch = sharedClone(prevSearch, (validator == null ? void 0 : validator(parentSearch)) ?? {});
|
|
616
|
-
batch(() => {
|
|
617
|
-
// Invalidate route matches when search param stability changes
|
|
618
|
-
if (prevSearch !== nextSearch) {
|
|
619
|
-
setStore(s => s.invalid = true);
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// TODO: Alright, do we need batch() here?
|
|
623
|
-
setStore(s => {
|
|
624
|
-
s.routeSearch = nextSearch;
|
|
625
|
-
s.search = sharedClone(parentSearch, {
|
|
626
|
-
...parentSearch,
|
|
627
|
-
...nextSearch
|
|
628
|
-
});
|
|
629
|
-
});
|
|
630
|
-
});
|
|
631
|
-
componentTypes.map(async type => {
|
|
632
|
-
const component = routeMatch.options[type];
|
|
633
|
-
if (typeof routeMatch.__[type] !== 'function') {
|
|
634
|
-
routeMatch.__[type] = component;
|
|
635
|
-
}
|
|
636
|
-
});
|
|
637
|
-
} catch (err) {
|
|
638
|
-
console.error(err);
|
|
639
|
-
const error = new Error('Invalid search params found', {
|
|
640
|
-
cause: err
|
|
641
|
-
});
|
|
642
|
-
error.code = 'INVALID_SEARCH_PARAMS';
|
|
643
|
-
setStore(s => {
|
|
644
|
-
s.status = 'error';
|
|
645
|
-
s.error = error;
|
|
646
|
-
});
|
|
692
|
+
};
|
|
693
|
+
cancel = () => {
|
|
694
|
+
this.abortController?.abort();
|
|
695
|
+
};
|
|
696
|
+
load = async loaderOpts => {
|
|
697
|
+
const now = Date.now();
|
|
698
|
+
const minMaxAge = loaderOpts?.preload ? Math.max(loaderOpts?.maxAge, loaderOpts?.gcMaxAge) : 0;
|
|
647
699
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
700
|
+
// If this is a preload, add it to the preload cache
|
|
701
|
+
if (loaderOpts?.preload && minMaxAge > 0) {
|
|
702
|
+
// If the match is currently active, don't preload it
|
|
703
|
+
if (this.router.store.state.currentMatches.find(d => d.id === this.id)) {
|
|
704
|
+
return;
|
|
651
705
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
var _routeMatch$__$abortC;
|
|
655
|
-
(_routeMatch$__$abortC = routeMatch.__.abortController) == null ? void 0 : _routeMatch$__$abortC.abort();
|
|
656
|
-
},
|
|
657
|
-
invalidate: () => {
|
|
658
|
-
setStore(s => s.invalid = true);
|
|
659
|
-
},
|
|
660
|
-
hasLoaders: () => {
|
|
661
|
-
return !!(route.options.loader || componentTypes.some(d => {
|
|
662
|
-
var _route$options$d;
|
|
663
|
-
return (_route$options$d = route.options[d]) == null ? void 0 : _route$options$d.preload;
|
|
664
|
-
}));
|
|
665
|
-
},
|
|
666
|
-
load: async loaderOpts => {
|
|
667
|
-
const now = Date.now();
|
|
668
|
-
const minMaxAge = loaderOpts != null && loaderOpts.preload ? Math.max(loaderOpts == null ? void 0 : loaderOpts.maxAge, loaderOpts == null ? void 0 : loaderOpts.gcMaxAge) : 0;
|
|
669
|
-
|
|
670
|
-
// If this is a preload, add it to the preload cache
|
|
671
|
-
if (loaderOpts != null && loaderOpts.preload && minMaxAge > 0) {
|
|
672
|
-
// If the match is currently active, don't preload it
|
|
673
|
-
if (router.store.currentMatches.find(d => d.matchId === routeMatch.matchId)) {
|
|
674
|
-
return;
|
|
675
|
-
}
|
|
676
|
-
router.store.matchCache[routeMatch.matchId] = {
|
|
706
|
+
this.router.store.setState(s => {
|
|
707
|
+
s.matchCache[this.id] = {
|
|
677
708
|
gc: now + loaderOpts.gcMaxAge,
|
|
678
|
-
match:
|
|
709
|
+
match: this
|
|
679
710
|
};
|
|
680
|
-
}
|
|
711
|
+
});
|
|
712
|
+
}
|
|
681
713
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
714
|
+
// If the match is invalid, errored or idle, trigger it to load
|
|
715
|
+
if (this.store.state.status === 'success' && this.getIsInvalid() || this.store.state.status === 'error' || this.store.state.status === 'idle') {
|
|
716
|
+
const maxAge = loaderOpts?.preload ? loaderOpts?.maxAge : undefined;
|
|
717
|
+
await this.fetch({
|
|
718
|
+
maxAge
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
fetch = async opts => {
|
|
723
|
+
this.__loadPromise = new Promise(async resolve => {
|
|
691
724
|
const loadId = '' + Date.now() + Math.random();
|
|
692
|
-
latestId = loadId;
|
|
693
|
-
const checkLatest =
|
|
694
|
-
|
|
695
|
-
// warning(true, 'Data loader is out of date!')
|
|
696
|
-
return new Promise(() => {});
|
|
697
|
-
}
|
|
698
|
-
};
|
|
725
|
+
this.#latestId = loadId;
|
|
726
|
+
const checkLatest = () => loadId !== this.#latestId ? this.__loadPromise?.then(() => resolve()) : undefined;
|
|
727
|
+
let latestPromise;
|
|
699
728
|
batch(() => {
|
|
700
729
|
// If the match was in an error state, set it
|
|
701
730
|
// to a loading state again. Otherwise, keep it
|
|
702
731
|
// as loading or resolved
|
|
703
|
-
if (store.status === 'idle') {
|
|
704
|
-
|
|
732
|
+
if (this.store.state.status === 'idle') {
|
|
733
|
+
this.store.setState(s => s.status = 'loading');
|
|
705
734
|
}
|
|
706
735
|
|
|
707
736
|
// We started loading the route, so it's no longer invalid
|
|
708
|
-
|
|
737
|
+
this.store.setState(s => s.invalid = false);
|
|
709
738
|
});
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
routeMatch.__[type] = await router.options.loadComponent(component);
|
|
724
|
-
}
|
|
725
|
-
}));
|
|
726
|
-
})();
|
|
727
|
-
dataPromise = Promise.resolve().then(async () => {
|
|
728
|
-
try {
|
|
729
|
-
if (routeMatch.options.loader) {
|
|
730
|
-
const data = await router.loadMatchData(routeMatch);
|
|
731
|
-
await checkLatest();
|
|
732
|
-
setLoaderData(data);
|
|
733
|
-
}
|
|
734
|
-
setStore(s => {
|
|
735
|
-
s.error = undefined;
|
|
736
|
-
s.status = 'success';
|
|
737
|
-
s.updatedAt = Date.now();
|
|
738
|
-
s.invalidAt = s.updatedAt + ((opts == null ? void 0 : opts.maxAge) ?? routeMatch.options.loaderMaxAge ?? router.options.defaultLoaderMaxAge ?? 0);
|
|
739
|
-
});
|
|
740
|
-
return store.routeLoaderData;
|
|
741
|
-
} catch (err) {
|
|
742
|
-
await checkLatest();
|
|
743
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
744
|
-
console.error(err);
|
|
745
|
-
}
|
|
746
|
-
setStore(s => {
|
|
747
|
-
s.error = err;
|
|
748
|
-
s.status = 'error';
|
|
749
|
-
s.updatedAt = Date.now();
|
|
750
|
-
});
|
|
751
|
-
throw err;
|
|
739
|
+
|
|
740
|
+
// We are now fetching, even if it's in the background of a
|
|
741
|
+
// resolved state
|
|
742
|
+
this.store.setState(s => s.isFetching = true);
|
|
743
|
+
this.#resolve = resolve;
|
|
744
|
+
const componentsPromise = (async () => {
|
|
745
|
+
// then run all component and data loaders in parallel
|
|
746
|
+
// For each component type, potentially load it asynchronously
|
|
747
|
+
|
|
748
|
+
await Promise.all(componentTypes.map(async type => {
|
|
749
|
+
const component = this.route.options[type];
|
|
750
|
+
if (this[type]?.preload) {
|
|
751
|
+
this[type] = await this.router.options.loadComponent(component);
|
|
752
752
|
}
|
|
753
|
-
});
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
setStore(s => s.isFetching = false);
|
|
757
|
-
delete routeMatch.__.loadPromise;
|
|
758
|
-
resolve();
|
|
759
|
-
};
|
|
753
|
+
}));
|
|
754
|
+
})();
|
|
755
|
+
const dataPromise = Promise.resolve().then(async () => {
|
|
760
756
|
try {
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
757
|
+
if (this.route.options.loader) {
|
|
758
|
+
const data = await this.router.loadMatchData(this);
|
|
759
|
+
if (latestPromise = checkLatest()) return latestPromise;
|
|
760
|
+
this.#setLoaderData(data);
|
|
761
|
+
}
|
|
762
|
+
this.store.setState(s => {
|
|
763
|
+
s.error = undefined;
|
|
764
|
+
s.status = 'success';
|
|
765
|
+
s.updatedAt = Date.now();
|
|
766
|
+
s.invalidAt = s.updatedAt + (opts?.maxAge ?? this.route.options.loaderMaxAge ?? this.router.options.defaultLoaderMaxAge ?? 0);
|
|
767
|
+
});
|
|
768
|
+
return this.store.state.routeLoaderData;
|
|
769
|
+
} catch (err) {
|
|
770
|
+
if (latestPromise = checkLatest()) return latestPromise;
|
|
771
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
772
|
+
console.error(err);
|
|
773
|
+
}
|
|
774
|
+
this.store.setState(s => {
|
|
775
|
+
s.error = err;
|
|
776
|
+
s.status = 'error';
|
|
777
|
+
s.updatedAt = Date.now();
|
|
778
|
+
});
|
|
779
|
+
throw err;
|
|
765
780
|
}
|
|
766
781
|
});
|
|
767
|
-
|
|
768
|
-
|
|
782
|
+
const after = async () => {
|
|
783
|
+
if (latestPromise = checkLatest()) return latestPromise;
|
|
784
|
+
this.store.setState(s => s.isFetching = false);
|
|
785
|
+
this.#resolve();
|
|
786
|
+
delete this.__loadPromise;
|
|
787
|
+
};
|
|
788
|
+
try {
|
|
789
|
+
await Promise.all([componentsPromise, dataPromise.catch(() => {})]);
|
|
790
|
+
after();
|
|
791
|
+
} catch {
|
|
792
|
+
after();
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
return this.__loadPromise;
|
|
796
|
+
};
|
|
797
|
+
invalidate = async () => {
|
|
798
|
+
this.store.setState(s => s.invalid = true);
|
|
799
|
+
if (this.router.store.state.currentMatches.find(d => d.id === this.id)) {
|
|
800
|
+
await this.load();
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
__hasLoaders = () => {
|
|
804
|
+
return !!(this.route.options.loader || componentTypes.some(d => this.route.options[d]?.preload));
|
|
805
|
+
};
|
|
806
|
+
getIsInvalid = () => {
|
|
807
|
+
const now = Date.now();
|
|
808
|
+
return this.store.state.invalid || this.store.state.invalidAt < now;
|
|
809
|
+
};
|
|
810
|
+
#updateLoaderData = () => {
|
|
811
|
+
this.store.setState(s => {
|
|
812
|
+
s.loaderData = replaceEqualDeep(s.loaderData, {
|
|
813
|
+
...this.parentMatch?.store.state.loaderData,
|
|
814
|
+
...s.routeLoaderData
|
|
815
|
+
});
|
|
816
|
+
});
|
|
817
|
+
this.onLoaderDataListeners.forEach(listener => listener());
|
|
818
|
+
};
|
|
819
|
+
__setParentMatch = parentMatch => {
|
|
820
|
+
if (!this.parentMatch && parentMatch) {
|
|
821
|
+
this.parentMatch = parentMatch;
|
|
822
|
+
this.parentMatch.__onLoaderData(() => {
|
|
823
|
+
this.#updateLoaderData();
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
__onLoaderData = listener => {
|
|
828
|
+
this.onLoaderDataListeners.add(listener);
|
|
829
|
+
// return () => this.onLoaderDataListeners.delete(listener)
|
|
830
|
+
};
|
|
831
|
+
|
|
832
|
+
__validate = () => {
|
|
833
|
+
// Validate the search params and stabilize them
|
|
834
|
+
const parentSearch = this.parentMatch?.store.state.search ?? this.router.store.state.latestLocation.search;
|
|
835
|
+
try {
|
|
836
|
+
const prevSearch = this.store.state.routeSearch;
|
|
837
|
+
const validator = typeof this.route.options.validateSearch === 'object' ? this.route.options.validateSearch.parse : this.route.options.validateSearch;
|
|
838
|
+
let nextSearch = validator?.(parentSearch) ?? {};
|
|
839
|
+
batch(() => {
|
|
840
|
+
// Invalidate route matches when search param stability changes
|
|
841
|
+
if (prevSearch !== nextSearch) {
|
|
842
|
+
this.store.setState(s => s.invalid = true);
|
|
843
|
+
}
|
|
844
|
+
this.store.setState(s => {
|
|
845
|
+
s.routeSearch = nextSearch;
|
|
846
|
+
s.search = {
|
|
847
|
+
...parentSearch,
|
|
848
|
+
...nextSearch
|
|
849
|
+
};
|
|
850
|
+
});
|
|
851
|
+
});
|
|
852
|
+
componentTypes.map(async type => {
|
|
853
|
+
const component = this.route.options[type];
|
|
854
|
+
if (typeof this[type] !== 'function') {
|
|
855
|
+
this[type] = component;
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
} catch (err) {
|
|
859
|
+
console.error(err);
|
|
860
|
+
const error = new Error('Invalid search params found', {
|
|
861
|
+
cause: err
|
|
862
|
+
});
|
|
863
|
+
error.code = 'INVALID_SEARCH_PARAMS';
|
|
864
|
+
this.store.setState(s => {
|
|
865
|
+
s.status = 'error';
|
|
866
|
+
s.error = error;
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
// Do not proceed with loading the route
|
|
870
|
+
return;
|
|
769
871
|
}
|
|
770
872
|
};
|
|
771
|
-
if (!routeMatch.hasLoaders()) {
|
|
772
|
-
setStore(s => s.status = 'success');
|
|
773
|
-
}
|
|
774
|
-
return routeMatch;
|
|
775
873
|
}
|
|
776
874
|
|
|
777
875
|
const defaultParseSearch = parseSearchWith(JSON.parse);
|
|
@@ -821,768 +919,792 @@ function stringifySearchWith(stringify) {
|
|
|
821
919
|
};
|
|
822
920
|
}
|
|
823
921
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
922
|
+
const defaultFetchServerDataFn = async ({
|
|
923
|
+
router,
|
|
924
|
+
routeMatch
|
|
925
|
+
}) => {
|
|
926
|
+
const next = router.buildNext({
|
|
927
|
+
to: '.',
|
|
928
|
+
search: d => ({
|
|
929
|
+
...(d ?? {}),
|
|
930
|
+
__data: {
|
|
931
|
+
matchId: routeMatch.id
|
|
932
|
+
}
|
|
933
|
+
})
|
|
934
|
+
});
|
|
935
|
+
const res = await fetch(next.href, {
|
|
936
|
+
method: 'GET',
|
|
937
|
+
signal: routeMatch.abortController.signal
|
|
938
|
+
});
|
|
939
|
+
if (res.ok) {
|
|
940
|
+
return res.json();
|
|
941
|
+
}
|
|
942
|
+
throw new Error('Failed to fetch match data');
|
|
943
|
+
};
|
|
944
|
+
class Router {
|
|
945
|
+
// __location: Location<TAllRouteInfo['fullSearchSchema']>
|
|
827
946
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
const originalOptions = {
|
|
850
|
-
defaultLoaderGcMaxAge: 5 * 60 * 1000,
|
|
851
|
-
defaultLoaderMaxAge: 0,
|
|
852
|
-
defaultPreloadMaxAge: 2000,
|
|
853
|
-
defaultPreloadDelay: 50,
|
|
854
|
-
context: undefined,
|
|
855
|
-
...userOptions,
|
|
856
|
-
stringifySearch: (userOptions == null ? void 0 : userOptions.stringifySearch) ?? defaultStringifySearch,
|
|
857
|
-
parseSearch: (userOptions == null ? void 0 : userOptions.parseSearch) ?? defaultParseSearch
|
|
858
|
-
};
|
|
859
|
-
const [store, setStore] = createStore(getInitialRouterState());
|
|
860
|
-
let navigationPromise;
|
|
861
|
-
let startedLoadingAt = Date.now();
|
|
862
|
-
let resolveNavigation = () => {};
|
|
863
|
-
function onFocus() {
|
|
864
|
-
router.load();
|
|
947
|
+
startedLoadingAt = Date.now();
|
|
948
|
+
resolveNavigation = () => {};
|
|
949
|
+
constructor(options) {
|
|
950
|
+
this.options = {
|
|
951
|
+
defaultLoaderGcMaxAge: 5 * 60 * 1000,
|
|
952
|
+
defaultLoaderMaxAge: 0,
|
|
953
|
+
defaultPreloadMaxAge: 2000,
|
|
954
|
+
defaultPreloadDelay: 50,
|
|
955
|
+
context: undefined,
|
|
956
|
+
...options,
|
|
957
|
+
stringifySearch: options?.stringifySearch ?? defaultStringifySearch,
|
|
958
|
+
parseSearch: options?.parseSearch ?? defaultParseSearch,
|
|
959
|
+
fetchServerDataFn: options?.fetchServerDataFn ?? defaultFetchServerDataFn
|
|
960
|
+
};
|
|
961
|
+
this.history = this.options?.history ?? isServer ? createMemoryHistory() : createBrowserHistory();
|
|
962
|
+
this.store = createStore(getInitialRouterState());
|
|
963
|
+
this.basepath = '';
|
|
964
|
+
this.update(options);
|
|
965
|
+
|
|
966
|
+
// Allow frameworks to hook into the router creation
|
|
967
|
+
this.options.createRouter?.(this);
|
|
865
968
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
969
|
+
reset = () => {
|
|
970
|
+
this.store.setState(s => Object.assign(s, getInitialRouterState()));
|
|
971
|
+
};
|
|
972
|
+
mount = () => {
|
|
973
|
+
// Mount only does anything on the client
|
|
974
|
+
if (!isServer) {
|
|
975
|
+
// If the router matches are empty, load the matches
|
|
976
|
+
if (!this.store.state.currentMatches.length) {
|
|
977
|
+
this.load();
|
|
978
|
+
}
|
|
979
|
+
const unsubHistory = this.history.listen(() => {
|
|
980
|
+
this.load(this.#parseLocation(this.store.state.latestLocation));
|
|
981
|
+
});
|
|
982
|
+
const visibilityChangeEvent = 'visibilitychange';
|
|
983
|
+
const focusEvent = 'focus';
|
|
984
|
+
|
|
985
|
+
// addEventListener does not exist in React Native, but window does
|
|
986
|
+
// In the future, we might need to invert control here for more adapters
|
|
987
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
988
|
+
if (window.addEventListener) {
|
|
989
|
+
// Listen to visibilitychange and focus
|
|
990
|
+
window.addEventListener(visibilityChangeEvent, this.#onFocus, false);
|
|
991
|
+
window.addEventListener(focusEvent, this.#onFocus, false);
|
|
992
|
+
}
|
|
993
|
+
return () => {
|
|
994
|
+
unsubHistory();
|
|
995
|
+
if (window.removeEventListener) {
|
|
996
|
+
// Be sure to unsubscribe if a new handler is set
|
|
997
|
+
|
|
998
|
+
window.removeEventListener(visibilityChangeEvent, this.#onFocus);
|
|
999
|
+
window.removeEventListener(focusEvent, this.#onFocus);
|
|
877
1000
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
return () => {};
|
|
1004
|
+
};
|
|
1005
|
+
update = opts => {
|
|
1006
|
+
if (!this.store.state.latestLocation) {
|
|
1007
|
+
this.store.setState(s => {
|
|
1008
|
+
s.latestLocation = this.#parseLocation();
|
|
1009
|
+
s.currentLocation = s.latestLocation;
|
|
882
1010
|
});
|
|
883
|
-
};
|
|
884
|
-
const routes = recurseRoutes([rootRouteConfig]);
|
|
885
|
-
return routes[0];
|
|
886
|
-
}
|
|
887
|
-
function parseLocation(location, previousLocation) {
|
|
888
|
-
const parsedSearch = router.options.parseSearch(location.search);
|
|
889
|
-
return {
|
|
890
|
-
pathname: location.pathname,
|
|
891
|
-
searchStr: location.search,
|
|
892
|
-
search: sharedClone(previousLocation == null ? void 0 : previousLocation.search, parsedSearch),
|
|
893
|
-
hash: location.hash.split('#').reverse()[0] ?? '',
|
|
894
|
-
href: `${location.pathname}${location.search}${location.hash}`,
|
|
895
|
-
state: location.state,
|
|
896
|
-
key: location.key
|
|
897
|
-
};
|
|
898
|
-
}
|
|
899
|
-
function navigate(location) {
|
|
900
|
-
const next = router.buildNext(location);
|
|
901
|
-
return commitLocation(next, location.replace);
|
|
902
|
-
}
|
|
903
|
-
function buildLocation(dest) {
|
|
904
|
-
var _last, _dest$__preSearchFilt, _dest$__preSearchFilt2, _dest$__postSearchFil;
|
|
905
|
-
if (dest === void 0) {
|
|
906
|
-
dest = {};
|
|
907
1011
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1012
|
+
Object.assign(this.options, opts);
|
|
1013
|
+
const {
|
|
1014
|
+
basepath,
|
|
1015
|
+
routeConfig
|
|
1016
|
+
} = this.options;
|
|
1017
|
+
this.basepath = `/${trimPath(basepath ?? '') ?? ''}`;
|
|
1018
|
+
if (routeConfig) {
|
|
1019
|
+
this.routesById = {};
|
|
1020
|
+
this.routeTree = this.#buildRouteTree(routeConfig);
|
|
1021
|
+
}
|
|
1022
|
+
return this;
|
|
1023
|
+
};
|
|
1024
|
+
buildNext = opts => {
|
|
1025
|
+
const next = this.#buildLocation(opts);
|
|
1026
|
+
const matches = this.matchRoutes(next.pathname);
|
|
1027
|
+
const __preSearchFilters = matches.map(match => match.route.options.preSearchFilters ?? []).flat().filter(Boolean);
|
|
1028
|
+
const __postSearchFilters = matches.map(match => match.route.options.postSearchFilters ?? []).flat().filter(Boolean);
|
|
1029
|
+
return this.#buildLocation({
|
|
1030
|
+
...opts,
|
|
1031
|
+
__preSearchFilters,
|
|
1032
|
+
__postSearchFilters
|
|
912
1033
|
});
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
pathname = interpolatePath(pathname, nextParams ?? {});
|
|
1034
|
+
};
|
|
1035
|
+
cancelMatches = () => {
|
|
1036
|
+
[...this.store.state.currentMatches, ...(this.store.state.pendingMatches || [])].forEach(match => {
|
|
1037
|
+
match.cancel();
|
|
1038
|
+
});
|
|
1039
|
+
};
|
|
1040
|
+
load = async next => {
|
|
1041
|
+
let now = Date.now();
|
|
1042
|
+
const startedAt = now;
|
|
1043
|
+
this.startedLoadingAt = startedAt;
|
|
924
1044
|
|
|
925
|
-
//
|
|
926
|
-
|
|
1045
|
+
// Cancel any pending matches
|
|
1046
|
+
this.cancelMatches();
|
|
1047
|
+
let matches;
|
|
1048
|
+
batch(() => {
|
|
1049
|
+
if (next) {
|
|
1050
|
+
// Ingest the new location
|
|
1051
|
+
this.store.setState(s => {
|
|
1052
|
+
s.latestLocation = next;
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
927
1055
|
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1056
|
+
// Match the routes
|
|
1057
|
+
matches = this.matchRoutes(this.store.state.latestLocation.pathname, {
|
|
1058
|
+
strictParseParams: true
|
|
1059
|
+
});
|
|
1060
|
+
this.store.setState(s => {
|
|
1061
|
+
s.status = 'loading';
|
|
1062
|
+
s.pendingMatches = matches;
|
|
1063
|
+
s.pendingLocation = this.store.state.latestLocation;
|
|
1064
|
+
});
|
|
1065
|
+
});
|
|
933
1066
|
|
|
934
|
-
//
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
return {
|
|
941
|
-
pathname,
|
|
942
|
-
search,
|
|
943
|
-
searchStr,
|
|
944
|
-
state: store.latestLocation.state,
|
|
945
|
-
hash,
|
|
946
|
-
href: `${pathname}${searchStr}${hash}`,
|
|
947
|
-
key: dest.key
|
|
948
|
-
};
|
|
949
|
-
}
|
|
950
|
-
function commitLocation(next, replace) {
|
|
951
|
-
const id = '' + Date.now() + Math.random();
|
|
952
|
-
let nextAction = 'replace';
|
|
953
|
-
if (!replace) {
|
|
954
|
-
nextAction = 'push';
|
|
1067
|
+
// Load the matches
|
|
1068
|
+
try {
|
|
1069
|
+
await this.loadMatches(matches);
|
|
1070
|
+
} catch (err) {
|
|
1071
|
+
console.warn(err);
|
|
1072
|
+
invariant(false, 'Matches failed to load due to error above ☝️. Navigation cancelled!');
|
|
955
1073
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1074
|
+
if (this.startedLoadingAt !== startedAt) {
|
|
1075
|
+
// Ignore side-effects of outdated side-effects
|
|
1076
|
+
return this.navigationPromise;
|
|
959
1077
|
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1078
|
+
const previousMatches = this.store.state.currentMatches;
|
|
1079
|
+
const exiting = [],
|
|
1080
|
+
staying = [];
|
|
1081
|
+
previousMatches.forEach(d => {
|
|
1082
|
+
if (matches.find(dd => dd.id === d.id)) {
|
|
1083
|
+
staying.push(d);
|
|
1084
|
+
} else {
|
|
1085
|
+
exiting.push(d);
|
|
1086
|
+
}
|
|
967
1087
|
});
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
resolveNavigation = () => {
|
|
971
|
-
previousNavigationResolve();
|
|
972
|
-
resolve();
|
|
973
|
-
};
|
|
1088
|
+
const entering = matches.filter(d => {
|
|
1089
|
+
return !previousMatches.find(dd => dd.id === d.id);
|
|
974
1090
|
});
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
store,
|
|
981
|
-
setStore,
|
|
982
|
-
options: originalOptions,
|
|
983
|
-
basepath: '',
|
|
984
|
-
routeTree: undefined,
|
|
985
|
-
routesById: {},
|
|
986
|
-
reset: () => {
|
|
987
|
-
setStore(s => Object.assign(s, getInitialRouterState()));
|
|
988
|
-
},
|
|
989
|
-
getRoute: id => {
|
|
990
|
-
return router.routesById[id];
|
|
991
|
-
},
|
|
992
|
-
dehydrate: () => {
|
|
993
|
-
return {
|
|
994
|
-
store: {
|
|
995
|
-
...pick(store, ['latestLocation', 'currentLocation', 'status', 'lastUpdated']),
|
|
996
|
-
currentMatches: store.currentMatches.map(match => ({
|
|
997
|
-
matchId: match.matchId,
|
|
998
|
-
store: pick(match.store, ['status', 'routeLoaderData', 'isInvalid', 'invalidAt'])
|
|
999
|
-
}))
|
|
1000
|
-
},
|
|
1001
|
-
context: router.options.context
|
|
1002
|
-
};
|
|
1003
|
-
},
|
|
1004
|
-
hydrate: dehydratedRouter => {
|
|
1005
|
-
setStore(s => {
|
|
1006
|
-
// Update the context TODO: make this part of state?
|
|
1007
|
-
router.options.context = dehydratedRouter.context;
|
|
1008
|
-
|
|
1009
|
-
// Match the routes
|
|
1010
|
-
const currentMatches = router.matchRoutes(dehydratedRouter.store.latestLocation.pathname, {
|
|
1011
|
-
strictParseParams: true
|
|
1012
|
-
});
|
|
1013
|
-
currentMatches.forEach((match, index) => {
|
|
1014
|
-
const dehydratedMatch = dehydratedRouter.store.currentMatches[index];
|
|
1015
|
-
invariant(dehydratedMatch && dehydratedMatch.matchId === match.matchId, 'Oh no! There was a hydration mismatch when attempting to restore the state of the router! 😬');
|
|
1016
|
-
Object.assign(match, dehydratedMatch);
|
|
1017
|
-
});
|
|
1018
|
-
currentMatches.forEach(match => match.__.validate());
|
|
1019
|
-
Object.assign(s, {
|
|
1020
|
-
...dehydratedRouter.store,
|
|
1021
|
-
currentMatches
|
|
1022
|
-
});
|
|
1091
|
+
now = Date.now();
|
|
1092
|
+
exiting.forEach(d => {
|
|
1093
|
+
d.__onExit?.({
|
|
1094
|
+
params: d.params,
|
|
1095
|
+
search: d.store.state.routeSearch
|
|
1023
1096
|
});
|
|
1024
|
-
},
|
|
1025
|
-
mount: () => {
|
|
1026
|
-
// Mount only does anything on the client
|
|
1027
|
-
if (!isServer) {
|
|
1028
|
-
// If the router matches are empty, load the matches
|
|
1029
|
-
if (!store.currentMatches.length) {
|
|
1030
|
-
router.load();
|
|
1031
|
-
}
|
|
1032
|
-
const unsub = router.history.listen(event => {
|
|
1033
|
-
router.load(parseLocation(event.location, store.latestLocation));
|
|
1034
|
-
});
|
|
1035
1097
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
window.addEventListener('visibilitychange', onFocus, false);
|
|
1042
|
-
window.addEventListener('focus', onFocus, false);
|
|
1043
|
-
}
|
|
1044
|
-
return () => {
|
|
1045
|
-
unsub();
|
|
1046
|
-
if (window.removeEventListener) {
|
|
1047
|
-
// Be sure to unsubscribe if a new handler is set
|
|
1048
|
-
window.removeEventListener('visibilitychange', onFocus);
|
|
1049
|
-
window.removeEventListener('focus', onFocus);
|
|
1050
|
-
}
|
|
1051
|
-
};
|
|
1052
|
-
}
|
|
1053
|
-
return () => {};
|
|
1054
|
-
},
|
|
1055
|
-
update: opts => {
|
|
1056
|
-
const newHistory = (opts == null ? void 0 : opts.history) !== router.history;
|
|
1057
|
-
if (!store.latestLocation || newHistory) {
|
|
1058
|
-
if (opts != null && opts.history) {
|
|
1059
|
-
router.history = opts.history;
|
|
1060
|
-
}
|
|
1061
|
-
setStore(s => {
|
|
1062
|
-
s.latestLocation = parseLocation(router.history.location);
|
|
1063
|
-
s.currentLocation = s.latestLocation;
|
|
1098
|
+
// Clear non-loading error states when match leaves
|
|
1099
|
+
if (d.store.state.status === 'error' && !d.store.state.isFetching) {
|
|
1100
|
+
d.store.setState(s => {
|
|
1101
|
+
s.status = 'idle';
|
|
1102
|
+
s.error = undefined;
|
|
1064
1103
|
});
|
|
1065
1104
|
}
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
} = router.options;
|
|
1071
|
-
router.basepath = `/${trimPath(basepath ?? '') ?? ''}`;
|
|
1072
|
-
if (routeConfig) {
|
|
1073
|
-
router.routesById = {};
|
|
1074
|
-
router.routeTree = buildRouteTree(routeConfig);
|
|
1075
|
-
}
|
|
1076
|
-
return router;
|
|
1077
|
-
},
|
|
1078
|
-
cancelMatches: () => {
|
|
1079
|
-
[...store.currentMatches, ...(store.pendingMatches || [])].forEach(match => {
|
|
1080
|
-
match.cancel();
|
|
1081
|
-
});
|
|
1082
|
-
},
|
|
1083
|
-
load: async next => {
|
|
1084
|
-
let now = Date.now();
|
|
1085
|
-
const startedAt = now;
|
|
1086
|
-
startedLoadingAt = startedAt;
|
|
1087
|
-
|
|
1088
|
-
// Cancel any pending matches
|
|
1089
|
-
router.cancelMatches();
|
|
1090
|
-
let matches;
|
|
1091
|
-
batch(() => {
|
|
1092
|
-
if (next) {
|
|
1093
|
-
// Ingest the new location
|
|
1094
|
-
setStore(s => {
|
|
1095
|
-
s.latestLocation = next;
|
|
1096
|
-
});
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
// Match the routes
|
|
1100
|
-
matches = router.matchRoutes(store.latestLocation.pathname, {
|
|
1101
|
-
strictParseParams: true
|
|
1102
|
-
});
|
|
1103
|
-
console.log('set loading', matches);
|
|
1104
|
-
setStore(s => {
|
|
1105
|
-
s.status = 'loading';
|
|
1106
|
-
s.pendingMatches = matches;
|
|
1107
|
-
s.pendingLocation = store.latestLocation;
|
|
1108
|
-
});
|
|
1109
|
-
});
|
|
1110
|
-
|
|
1111
|
-
// Load the matches
|
|
1112
|
-
try {
|
|
1113
|
-
await router.loadMatches(matches);
|
|
1114
|
-
} catch (err) {
|
|
1115
|
-
console.log(err);
|
|
1116
|
-
invariant(false, 'Matches failed to load due to error above ☝️. Navigation cancelled!');
|
|
1117
|
-
}
|
|
1118
|
-
if (startedLoadingAt !== startedAt) {
|
|
1119
|
-
// Ignore side-effects of outdated side-effects
|
|
1120
|
-
return navigationPromise;
|
|
1121
|
-
}
|
|
1122
|
-
const previousMatches = store.currentMatches;
|
|
1123
|
-
const exiting = [],
|
|
1124
|
-
staying = [];
|
|
1125
|
-
previousMatches.forEach(d => {
|
|
1126
|
-
if (matches.find(dd => dd.matchId === d.matchId)) {
|
|
1127
|
-
staying.push(d);
|
|
1128
|
-
} else {
|
|
1129
|
-
exiting.push(d);
|
|
1130
|
-
}
|
|
1131
|
-
});
|
|
1132
|
-
const entering = matches.filter(d => {
|
|
1133
|
-
return !previousMatches.find(dd => dd.matchId === d.matchId);
|
|
1134
|
-
});
|
|
1135
|
-
now = Date.now();
|
|
1136
|
-
exiting.forEach(d => {
|
|
1137
|
-
d.__.onExit == null ? void 0 : d.__.onExit({
|
|
1138
|
-
params: d.params,
|
|
1139
|
-
search: d.store.routeSearch
|
|
1140
|
-
});
|
|
1141
|
-
|
|
1142
|
-
// Clear idle error states when match leaves
|
|
1143
|
-
if (d.store.status === 'error' && !d.store.isFetching) {
|
|
1144
|
-
d.store.status = 'idle';
|
|
1145
|
-
d.store.error = undefined;
|
|
1146
|
-
}
|
|
1147
|
-
const gc = Math.max(d.options.loaderGcMaxAge ?? router.options.defaultLoaderGcMaxAge ?? 0, d.options.loaderMaxAge ?? router.options.defaultLoaderMaxAge ?? 0);
|
|
1148
|
-
if (gc > 0) {
|
|
1149
|
-
store.matchCache[d.matchId] = {
|
|
1105
|
+
const gc = Math.max(d.route.options.loaderGcMaxAge ?? this.options.defaultLoaderGcMaxAge ?? 0, d.route.options.loaderMaxAge ?? this.options.defaultLoaderMaxAge ?? 0);
|
|
1106
|
+
if (gc > 0) {
|
|
1107
|
+
this.store.setState(s => {
|
|
1108
|
+
s.matchCache[d.id] = {
|
|
1150
1109
|
gc: gc == Infinity ? Number.MAX_SAFE_INTEGER : now + gc,
|
|
1151
1110
|
match: d
|
|
1152
1111
|
};
|
|
1153
|
-
}
|
|
1154
|
-
});
|
|
1155
|
-
staying.forEach(d => {
|
|
1156
|
-
d.options.onTransition == null ? void 0 : d.options.onTransition({
|
|
1157
|
-
params: d.params,
|
|
1158
|
-
search: d.store.routeSearch
|
|
1159
|
-
});
|
|
1160
|
-
});
|
|
1161
|
-
entering.forEach(d => {
|
|
1162
|
-
d.__.onExit = d.options.onLoaded == null ? void 0 : d.options.onLoaded({
|
|
1163
|
-
params: d.params,
|
|
1164
|
-
search: d.store.search
|
|
1165
1112
|
});
|
|
1166
|
-
delete store.matchCache[d.matchId];
|
|
1167
|
-
});
|
|
1168
|
-
if (startedLoadingAt !== startedAt) {
|
|
1169
|
-
// Ignore side-effects of match loading
|
|
1170
|
-
return;
|
|
1171
1113
|
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
match.action.submissions = [];
|
|
1178
|
-
}
|
|
1114
|
+
});
|
|
1115
|
+
staying.forEach(d => {
|
|
1116
|
+
d.route.options.onTransition?.({
|
|
1117
|
+
params: d.params,
|
|
1118
|
+
search: d.store.state.routeSearch
|
|
1179
1119
|
});
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
currentMatches: matches,
|
|
1186
|
-
pendingLocation: undefined,
|
|
1187
|
-
pendingMatches: undefined
|
|
1188
|
-
});
|
|
1120
|
+
});
|
|
1121
|
+
entering.forEach(d => {
|
|
1122
|
+
d.__onExit = d.route.options.onLoaded?.({
|
|
1123
|
+
params: d.params,
|
|
1124
|
+
search: d.store.state.search
|
|
1189
1125
|
});
|
|
1190
|
-
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1126
|
+
delete this.store.state.matchCache[d.id];
|
|
1127
|
+
});
|
|
1128
|
+
this.store.setState(s => {
|
|
1129
|
+
Object.assign(s, {
|
|
1130
|
+
status: 'idle',
|
|
1131
|
+
currentLocation: this.store.state.latestLocation,
|
|
1132
|
+
currentMatches: matches,
|
|
1133
|
+
pendingLocation: undefined,
|
|
1134
|
+
pendingMatches: undefined
|
|
1135
|
+
});
|
|
1136
|
+
});
|
|
1137
|
+
this.options.onRouteChange?.();
|
|
1138
|
+
this.resolveNavigation();
|
|
1139
|
+
};
|
|
1140
|
+
cleanMatchCache = () => {
|
|
1141
|
+
const now = Date.now();
|
|
1142
|
+
this.store.setState(s => {
|
|
1143
|
+
Object.keys(s.matchCache).forEach(matchId => {
|
|
1144
|
+
const entry = s.matchCache[matchId];
|
|
1202
1145
|
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1146
|
+
// Don't remove loading matches
|
|
1147
|
+
if (entry.match.store.state.status === 'loading') {
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1207
1150
|
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
navigateOpts = store.latestLocation;
|
|
1216
|
-
}
|
|
1217
|
-
const next = router.buildNext(navigateOpts);
|
|
1218
|
-
const matches = router.matchRoutes(next.pathname, {
|
|
1219
|
-
strictParseParams: true
|
|
1220
|
-
});
|
|
1221
|
-
await router.loadMatches(matches);
|
|
1222
|
-
return matches;
|
|
1223
|
-
},
|
|
1224
|
-
preloadRoute: async function (navigateOpts, loaderOpts) {
|
|
1225
|
-
if (navigateOpts === void 0) {
|
|
1226
|
-
navigateOpts = store.latestLocation;
|
|
1227
|
-
}
|
|
1228
|
-
const next = router.buildNext(navigateOpts);
|
|
1229
|
-
const matches = router.matchRoutes(next.pathname, {
|
|
1230
|
-
strictParseParams: true
|
|
1231
|
-
});
|
|
1232
|
-
await router.loadMatches(matches, {
|
|
1233
|
-
preload: true,
|
|
1234
|
-
maxAge: loaderOpts.maxAge ?? router.options.defaultPreloadMaxAge ?? router.options.defaultLoaderMaxAge ?? 0,
|
|
1235
|
-
gcMaxAge: loaderOpts.gcMaxAge ?? router.options.defaultPreloadGcMaxAge ?? router.options.defaultLoaderGcMaxAge ?? 0
|
|
1151
|
+
// Do not remove successful matches that are still valid
|
|
1152
|
+
if (entry.gc > 0 && entry.gc > now) {
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// Everything else gets removed
|
|
1157
|
+
delete s.matchCache[matchId];
|
|
1236
1158
|
});
|
|
1159
|
+
});
|
|
1160
|
+
};
|
|
1161
|
+
getRoute = id => {
|
|
1162
|
+
const route = this.routesById[id];
|
|
1163
|
+
invariant(route, `Route with id "${id}" not found`);
|
|
1164
|
+
return route;
|
|
1165
|
+
};
|
|
1166
|
+
loadRoute = async (navigateOpts = this.store.state.latestLocation) => {
|
|
1167
|
+
const next = this.buildNext(navigateOpts);
|
|
1168
|
+
const matches = this.matchRoutes(next.pathname, {
|
|
1169
|
+
strictParseParams: true
|
|
1170
|
+
});
|
|
1171
|
+
await this.loadMatches(matches);
|
|
1172
|
+
return matches;
|
|
1173
|
+
};
|
|
1174
|
+
preloadRoute = async (navigateOpts = this.store.state.latestLocation, loaderOpts) => {
|
|
1175
|
+
const next = this.buildNext(navigateOpts);
|
|
1176
|
+
const matches = this.matchRoutes(next.pathname, {
|
|
1177
|
+
strictParseParams: true
|
|
1178
|
+
});
|
|
1179
|
+
await this.loadMatches(matches, {
|
|
1180
|
+
preload: true,
|
|
1181
|
+
maxAge: loaderOpts.maxAge ?? this.options.defaultPreloadMaxAge ?? this.options.defaultLoaderMaxAge ?? 0,
|
|
1182
|
+
gcMaxAge: loaderOpts.gcMaxAge ?? this.options.defaultPreloadGcMaxAge ?? this.options.defaultLoaderGcMaxAge ?? 0
|
|
1183
|
+
});
|
|
1184
|
+
return matches;
|
|
1185
|
+
};
|
|
1186
|
+
matchRoutes = (pathname, opts) => {
|
|
1187
|
+
const matches = [];
|
|
1188
|
+
if (!this.routeTree) {
|
|
1237
1189
|
return matches;
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
const
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
const
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
});
|
|
1264
|
-
if (matchParams) {
|
|
1265
|
-
let parsedParams;
|
|
1266
|
-
try {
|
|
1267
|
-
parsedParams = (route.options.parseParams == null ? void 0 : route.options.parseParams(matchParams)) ?? matchParams;
|
|
1268
|
-
} catch (err) {
|
|
1269
|
-
if (opts != null && opts.strictParseParams) {
|
|
1270
|
-
throw err;
|
|
1271
|
-
}
|
|
1190
|
+
}
|
|
1191
|
+
const existingMatches = [...this.store.state.currentMatches, ...(this.store.state.pendingMatches ?? [])];
|
|
1192
|
+
const recurse = async routes => {
|
|
1193
|
+
const parentMatch = last(matches);
|
|
1194
|
+
let params = parentMatch?.params ?? {};
|
|
1195
|
+
const filteredRoutes = this.options.filterRoutes?.(routes) ?? routes;
|
|
1196
|
+
let foundRoutes = [];
|
|
1197
|
+
const findMatchInRoutes = (parentRoutes, routes) => {
|
|
1198
|
+
routes.some(route => {
|
|
1199
|
+
if (!route.path && route.childRoutes?.length) {
|
|
1200
|
+
return findMatchInRoutes([...foundRoutes, route], route.childRoutes);
|
|
1201
|
+
}
|
|
1202
|
+
const fuzzy = !!(route.path !== '/' || route.childRoutes?.length);
|
|
1203
|
+
const matchParams = matchPathname(this.basepath, pathname, {
|
|
1204
|
+
to: route.fullPath,
|
|
1205
|
+
fuzzy,
|
|
1206
|
+
caseSensitive: route.options.caseSensitive ?? this.options.caseSensitive
|
|
1207
|
+
});
|
|
1208
|
+
if (matchParams) {
|
|
1209
|
+
let parsedParams;
|
|
1210
|
+
try {
|
|
1211
|
+
parsedParams = route.options.parseParams?.(matchParams) ?? matchParams;
|
|
1212
|
+
} catch (err) {
|
|
1213
|
+
if (opts?.strictParseParams) {
|
|
1214
|
+
throw err;
|
|
1272
1215
|
}
|
|
1273
|
-
params = {
|
|
1274
|
-
...params,
|
|
1275
|
-
...parsedParams
|
|
1276
|
-
};
|
|
1277
|
-
}
|
|
1278
|
-
if (!!matchParams) {
|
|
1279
|
-
foundRoutes = [...parentRoutes, route];
|
|
1280
1216
|
}
|
|
1281
|
-
|
|
1282
|
-
|
|
1217
|
+
params = {
|
|
1218
|
+
...params,
|
|
1219
|
+
...parsedParams
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
if (!!matchParams) {
|
|
1223
|
+
foundRoutes = [...parentRoutes, route];
|
|
1224
|
+
}
|
|
1283
1225
|
return !!foundRoutes.length;
|
|
1284
|
-
};
|
|
1285
|
-
findMatchInRoutes([], filteredRoutes);
|
|
1286
|
-
if (!foundRoutes.length) {
|
|
1287
|
-
return;
|
|
1288
|
-
}
|
|
1289
|
-
foundRoutes.forEach(foundRoute => {
|
|
1290
|
-
var _store$matchCache$mat;
|
|
1291
|
-
const interpolatedPath = interpolatePath(foundRoute.routePath, params);
|
|
1292
|
-
const matchId = interpolatePath(foundRoute.routeId, params, true);
|
|
1293
|
-
const match = existingMatches.find(d => d.matchId === matchId) || ((_store$matchCache$mat = store.matchCache[matchId]) == null ? void 0 : _store$matchCache$mat.match) || createRouteMatch(router, foundRoute, {
|
|
1294
|
-
parentMatch,
|
|
1295
|
-
matchId,
|
|
1296
|
-
params,
|
|
1297
|
-
pathname: joinPaths([router.basepath, interpolatedPath])
|
|
1298
|
-
});
|
|
1299
|
-
matches.push(match);
|
|
1300
1226
|
});
|
|
1301
|
-
|
|
1302
|
-
if ((_foundRoute$childRout = foundRoute.childRoutes) != null && _foundRoute$childRout.length) {
|
|
1303
|
-
recurse(foundRoute.childRoutes);
|
|
1304
|
-
}
|
|
1227
|
+
return !!foundRoutes.length;
|
|
1305
1228
|
};
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
match.
|
|
1229
|
+
findMatchInRoutes([], filteredRoutes);
|
|
1230
|
+
if (!foundRoutes.length) {
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
foundRoutes.forEach(foundRoute => {
|
|
1234
|
+
const interpolatedPath = interpolatePath(foundRoute.path, params);
|
|
1235
|
+
const matchId = interpolatePath(foundRoute.id, params, true);
|
|
1236
|
+
const match = existingMatches.find(d => d.id === matchId) || this.store.state.matchCache[matchId]?.match || new RouteMatch(this, foundRoute, {
|
|
1237
|
+
matchId,
|
|
1238
|
+
params,
|
|
1239
|
+
pathname: joinPaths([this.basepath, interpolatedPath])
|
|
1240
|
+
});
|
|
1241
|
+
matches.push(match);
|
|
1314
1242
|
});
|
|
1243
|
+
const foundRoute = last(foundRoutes);
|
|
1244
|
+
if (foundRoute.childRoutes?.length) {
|
|
1245
|
+
recurse(foundRoute.childRoutes);
|
|
1246
|
+
}
|
|
1247
|
+
};
|
|
1248
|
+
recurse([this.routeTree]);
|
|
1249
|
+
linkMatches(matches);
|
|
1250
|
+
return matches;
|
|
1251
|
+
};
|
|
1252
|
+
loadMatches = async (resolvedMatches, loaderOpts) => {
|
|
1253
|
+
this.cleanMatchCache();
|
|
1254
|
+
resolvedMatches.forEach(async match => {
|
|
1255
|
+
// Validate the match (loads search params etc)
|
|
1256
|
+
match.__validate();
|
|
1257
|
+
});
|
|
1315
1258
|
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
}));
|
|
1323
|
-
} catch (err) {
|
|
1324
|
-
if (!(loaderOpts != null && loaderOpts.preload)) {
|
|
1325
|
-
match.options.onLoadError == null ? void 0 : match.options.onLoadError(err);
|
|
1326
|
-
}
|
|
1327
|
-
throw err;
|
|
1328
|
-
}
|
|
1329
|
-
}));
|
|
1330
|
-
const matchPromises = resolvedMatches.map(async match => {
|
|
1331
|
-
var _search$__data;
|
|
1332
|
-
const search = match.store.search;
|
|
1333
|
-
if ((_search$__data = search.__data) != null && _search$__data.matchId && search.__data.matchId !== match.matchId) {
|
|
1334
|
-
return;
|
|
1335
|
-
}
|
|
1336
|
-
match.load(loaderOpts);
|
|
1337
|
-
if (match.store.status !== 'success' && match.__.loadPromise) {
|
|
1338
|
-
// Wait for the first sign of activity from the match
|
|
1339
|
-
await match.__.loadPromise;
|
|
1340
|
-
}
|
|
1341
|
-
});
|
|
1342
|
-
await Promise.all(matchPromises);
|
|
1343
|
-
},
|
|
1344
|
-
loadMatchData: async routeMatch => {
|
|
1345
|
-
if (isServer || !router.options.useServerData) {
|
|
1346
|
-
return (await (routeMatch.options.loader == null ? void 0 : routeMatch.options.loader({
|
|
1347
|
-
// parentLoaderPromise: routeMatch.parentMatch?.__.dataPromise,
|
|
1348
|
-
params: routeMatch.params,
|
|
1349
|
-
search: routeMatch.store.routeSearch,
|
|
1350
|
-
signal: routeMatch.__.abortController.signal
|
|
1351
|
-
}))) || {};
|
|
1352
|
-
} else {
|
|
1353
|
-
const next = router.buildNext({
|
|
1354
|
-
to: '.',
|
|
1355
|
-
search: d => ({
|
|
1356
|
-
...(d ?? {}),
|
|
1357
|
-
__data: {
|
|
1358
|
-
matchId: routeMatch.matchId
|
|
1359
|
-
}
|
|
1360
|
-
})
|
|
1259
|
+
// Check each match middleware to see if the route can be accessed
|
|
1260
|
+
await Promise.all(resolvedMatches.map(async match => {
|
|
1261
|
+
try {
|
|
1262
|
+
await match.route.options.beforeLoad?.({
|
|
1263
|
+
router: this,
|
|
1264
|
+
match
|
|
1361
1265
|
});
|
|
1266
|
+
} catch (err) {
|
|
1267
|
+
if (!loaderOpts?.preload) {
|
|
1268
|
+
match.route.options.onLoadError?.(err);
|
|
1269
|
+
}
|
|
1270
|
+
throw err;
|
|
1271
|
+
}
|
|
1272
|
+
}));
|
|
1273
|
+
const matchPromises = resolvedMatches.map(async (match, index) => {
|
|
1274
|
+
const prevMatch = resolvedMatches[1];
|
|
1275
|
+
const search = match.store.state.search;
|
|
1276
|
+
if (search.__data?.matchId && search.__data.matchId !== match.id) {
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
match.load(loaderOpts);
|
|
1280
|
+
if (match.store.state.status !== 'success' && match.__loadPromise) {
|
|
1281
|
+
// Wait for the first sign of activity from the match
|
|
1282
|
+
await match.__loadPromise;
|
|
1283
|
+
}
|
|
1284
|
+
if (prevMatch) {
|
|
1285
|
+
await prevMatch.__loadPromise;
|
|
1286
|
+
}
|
|
1287
|
+
});
|
|
1288
|
+
await Promise.all(matchPromises);
|
|
1289
|
+
};
|
|
1290
|
+
loadMatchData = async routeMatch => {
|
|
1291
|
+
if (isServer || !this.options.useServerData) {
|
|
1292
|
+
return (await routeMatch.route.options.loader?.({
|
|
1293
|
+
// parentLoaderPromise: routeMatch.parentMatch.dataPromise,
|
|
1294
|
+
params: routeMatch.params,
|
|
1295
|
+
search: routeMatch.store.state.routeSearch,
|
|
1296
|
+
signal: routeMatch.abortController.signal
|
|
1297
|
+
})) || {};
|
|
1298
|
+
} else {
|
|
1299
|
+
// Refresh:
|
|
1300
|
+
// '/dashboard'
|
|
1301
|
+
// '/dashboard/invoices/'
|
|
1302
|
+
// '/dashboard/invoices/123'
|
|
1362
1303
|
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
// '/dashboard/invoices/'
|
|
1366
|
-
// '/dashboard/invoices/123'
|
|
1367
|
-
|
|
1368
|
-
// New:
|
|
1369
|
-
// '/dashboard/invoices/456'
|
|
1370
|
-
|
|
1371
|
-
// TODO: batch requests when possible
|
|
1304
|
+
// New:
|
|
1305
|
+
// '/dashboard/invoices/456'
|
|
1372
1306
|
|
|
1373
|
-
|
|
1374
|
-
method: 'GET'
|
|
1375
|
-
// signal: routeMatch.__.abortController.signal,
|
|
1376
|
-
});
|
|
1307
|
+
// TODO: batch requests when possible
|
|
1377
1308
|
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
throw new Error('Failed to fetch match data');
|
|
1382
|
-
}
|
|
1383
|
-
},
|
|
1384
|
-
invalidateRoute: opts => {
|
|
1385
|
-
const next = router.buildNext(opts);
|
|
1386
|
-
const unloadedMatchIds = router.matchRoutes(next.pathname).map(d => d.matchId);
|
|
1387
|
-
[...store.currentMatches, ...(store.pendingMatches ?? [])].forEach(match => {
|
|
1388
|
-
if (unloadedMatchIds.includes(match.matchId)) {
|
|
1389
|
-
match.invalidate();
|
|
1390
|
-
}
|
|
1309
|
+
return this.options.fetchServerDataFn({
|
|
1310
|
+
router: this,
|
|
1311
|
+
routeMatch
|
|
1391
1312
|
});
|
|
1392
|
-
}
|
|
1393
|
-
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
invalidateRoute = async opts => {
|
|
1316
|
+
const next = this.buildNext(opts);
|
|
1317
|
+
const unloadedMatchIds = this.matchRoutes(next.pathname).map(d => d.id);
|
|
1318
|
+
await Promise.allSettled([...this.store.state.currentMatches, ...(this.store.state.pendingMatches ?? [])].map(async match => {
|
|
1319
|
+
if (unloadedMatchIds.includes(match.id)) {
|
|
1320
|
+
return match.invalidate();
|
|
1321
|
+
}
|
|
1322
|
+
}));
|
|
1323
|
+
};
|
|
1324
|
+
reload = () => {
|
|
1325
|
+
this.navigate({
|
|
1394
1326
|
fromCurrent: true,
|
|
1395
1327
|
replace: true,
|
|
1396
1328
|
search: true
|
|
1397
|
-
})
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1329
|
+
});
|
|
1330
|
+
};
|
|
1331
|
+
resolvePath = (from, path) => {
|
|
1332
|
+
return resolvePath(this.basepath, from, cleanPath(path));
|
|
1333
|
+
};
|
|
1334
|
+
navigate = async ({
|
|
1335
|
+
from,
|
|
1336
|
+
to = '.',
|
|
1337
|
+
search,
|
|
1338
|
+
hash,
|
|
1339
|
+
replace,
|
|
1340
|
+
params
|
|
1341
|
+
}) => {
|
|
1342
|
+
// If this link simply reloads the current route,
|
|
1343
|
+
// make sure it has a new key so it will trigger a data refresh
|
|
1403
1344
|
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1345
|
+
// If this `to` is a valid external URL, return
|
|
1346
|
+
// null for LinkUtils
|
|
1347
|
+
const toString = String(to);
|
|
1348
|
+
const fromString = String(from);
|
|
1349
|
+
let isExternal;
|
|
1350
|
+
try {
|
|
1351
|
+
new URL(`${toString}`);
|
|
1352
|
+
isExternal = true;
|
|
1353
|
+
} catch (e) {}
|
|
1354
|
+
invariant(!isExternal, 'Attempting to navigate to external url with this.navigate!');
|
|
1355
|
+
return this.#commitLocation({
|
|
1356
|
+
from: fromString,
|
|
1357
|
+
to: toString,
|
|
1358
|
+
search,
|
|
1359
|
+
hash,
|
|
1360
|
+
replace,
|
|
1361
|
+
params
|
|
1362
|
+
});
|
|
1363
|
+
};
|
|
1364
|
+
matchRoute = (location, opts) => {
|
|
1365
|
+
location = {
|
|
1366
|
+
...location,
|
|
1367
|
+
to: location.to ? this.resolvePath(location.from ?? '', location.to) : undefined
|
|
1368
|
+
};
|
|
1369
|
+
const next = this.buildNext(location);
|
|
1370
|
+
if (opts?.pending) {
|
|
1371
|
+
if (!this.store.state.pendingLocation) {
|
|
1372
|
+
return false;
|
|
1417
1373
|
}
|
|
1418
|
-
return matchPathname(
|
|
1374
|
+
return matchPathname(this.basepath, this.store.state.pendingLocation.pathname, {
|
|
1419
1375
|
...opts,
|
|
1420
1376
|
to: next.pathname
|
|
1421
1377
|
});
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
return navigate({
|
|
1446
|
-
from: fromString,
|
|
1447
|
-
to: toString,
|
|
1448
|
-
search,
|
|
1449
|
-
hash,
|
|
1450
|
-
replace,
|
|
1451
|
-
params
|
|
1452
|
-
});
|
|
1453
|
-
},
|
|
1454
|
-
buildLink: _ref2 => {
|
|
1455
|
-
let {
|
|
1456
|
-
from,
|
|
1457
|
-
to = '.',
|
|
1458
|
-
search,
|
|
1459
|
-
params,
|
|
1460
|
-
hash,
|
|
1461
|
-
target,
|
|
1462
|
-
replace,
|
|
1463
|
-
activeOptions,
|
|
1464
|
-
preload,
|
|
1465
|
-
preloadMaxAge: userPreloadMaxAge,
|
|
1466
|
-
preloadGcMaxAge: userPreloadGcMaxAge,
|
|
1467
|
-
preloadDelay: userPreloadDelay,
|
|
1468
|
-
disabled
|
|
1469
|
-
} = _ref2;
|
|
1470
|
-
// If this link simply reloads the current route,
|
|
1471
|
-
// make sure it has a new key so it will trigger a data refresh
|
|
1472
|
-
|
|
1473
|
-
// If this `to` is a valid external URL, return
|
|
1474
|
-
// null for LinkUtils
|
|
1378
|
+
}
|
|
1379
|
+
return matchPathname(this.basepath, this.store.state.currentLocation.pathname, {
|
|
1380
|
+
...opts,
|
|
1381
|
+
to: next.pathname
|
|
1382
|
+
});
|
|
1383
|
+
};
|
|
1384
|
+
buildLink = ({
|
|
1385
|
+
from,
|
|
1386
|
+
to = '.',
|
|
1387
|
+
search,
|
|
1388
|
+
params,
|
|
1389
|
+
hash,
|
|
1390
|
+
target,
|
|
1391
|
+
replace,
|
|
1392
|
+
activeOptions,
|
|
1393
|
+
preload,
|
|
1394
|
+
preloadMaxAge: userPreloadMaxAge,
|
|
1395
|
+
preloadGcMaxAge: userPreloadGcMaxAge,
|
|
1396
|
+
preloadDelay: userPreloadDelay,
|
|
1397
|
+
disabled
|
|
1398
|
+
}) => {
|
|
1399
|
+
// If this link simply reloads the current route,
|
|
1400
|
+
// make sure it has a new key so it will trigger a data refresh
|
|
1475
1401
|
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
from,
|
|
1485
|
-
to,
|
|
1486
|
-
search,
|
|
1487
|
-
params,
|
|
1488
|
-
hash,
|
|
1489
|
-
replace
|
|
1402
|
+
// If this `to` is a valid external URL, return
|
|
1403
|
+
// null for LinkUtils
|
|
1404
|
+
|
|
1405
|
+
try {
|
|
1406
|
+
new URL(`${to}`);
|
|
1407
|
+
return {
|
|
1408
|
+
type: 'external',
|
|
1409
|
+
href: to
|
|
1490
1410
|
};
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
router.invalidateRoute(nextOpts);
|
|
1514
|
-
}
|
|
1411
|
+
} catch (e) {}
|
|
1412
|
+
const nextOpts = {
|
|
1413
|
+
from,
|
|
1414
|
+
to,
|
|
1415
|
+
search,
|
|
1416
|
+
params,
|
|
1417
|
+
hash,
|
|
1418
|
+
replace
|
|
1419
|
+
};
|
|
1420
|
+
const next = this.buildNext(nextOpts);
|
|
1421
|
+
preload = preload ?? this.options.defaultPreload;
|
|
1422
|
+
const preloadDelay = userPreloadDelay ?? this.options.defaultPreloadDelay ?? 0;
|
|
1423
|
+
|
|
1424
|
+
// Compare path/hash for matches
|
|
1425
|
+
const pathIsEqual = this.store.state.currentLocation.pathname === next.pathname;
|
|
1426
|
+
const currentPathSplit = this.store.state.currentLocation.pathname.split('/');
|
|
1427
|
+
const nextPathSplit = next.pathname.split('/');
|
|
1428
|
+
const pathIsFuzzyEqual = nextPathSplit.every((d, i) => d === currentPathSplit[i]);
|
|
1429
|
+
const hashIsEqual = this.store.state.currentLocation.hash === next.hash;
|
|
1430
|
+
// Combine the matches based on user options
|
|
1431
|
+
const pathTest = activeOptions?.exact ? pathIsEqual : pathIsFuzzyEqual;
|
|
1432
|
+
const hashTest = activeOptions?.includeHash ? hashIsEqual : true;
|
|
1515
1433
|
|
|
1516
|
-
|
|
1517
|
-
|
|
1434
|
+
// The final "active" test
|
|
1435
|
+
const isActive = pathTest && hashTest;
|
|
1436
|
+
|
|
1437
|
+
// The click handler
|
|
1438
|
+
const handleClick = e => {
|
|
1439
|
+
if (!disabled && !isCtrlEvent(e) && !e.defaultPrevented && (!target || target === '_self') && e.button === 0) {
|
|
1440
|
+
e.preventDefault();
|
|
1441
|
+
if (pathIsEqual && !search && !hash) {
|
|
1442
|
+
this.invalidateRoute(nextOpts);
|
|
1518
1443
|
}
|
|
1519
|
-
};
|
|
1520
1444
|
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1445
|
+
// All is well? Navigate!
|
|
1446
|
+
this.#commitLocation(nextOpts);
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
|
|
1450
|
+
// The click handler
|
|
1451
|
+
const handleFocus = e => {
|
|
1452
|
+
if (preload) {
|
|
1453
|
+
this.preloadRoute(nextOpts, {
|
|
1454
|
+
maxAge: userPreloadMaxAge,
|
|
1455
|
+
gcMaxAge: userPreloadGcMaxAge
|
|
1456
|
+
}).catch(err => {
|
|
1457
|
+
console.warn(err);
|
|
1458
|
+
console.warn('Error preloading route! ☝️');
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
};
|
|
1462
|
+
const handleEnter = e => {
|
|
1463
|
+
const target = e.target || {};
|
|
1464
|
+
if (preload) {
|
|
1465
|
+
if (target.preloadTimeout) {
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
target.preloadTimeout = setTimeout(() => {
|
|
1469
|
+
target.preloadTimeout = null;
|
|
1470
|
+
this.preloadRoute(nextOpts, {
|
|
1525
1471
|
maxAge: userPreloadMaxAge,
|
|
1526
1472
|
gcMaxAge: userPreloadGcMaxAge
|
|
1527
1473
|
}).catch(err => {
|
|
1528
|
-
console.
|
|
1474
|
+
console.warn(err);
|
|
1529
1475
|
console.warn('Error preloading route! ☝️');
|
|
1530
1476
|
});
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1477
|
+
}, preloadDelay);
|
|
1478
|
+
}
|
|
1479
|
+
};
|
|
1480
|
+
const handleLeave = e => {
|
|
1481
|
+
const target = e.target || {};
|
|
1482
|
+
if (target.preloadTimeout) {
|
|
1483
|
+
clearTimeout(target.preloadTimeout);
|
|
1484
|
+
target.preloadTimeout = null;
|
|
1485
|
+
}
|
|
1486
|
+
};
|
|
1487
|
+
return {
|
|
1488
|
+
type: 'internal',
|
|
1489
|
+
next,
|
|
1490
|
+
handleFocus,
|
|
1491
|
+
handleClick,
|
|
1492
|
+
handleEnter,
|
|
1493
|
+
handleLeave,
|
|
1494
|
+
isActive,
|
|
1495
|
+
disabled
|
|
1496
|
+
};
|
|
1497
|
+
};
|
|
1498
|
+
dehydrate = () => {
|
|
1499
|
+
return {
|
|
1500
|
+
state: {
|
|
1501
|
+
...pick(this.store.state, ['latestLocation', 'currentLocation', 'status', 'lastUpdated']),
|
|
1502
|
+
currentMatches: this.store.state.currentMatches.map(match => ({
|
|
1503
|
+
matchId: match.id,
|
|
1504
|
+
state: {
|
|
1505
|
+
...pick(match.store.state, ['status', 'routeLoaderData', 'invalidAt', 'invalid'])
|
|
1538
1506
|
}
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1507
|
+
}))
|
|
1508
|
+
},
|
|
1509
|
+
context: this.options.context
|
|
1510
|
+
};
|
|
1511
|
+
};
|
|
1512
|
+
hydrate = dehydratedRouter => {
|
|
1513
|
+
this.store.setState(s => {
|
|
1514
|
+
// Update the context TODO: make this part of state?
|
|
1515
|
+
this.options.context = dehydratedRouter.context;
|
|
1516
|
+
|
|
1517
|
+
// Match the routes
|
|
1518
|
+
const currentMatches = this.matchRoutes(dehydratedRouter.state.latestLocation.pathname, {
|
|
1519
|
+
strictParseParams: true
|
|
1520
|
+
});
|
|
1521
|
+
currentMatches.forEach((match, index) => {
|
|
1522
|
+
const dehydratedMatch = dehydratedRouter.state.currentMatches[index];
|
|
1523
|
+
invariant(dehydratedMatch && dehydratedMatch.matchId === match.id, 'Oh no! There was a hydration mismatch when attempting to rethis.store the state of the router! 😬');
|
|
1524
|
+
Object.assign(match, dehydratedMatch);
|
|
1525
|
+
});
|
|
1526
|
+
currentMatches.forEach(match => match.__validate());
|
|
1527
|
+
Object.assign(s, {
|
|
1528
|
+
...dehydratedRouter.state,
|
|
1529
|
+
currentMatches
|
|
1530
|
+
});
|
|
1531
|
+
});
|
|
1532
|
+
};
|
|
1533
|
+
getLoader = opts => {
|
|
1534
|
+
const id = opts.from || '/';
|
|
1535
|
+
const route = this.getRoute(id);
|
|
1536
|
+
if (!route) return undefined;
|
|
1537
|
+
let loader = this.store.state.loaders[id] || (() => {
|
|
1538
|
+
this.store.setState(s => {
|
|
1539
|
+
s.loaders[id] = {
|
|
1540
|
+
pending: [],
|
|
1541
|
+
fetch: async loaderContext => {
|
|
1542
|
+
if (!route) {
|
|
1543
|
+
return;
|
|
1544
|
+
}
|
|
1545
|
+
const loaderState = {
|
|
1546
|
+
loadedAt: Date.now(),
|
|
1547
|
+
loaderContext
|
|
1548
|
+
};
|
|
1549
|
+
this.store.setState(s => {
|
|
1550
|
+
s.loaders[id].current = loaderState;
|
|
1551
|
+
s.loaders[id].latest = loaderState;
|
|
1552
|
+
s.loaders[id].pending.push(loaderState);
|
|
1547
1553
|
});
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1554
|
+
try {
|
|
1555
|
+
return await route.options.loader?.(loaderContext);
|
|
1556
|
+
} finally {
|
|
1557
|
+
this.store.setState(s => {
|
|
1558
|
+
s.loaders[id].pending = s.loaders[id].pending.filter(d => d !== loaderState);
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
};
|
|
1563
|
+
});
|
|
1564
|
+
return this.store.state.loaders[id];
|
|
1565
|
+
})();
|
|
1566
|
+
return loader;
|
|
1567
|
+
};
|
|
1568
|
+
#buildRouteTree = rootRouteConfig => {
|
|
1569
|
+
const recurseRoutes = (routeConfigs, parent) => {
|
|
1570
|
+
return routeConfigs.map((routeConfig, i) => {
|
|
1571
|
+
const routeOptions = routeConfig.options;
|
|
1572
|
+
const route = new Route(routeConfig, routeOptions, i, parent, this);
|
|
1573
|
+
const existingRoute = this.routesById[route.id];
|
|
1574
|
+
if (existingRoute) {
|
|
1575
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1576
|
+
console.warn(`Duplicate routes found with id: ${String(route.id)}`, this.routesById, route);
|
|
1577
|
+
}
|
|
1578
|
+
throw new Error();
|
|
1556
1579
|
}
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1580
|
+
this.routesById[route.id] = route;
|
|
1581
|
+
const children = routeConfig.children;
|
|
1582
|
+
route.childRoutes = children.length ? recurseRoutes(children, route) : undefined;
|
|
1583
|
+
return route;
|
|
1584
|
+
});
|
|
1585
|
+
};
|
|
1586
|
+
const routes = recurseRoutes([rootRouteConfig]);
|
|
1587
|
+
return routes[0];
|
|
1588
|
+
};
|
|
1589
|
+
#parseLocation = previousLocation => {
|
|
1590
|
+
let {
|
|
1591
|
+
pathname,
|
|
1592
|
+
search,
|
|
1593
|
+
hash,
|
|
1594
|
+
state
|
|
1595
|
+
} = this.history.location;
|
|
1596
|
+
const parsedSearch = this.options.parseSearch(search);
|
|
1597
|
+
console.log({
|
|
1598
|
+
pathname: pathname,
|
|
1599
|
+
searchStr: search,
|
|
1600
|
+
search: replaceEqualDeep(previousLocation?.search, parsedSearch),
|
|
1601
|
+
hash: hash.split('#').reverse()[0] ?? '',
|
|
1602
|
+
href: `${pathname}${search}${hash}`,
|
|
1603
|
+
state: state,
|
|
1604
|
+
key: state?.key || '__init__'
|
|
1605
|
+
});
|
|
1606
|
+
return {
|
|
1607
|
+
pathname: pathname,
|
|
1608
|
+
searchStr: search,
|
|
1609
|
+
search: replaceEqualDeep(previousLocation?.search, parsedSearch),
|
|
1610
|
+
hash: hash.split('#').reverse()[0] ?? '',
|
|
1611
|
+
href: `${pathname}${search}${hash}`,
|
|
1612
|
+
state: state,
|
|
1613
|
+
key: state?.key || '__init__'
|
|
1614
|
+
};
|
|
1615
|
+
};
|
|
1616
|
+
#onFocus = () => {
|
|
1617
|
+
this.load();
|
|
1618
|
+
};
|
|
1619
|
+
#buildLocation = (dest = {}) => {
|
|
1620
|
+
const fromPathname = dest.fromCurrent ? this.store.state.latestLocation.pathname : dest.from ?? this.store.state.latestLocation.pathname;
|
|
1621
|
+
let pathname = resolvePath(this.basepath ?? '/', fromPathname, `${dest.to ?? '.'}`);
|
|
1622
|
+
const fromMatches = this.matchRoutes(this.store.state.latestLocation.pathname, {
|
|
1623
|
+
strictParseParams: true
|
|
1624
|
+
});
|
|
1625
|
+
const toMatches = this.matchRoutes(pathname);
|
|
1626
|
+
const prevParams = {
|
|
1627
|
+
...last(fromMatches)?.params
|
|
1628
|
+
};
|
|
1629
|
+
let nextParams = (dest.params ?? true) === true ? prevParams : functionalUpdate(dest.params, prevParams);
|
|
1630
|
+
if (nextParams) {
|
|
1631
|
+
toMatches.map(d => d.route.options.stringifyParams).filter(Boolean).forEach(fn => {
|
|
1632
|
+
Object.assign({}, nextParams, fn(nextParams));
|
|
1578
1633
|
});
|
|
1579
1634
|
}
|
|
1635
|
+
pathname = interpolatePath(pathname, nextParams ?? {});
|
|
1636
|
+
|
|
1637
|
+
// Pre filters first
|
|
1638
|
+
const preFilteredSearch = dest.__preSearchFilters?.length ? dest.__preSearchFilters?.reduce((prev, next) => next(prev), this.store.state.latestLocation.search) : this.store.state.latestLocation.search;
|
|
1639
|
+
|
|
1640
|
+
// Then the link/navigate function
|
|
1641
|
+
const destSearch = dest.search === true ? preFilteredSearch // Preserve resolvedFrom true
|
|
1642
|
+
: dest.search ? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
|
|
1643
|
+
: dest.__preSearchFilters?.length ? preFilteredSearch // Preserve resolvedFrom filters
|
|
1644
|
+
: {};
|
|
1645
|
+
|
|
1646
|
+
// Then post filters
|
|
1647
|
+
const postFilteredSearch = dest.__postSearchFilters?.length ? dest.__postSearchFilters.reduce((prev, next) => next(prev), destSearch) : destSearch;
|
|
1648
|
+
const search = replaceEqualDeep(this.store.state.latestLocation.search, postFilteredSearch);
|
|
1649
|
+
const searchStr = this.options.stringifySearch(search);
|
|
1650
|
+
let hash = dest.hash === true ? this.store.state.latestLocation.hash : functionalUpdate(dest.hash, this.store.state.latestLocation.hash);
|
|
1651
|
+
hash = hash ? `#${hash}` : '';
|
|
1652
|
+
return {
|
|
1653
|
+
pathname,
|
|
1654
|
+
search,
|
|
1655
|
+
searchStr,
|
|
1656
|
+
state: this.store.state.latestLocation.state,
|
|
1657
|
+
hash,
|
|
1658
|
+
href: `${pathname}${searchStr}${hash}`,
|
|
1659
|
+
key: dest.key
|
|
1660
|
+
};
|
|
1661
|
+
};
|
|
1662
|
+
#commitLocation = location => {
|
|
1663
|
+
const next = this.buildNext(location);
|
|
1664
|
+
const id = '' + Date.now() + Math.random();
|
|
1665
|
+
if (this.navigateTimeout) clearTimeout(this.navigateTimeout);
|
|
1666
|
+
let nextAction = 'replace';
|
|
1667
|
+
if (!location.replace) {
|
|
1668
|
+
nextAction = 'push';
|
|
1669
|
+
}
|
|
1670
|
+
const isSameUrl = this.store.state.latestLocation.href === next.href;
|
|
1671
|
+
if (isSameUrl && !next.key) {
|
|
1672
|
+
nextAction = 'replace';
|
|
1673
|
+
}
|
|
1674
|
+
const href = `${next.pathname}${next.searchStr}${next.hash ? `#${next.hash}` : ''}`;
|
|
1675
|
+
this.history[nextAction === 'push' ? 'push' : 'replace'](href, {
|
|
1676
|
+
id,
|
|
1677
|
+
...next.state
|
|
1678
|
+
});
|
|
1679
|
+
this.load(this.#parseLocation(this.store.state.latestLocation));
|
|
1680
|
+
return this.navigationPromise = new Promise(resolve => {
|
|
1681
|
+
const previousNavigationResolve = this.resolveNavigation;
|
|
1682
|
+
this.resolveNavigation = () => {
|
|
1683
|
+
previousNavigationResolve();
|
|
1684
|
+
resolve();
|
|
1685
|
+
};
|
|
1686
|
+
});
|
|
1580
1687
|
};
|
|
1581
|
-
|
|
1688
|
+
}
|
|
1582
1689
|
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1690
|
+
// Detect if we're in the DOM
|
|
1691
|
+
const isServer = typeof window === 'undefined' || !window.document.createElement;
|
|
1692
|
+
function getInitialRouterState() {
|
|
1693
|
+
return {
|
|
1694
|
+
status: 'idle',
|
|
1695
|
+
latestLocation: null,
|
|
1696
|
+
currentLocation: null,
|
|
1697
|
+
currentMatches: [],
|
|
1698
|
+
loaders: {},
|
|
1699
|
+
lastUpdated: Date.now(),
|
|
1700
|
+
matchCache: {},
|
|
1701
|
+
get isFetching() {
|
|
1702
|
+
return this.status === 'loading' || this.currentMatches.some(d => d.store.state.isFetching);
|
|
1703
|
+
},
|
|
1704
|
+
get isPreloading() {
|
|
1705
|
+
return Object.values(this.matchCache).some(d => d.match.store.state.isFetching && !this.currentMatches.find(dd => dd.id === d.match.id));
|
|
1706
|
+
}
|
|
1707
|
+
};
|
|
1586
1708
|
}
|
|
1587
1709
|
function isCtrlEvent(e) {
|
|
1588
1710
|
return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey);
|
|
@@ -1591,12 +1713,81 @@ function linkMatches(matches) {
|
|
|
1591
1713
|
matches.forEach((match, index) => {
|
|
1592
1714
|
const parent = matches[index - 1];
|
|
1593
1715
|
if (parent) {
|
|
1594
|
-
match.
|
|
1595
|
-
} else {
|
|
1596
|
-
match.__.setParentMatch(undefined);
|
|
1716
|
+
match.__setParentMatch(parent);
|
|
1597
1717
|
}
|
|
1598
1718
|
});
|
|
1599
1719
|
}
|
|
1600
1720
|
|
|
1601
|
-
|
|
1721
|
+
// createRouterAction is a constrained identify function that takes options: key, action, onSuccess, onError, onSettled, etc
|
|
1722
|
+
function createAction(options) {
|
|
1723
|
+
const store = createStore({
|
|
1724
|
+
submissions: []
|
|
1725
|
+
}, options.debug);
|
|
1726
|
+
return {
|
|
1727
|
+
options,
|
|
1728
|
+
store,
|
|
1729
|
+
reset: () => {
|
|
1730
|
+
store.setState(s => {
|
|
1731
|
+
s.submissions = [];
|
|
1732
|
+
});
|
|
1733
|
+
},
|
|
1734
|
+
submit: async payload => {
|
|
1735
|
+
const submission = {
|
|
1736
|
+
submittedAt: Date.now(),
|
|
1737
|
+
status: 'pending',
|
|
1738
|
+
payload: payload,
|
|
1739
|
+
invalidate: () => {
|
|
1740
|
+
setSubmission(s => {
|
|
1741
|
+
s.isInvalid = true;
|
|
1742
|
+
});
|
|
1743
|
+
},
|
|
1744
|
+
getIsLatest: () => store.state.submissions[store.state.submissions.length - 1]?.submittedAt === submission.submittedAt
|
|
1745
|
+
};
|
|
1746
|
+
const setSubmission = updater => {
|
|
1747
|
+
store.setState(s => {
|
|
1748
|
+
const a = s.submissions.find(d => d.submittedAt === submission.submittedAt);
|
|
1749
|
+
invariant(a, 'Could not find submission in store');
|
|
1750
|
+
updater(a);
|
|
1751
|
+
});
|
|
1752
|
+
};
|
|
1753
|
+
store.setState(s => {
|
|
1754
|
+
s.submissions.push(submission);
|
|
1755
|
+
s.submissions.reverse();
|
|
1756
|
+
s.submissions = s.submissions.slice(0, options.maxSubmissions ?? 10);
|
|
1757
|
+
s.submissions.reverse();
|
|
1758
|
+
});
|
|
1759
|
+
const after = async () => {
|
|
1760
|
+
options.onEachSettled?.(submission);
|
|
1761
|
+
if (submission.getIsLatest()) await options.onLatestSettled?.(submission);
|
|
1762
|
+
};
|
|
1763
|
+
try {
|
|
1764
|
+
const res = await options.action?.(submission.payload);
|
|
1765
|
+
setSubmission(s => {
|
|
1766
|
+
s.response = res;
|
|
1767
|
+
});
|
|
1768
|
+
await options.onEachSuccess?.(submission);
|
|
1769
|
+
if (submission.getIsLatest()) await options.onLatestSuccess?.(submission);
|
|
1770
|
+
await after();
|
|
1771
|
+
setSubmission(s => {
|
|
1772
|
+
s.status = 'success';
|
|
1773
|
+
});
|
|
1774
|
+
return res;
|
|
1775
|
+
} catch (err) {
|
|
1776
|
+
console.error(err);
|
|
1777
|
+
setSubmission(s => {
|
|
1778
|
+
s.error = err;
|
|
1779
|
+
});
|
|
1780
|
+
await options.onEachError?.(submission);
|
|
1781
|
+
if (submission.getIsLatest()) await options.onLatestError?.(submission);
|
|
1782
|
+
await after();
|
|
1783
|
+
setSubmission(s => {
|
|
1784
|
+
s.status = 'error';
|
|
1785
|
+
});
|
|
1786
|
+
throw err;
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
export { Route, RouteMatch, Router, batch, cleanPath, createAction, createBrowserHistory, createHashHistory, createMemoryHistory, createRouteConfig, createStore, decode, defaultFetchServerDataFn, defaultParseSearch, defaultStringifySearch, encode, functionalUpdate, interpolatePath, joinPaths, last, matchByPath, matchPathname, parsePathname, parseSearchWith, pick, replaceEqualDeep, resolvePath, rootRouteId, stringifySearchWith, trackDeep, trimPath, trimPathLeft, trimPathRight, warning };
|
|
1602
1793
|
//# sourceMappingURL=index.js.map
|