@tanstack/react-router 1.6.1 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/Matches.js +22 -1
- package/build/cjs/Matches.js.map +1 -1
- package/build/cjs/awaited.js +21 -4
- package/build/cjs/awaited.js.map +1 -1
- package/build/cjs/defer.js +7 -2
- package/build/cjs/defer.js.map +1 -1
- package/build/cjs/index.js +3 -0
- package/build/cjs/index.js.map +1 -1
- package/build/cjs/link.js.map +1 -1
- package/build/cjs/router.js +20 -1
- package/build/cjs/router.js.map +1 -1
- package/build/esm/index.js +1900 -1846
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +1 -1
- package/build/stats-react.json +585 -560
- package/build/types/Matches.d.ts +5 -0
- package/build/types/defer.d.ts +4 -1
- package/build/types/link.d.ts +3 -1
- package/build/types/router.d.ts +20 -6
- package/build/types/routerContext.d.ts +1 -1
- package/build/umd/index.development.js +1902 -1845
- package/build/umd/index.development.js.map +1 -1
- package/build/umd/index.production.js +2 -2
- package/build/umd/index.production.js.map +1 -1
- package/package.json +2 -2
- package/src/Matches.tsx +28 -1
- package/src/awaited.tsx +25 -1
- package/src/defer.ts +12 -2
- package/src/link.tsx +24 -16
- package/src/router.ts +50 -7
package/build/esm/index.js
CHANGED
|
@@ -18,69 +18,6 @@ import * as React from 'react';
|
|
|
18
18
|
import { useStore } from '@tanstack/react-store';
|
|
19
19
|
import { Store } from '@tanstack/store';
|
|
20
20
|
|
|
21
|
-
let routerContext = /*#__PURE__*/React.createContext(null);
|
|
22
|
-
if (typeof document !== 'undefined') {
|
|
23
|
-
if (window.__TSR_ROUTER_CONTEXT__) {
|
|
24
|
-
routerContext = window.__TSR_ROUTER_CONTEXT__;
|
|
25
|
-
} else {
|
|
26
|
-
window.__TSR_ROUTER_CONTEXT__ = routerContext;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function useRouter(opts) {
|
|
31
|
-
const resolvedContext = typeof document !== 'undefined' ? window.__TSR_ROUTER_CONTEXT__ || routerContext : routerContext;
|
|
32
|
-
const value = React.useContext(resolvedContext);
|
|
33
|
-
warning(!((opts?.warn ?? true) && !value), 'useRouter must be used inside a <RouterProvider> component!');
|
|
34
|
-
return value;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function defer(_promise) {
|
|
38
|
-
const promise = _promise;
|
|
39
|
-
if (!promise.__deferredState) {
|
|
40
|
-
promise.__deferredState = {
|
|
41
|
-
uid: Math.random().toString(36).slice(2),
|
|
42
|
-
status: 'pending'
|
|
43
|
-
};
|
|
44
|
-
const state = promise.__deferredState;
|
|
45
|
-
promise.then(data => {
|
|
46
|
-
state.status = 'success';
|
|
47
|
-
state.data = data;
|
|
48
|
-
}).catch(error => {
|
|
49
|
-
state.status = 'error';
|
|
50
|
-
state.error = error;
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
return promise;
|
|
54
|
-
}
|
|
55
|
-
function isDehydratedDeferred(obj) {
|
|
56
|
-
return typeof obj === 'object' && obj !== null && !(obj instanceof Promise) && !obj.then && '__deferredState' in obj;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function useAwaited({
|
|
60
|
-
promise
|
|
61
|
-
}) {
|
|
62
|
-
const router = useRouter();
|
|
63
|
-
let state = promise.__deferredState;
|
|
64
|
-
const key = `__TSR__DEFERRED__${state.uid}`;
|
|
65
|
-
if (isDehydratedDeferred(promise)) {
|
|
66
|
-
state = router.hydrateData(key);
|
|
67
|
-
promise = Promise.resolve(state.data);
|
|
68
|
-
promise.__deferredState = state;
|
|
69
|
-
}
|
|
70
|
-
if (state.status === 'pending') {
|
|
71
|
-
throw promise;
|
|
72
|
-
}
|
|
73
|
-
if (state.status === 'error') {
|
|
74
|
-
throw state.error;
|
|
75
|
-
}
|
|
76
|
-
router.dehydrateData(key, state);
|
|
77
|
-
return [state.data];
|
|
78
|
-
}
|
|
79
|
-
function Await(props) {
|
|
80
|
-
const awaited = useAwaited(props);
|
|
81
|
-
return props.children(...awaited);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
21
|
function CatchBoundary(props) {
|
|
85
22
|
const errorComponent = props.errorComponent ?? ErrorComponent;
|
|
86
23
|
return /*#__PURE__*/React.createElement(CatchBoundaryImpl, {
|
|
@@ -172,6 +109,22 @@ function ErrorComponent({
|
|
|
172
109
|
}, error.message ? /*#__PURE__*/React.createElement("code", null, error.message) : null)) : null);
|
|
173
110
|
}
|
|
174
111
|
|
|
112
|
+
let routerContext = /*#__PURE__*/React.createContext(null);
|
|
113
|
+
if (typeof document !== 'undefined') {
|
|
114
|
+
if (window.__TSR_ROUTER_CONTEXT__) {
|
|
115
|
+
routerContext = window.__TSR_ROUTER_CONTEXT__;
|
|
116
|
+
} else {
|
|
117
|
+
window.__TSR_ROUTER_CONTEXT__ = routerContext;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function useRouter(opts) {
|
|
122
|
+
const resolvedContext = typeof document !== 'undefined' ? window.__TSR_ROUTER_CONTEXT__ || routerContext : routerContext;
|
|
123
|
+
const value = React.useContext(resolvedContext);
|
|
124
|
+
warning(!((opts?.warn ?? true) && !value), 'useRouter must be used inside a <RouterProvider> component!');
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
127
|
+
|
|
175
128
|
function useRouterState(opts) {
|
|
176
129
|
const contextRouter = useRouter({
|
|
177
130
|
warn: opts?.router === undefined
|
|
@@ -443,7 +396,12 @@ function MatchInner({
|
|
|
443
396
|
select: s => pick(getRenderedMatches(s).find(d => d.id === matchId), ['status', 'error', 'showPending', 'loadPromise'])
|
|
444
397
|
});
|
|
445
398
|
if (match.status === 'error') {
|
|
446
|
-
|
|
399
|
+
if (isServerSideError(match.error)) {
|
|
400
|
+
const deserializeError = router.options.errorSerializer?.deserialize ?? defaultDeserializeError;
|
|
401
|
+
throw deserializeError(match.error.data);
|
|
402
|
+
} else {
|
|
403
|
+
throw match.error;
|
|
404
|
+
}
|
|
447
405
|
}
|
|
448
406
|
if (match.status === 'pending') {
|
|
449
407
|
if (match.showPending) {
|
|
@@ -564,114 +522,353 @@ function useLoaderData(opts) {
|
|
|
564
522
|
}
|
|
565
523
|
});
|
|
566
524
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
525
|
+
function isServerSideError(error) {
|
|
526
|
+
if (!(typeof error === 'object' && error && 'data' in error)) return false;
|
|
527
|
+
if (!('__isServerError' in error && error.__isServerError)) return false;
|
|
528
|
+
if (!(typeof error.data === 'object' && error.data)) return false;
|
|
529
|
+
return error.__isServerError === true;
|
|
530
|
+
}
|
|
531
|
+
function defaultDeserializeError(serializedData) {
|
|
532
|
+
if ('name' in serializedData && 'message' in serializedData) {
|
|
533
|
+
const error = new Error(serializedData.message);
|
|
534
|
+
error.name = serializedData.name;
|
|
535
|
+
return error;
|
|
536
|
+
}
|
|
537
|
+
return serializedData.data;
|
|
577
538
|
}
|
|
578
|
-
|
|
579
|
-
|
|
539
|
+
|
|
540
|
+
// @ts-nocheck
|
|
541
|
+
|
|
542
|
+
// qss has been slightly modified and inlined here for our use cases (and compression's sake). We've included it as a hard dependency for MIT license attribution.
|
|
543
|
+
|
|
544
|
+
function encode(obj, pfx) {
|
|
545
|
+
var k,
|
|
546
|
+
i,
|
|
547
|
+
tmp,
|
|
548
|
+
str = '';
|
|
549
|
+
for (k in obj) {
|
|
550
|
+
if ((tmp = obj[k]) !== void 0) {
|
|
551
|
+
if (Array.isArray(tmp)) {
|
|
552
|
+
for (i = 0; i < tmp.length; i++) {
|
|
553
|
+
str && (str += '&');
|
|
554
|
+
str += encodeURIComponent(k) + '=' + encodeURIComponent(tmp[i]);
|
|
555
|
+
}
|
|
556
|
+
} else {
|
|
557
|
+
str && (str += '&');
|
|
558
|
+
str += encodeURIComponent(k) + '=' + encodeURIComponent(tmp);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return (pfx || '') + str;
|
|
580
563
|
}
|
|
581
|
-
function
|
|
582
|
-
|
|
564
|
+
function toValue(mix) {
|
|
565
|
+
if (!mix) return '';
|
|
566
|
+
var str = decodeURIComponent(mix);
|
|
567
|
+
if (str === 'false') return false;
|
|
568
|
+
if (str === 'true') return true;
|
|
569
|
+
return +str * 0 === 0 && +str + '' === str ? +str : str;
|
|
583
570
|
}
|
|
584
|
-
function
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
} else if (index === toSegments.length - 1) {
|
|
595
|
-
// Trailing Slash
|
|
596
|
-
baseSegments.push(toSegment);
|
|
597
|
-
} else ;
|
|
598
|
-
} else if (toSegment.value === '..') {
|
|
599
|
-
// Extra trailing slash? pop it off
|
|
600
|
-
if (baseSegments.length > 1 && last(baseSegments)?.value === '/') {
|
|
601
|
-
baseSegments.pop();
|
|
602
|
-
}
|
|
603
|
-
baseSegments.pop();
|
|
604
|
-
} else if (toSegment.value === '.') {
|
|
605
|
-
return;
|
|
571
|
+
function decode(str) {
|
|
572
|
+
var tmp,
|
|
573
|
+
k,
|
|
574
|
+
out = {},
|
|
575
|
+
arr = str.split('&');
|
|
576
|
+
while (tmp = arr.shift()) {
|
|
577
|
+
tmp = tmp.split('=');
|
|
578
|
+
k = tmp.shift();
|
|
579
|
+
if (out[k] !== void 0) {
|
|
580
|
+
out[k] = [].concat(out[k], toValue(tmp.shift()));
|
|
606
581
|
} else {
|
|
607
|
-
|
|
582
|
+
out[k] = toValue(tmp.shift());
|
|
608
583
|
}
|
|
609
|
-
});
|
|
610
|
-
const joined = joinPaths([basepath, ...baseSegments.map(d => d.value)]);
|
|
611
|
-
return cleanPath(joined);
|
|
612
|
-
}
|
|
613
|
-
function parsePathname(pathname) {
|
|
614
|
-
if (!pathname) {
|
|
615
|
-
return [];
|
|
616
|
-
}
|
|
617
|
-
pathname = cleanPath(pathname);
|
|
618
|
-
const segments = [];
|
|
619
|
-
if (pathname.slice(0, 1) === '/') {
|
|
620
|
-
pathname = pathname.substring(1);
|
|
621
|
-
segments.push({
|
|
622
|
-
type: 'pathname',
|
|
623
|
-
value: '/'
|
|
624
|
-
});
|
|
625
|
-
}
|
|
626
|
-
if (!pathname) {
|
|
627
|
-
return segments;
|
|
628
584
|
}
|
|
585
|
+
return out;
|
|
586
|
+
}
|
|
629
587
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
value: part
|
|
637
|
-
};
|
|
588
|
+
const defaultParseSearch = parseSearchWith(JSON.parse);
|
|
589
|
+
const defaultStringifySearch = stringifySearchWith(JSON.stringify, JSON.parse);
|
|
590
|
+
function parseSearchWith(parser) {
|
|
591
|
+
return searchStr => {
|
|
592
|
+
if (searchStr.substring(0, 1) === '?') {
|
|
593
|
+
searchStr = searchStr.substring(1);
|
|
638
594
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
595
|
+
let query = decode(searchStr);
|
|
596
|
+
|
|
597
|
+
// Try to parse any query params that might be json
|
|
598
|
+
for (let key in query) {
|
|
599
|
+
const value = query[key];
|
|
600
|
+
if (typeof value === 'string') {
|
|
601
|
+
try {
|
|
602
|
+
query[key] = parser(value);
|
|
603
|
+
} catch (err) {
|
|
604
|
+
//
|
|
605
|
+
}
|
|
606
|
+
}
|
|
644
607
|
}
|
|
645
|
-
return
|
|
646
|
-
|
|
647
|
-
value: part
|
|
648
|
-
};
|
|
649
|
-
}));
|
|
650
|
-
if (pathname.slice(-1) === '/') {
|
|
651
|
-
pathname = pathname.substring(1);
|
|
652
|
-
segments.push({
|
|
653
|
-
type: 'pathname',
|
|
654
|
-
value: '/'
|
|
655
|
-
});
|
|
656
|
-
}
|
|
657
|
-
return segments;
|
|
608
|
+
return query;
|
|
609
|
+
};
|
|
658
610
|
}
|
|
659
|
-
function
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
if (
|
|
668
|
-
|
|
611
|
+
function stringifySearchWith(stringify, parser) {
|
|
612
|
+
function stringifyValue(val) {
|
|
613
|
+
if (typeof val === 'object' && val !== null) {
|
|
614
|
+
try {
|
|
615
|
+
return stringify(val);
|
|
616
|
+
} catch (err) {
|
|
617
|
+
// silent
|
|
618
|
+
}
|
|
619
|
+
} else if (typeof val === 'string' && typeof parser === 'function') {
|
|
620
|
+
try {
|
|
621
|
+
// Check if it's a valid parseable string.
|
|
622
|
+
// If it is, then stringify it again.
|
|
623
|
+
parser(val);
|
|
624
|
+
return stringify(val);
|
|
625
|
+
} catch (err) {
|
|
626
|
+
// silent
|
|
627
|
+
}
|
|
669
628
|
}
|
|
670
|
-
return
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
629
|
+
return val;
|
|
630
|
+
}
|
|
631
|
+
return search => {
|
|
632
|
+
search = {
|
|
633
|
+
...search
|
|
634
|
+
};
|
|
635
|
+
if (search) {
|
|
636
|
+
Object.keys(search).forEach(key => {
|
|
637
|
+
const val = search[key];
|
|
638
|
+
if (typeof val === 'undefined' || val === undefined) {
|
|
639
|
+
delete search[key];
|
|
640
|
+
} else {
|
|
641
|
+
search[key] = stringifyValue(val);
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
const searchStr = encode(search).toString();
|
|
646
|
+
return searchStr ? `?${searchStr}` : '';
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const useTransition = React.useTransition || (() => [false, cb => {
|
|
651
|
+
cb();
|
|
652
|
+
}]);
|
|
653
|
+
function RouterProvider({
|
|
654
|
+
router,
|
|
655
|
+
...rest
|
|
656
|
+
}) {
|
|
657
|
+
// Allow the router to update options on the router instance
|
|
658
|
+
router.update({
|
|
659
|
+
...router.options,
|
|
660
|
+
...rest,
|
|
661
|
+
context: {
|
|
662
|
+
...router.options.context,
|
|
663
|
+
...rest?.context
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
const matches = router.options.InnerWrap ? /*#__PURE__*/React.createElement(router.options.InnerWrap, null, /*#__PURE__*/React.createElement(Matches, null)) : /*#__PURE__*/React.createElement(Matches, null);
|
|
667
|
+
const provider = /*#__PURE__*/React.createElement(routerContext.Provider, {
|
|
668
|
+
value: router
|
|
669
|
+
}, matches, /*#__PURE__*/React.createElement(Transitioner, null));
|
|
670
|
+
if (router.options.Wrap) {
|
|
671
|
+
return /*#__PURE__*/React.createElement(router.options.Wrap, null, provider);
|
|
672
|
+
}
|
|
673
|
+
return provider;
|
|
674
|
+
}
|
|
675
|
+
function Transitioner() {
|
|
676
|
+
const mountLoadCount = React.useRef(0);
|
|
677
|
+
const router = useRouter();
|
|
678
|
+
const routerState = useRouterState({
|
|
679
|
+
select: s => pick(s, ['isLoading', 'location', 'resolvedLocation', 'isTransitioning'])
|
|
680
|
+
});
|
|
681
|
+
const [isTransitioning, startReactTransition] = useTransition();
|
|
682
|
+
router.startReactTransition = startReactTransition;
|
|
683
|
+
React.useEffect(() => {
|
|
684
|
+
if (isTransitioning) {
|
|
685
|
+
router.__store.setState(s => ({
|
|
686
|
+
...s,
|
|
687
|
+
isTransitioning
|
|
688
|
+
}));
|
|
689
|
+
}
|
|
690
|
+
}, [isTransitioning]);
|
|
691
|
+
const tryLoad = () => {
|
|
692
|
+
const apply = cb => {
|
|
693
|
+
if (!routerState.isTransitioning) {
|
|
694
|
+
startReactTransition(() => cb());
|
|
695
|
+
} else {
|
|
696
|
+
cb();
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
apply(() => {
|
|
700
|
+
try {
|
|
701
|
+
router.load();
|
|
702
|
+
} catch (err) {
|
|
703
|
+
console.error(err);
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
};
|
|
707
|
+
useLayoutEffect$1(() => {
|
|
708
|
+
const unsub = router.history.subscribe(() => {
|
|
709
|
+
router.latestLocation = router.parseLocation(router.latestLocation);
|
|
710
|
+
if (routerState.location !== router.latestLocation) {
|
|
711
|
+
tryLoad();
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
const nextLocation = router.buildLocation({
|
|
715
|
+
search: true,
|
|
716
|
+
params: true,
|
|
717
|
+
hash: true,
|
|
718
|
+
state: true
|
|
719
|
+
});
|
|
720
|
+
if (routerState.location.href !== nextLocation.href) {
|
|
721
|
+
router.commitLocation({
|
|
722
|
+
...nextLocation,
|
|
723
|
+
replace: true
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
return () => {
|
|
727
|
+
unsub();
|
|
728
|
+
};
|
|
729
|
+
}, [router.history]);
|
|
730
|
+
useLayoutEffect$1(() => {
|
|
731
|
+
if (React.useTransition ? routerState.isTransitioning && !isTransitioning : !routerState.isLoading && routerState.resolvedLocation !== routerState.location) {
|
|
732
|
+
router.emit({
|
|
733
|
+
type: 'onResolved',
|
|
734
|
+
fromLocation: routerState.resolvedLocation,
|
|
735
|
+
toLocation: routerState.location,
|
|
736
|
+
pathChanged: routerState.location.href !== routerState.resolvedLocation?.href
|
|
737
|
+
});
|
|
738
|
+
if (document.querySelector) {
|
|
739
|
+
if (routerState.location.hash !== '') {
|
|
740
|
+
const el = document.getElementById(routerState.location.hash);
|
|
741
|
+
if (el) {
|
|
742
|
+
el.scrollIntoView();
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
router.__store.setState(s => ({
|
|
747
|
+
...s,
|
|
748
|
+
isTransitioning: false,
|
|
749
|
+
resolvedLocation: s.location
|
|
750
|
+
}));
|
|
751
|
+
}
|
|
752
|
+
}, [routerState.isTransitioning, isTransitioning, routerState.isLoading, routerState.resolvedLocation, routerState.location]);
|
|
753
|
+
useLayoutEffect$1(() => {
|
|
754
|
+
if (!window.__TSR_DEHYDRATED__ && !mountLoadCount.current) {
|
|
755
|
+
mountLoadCount.current++;
|
|
756
|
+
tryLoad();
|
|
757
|
+
}
|
|
758
|
+
}, []);
|
|
759
|
+
return null;
|
|
760
|
+
}
|
|
761
|
+
function getRouteMatch(state, id) {
|
|
762
|
+
return [...state.cachedMatches, ...(state.pendingMatches ?? []), ...state.matches].find(d => d.id === id);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function joinPaths(paths) {
|
|
766
|
+
return cleanPath(paths.filter(Boolean).join('/'));
|
|
767
|
+
}
|
|
768
|
+
function cleanPath(path) {
|
|
769
|
+
// remove double slashes
|
|
770
|
+
return path.replace(/\/{2,}/g, '/');
|
|
771
|
+
}
|
|
772
|
+
function trimPathLeft(path) {
|
|
773
|
+
return path === '/' ? path : path.replace(/^\/{1,}/, '');
|
|
774
|
+
}
|
|
775
|
+
function trimPathRight(path) {
|
|
776
|
+
return path === '/' ? path : path.replace(/\/{1,}$/, '');
|
|
777
|
+
}
|
|
778
|
+
function trimPath(path) {
|
|
779
|
+
return trimPathRight(trimPathLeft(path));
|
|
780
|
+
}
|
|
781
|
+
function resolvePath(basepath, base, to) {
|
|
782
|
+
base = base.replace(new RegExp(`^${basepath}`), '/');
|
|
783
|
+
to = to.replace(new RegExp(`^${basepath}`), '/');
|
|
784
|
+
let baseSegments = parsePathname(base);
|
|
785
|
+
const toSegments = parsePathname(to);
|
|
786
|
+
toSegments.forEach((toSegment, index) => {
|
|
787
|
+
if (toSegment.value === '/') {
|
|
788
|
+
if (!index) {
|
|
789
|
+
// Leading slash
|
|
790
|
+
baseSegments = [toSegment];
|
|
791
|
+
} else if (index === toSegments.length - 1) {
|
|
792
|
+
// Trailing Slash
|
|
793
|
+
baseSegments.push(toSegment);
|
|
794
|
+
} else ;
|
|
795
|
+
} else if (toSegment.value === '..') {
|
|
796
|
+
// Extra trailing slash? pop it off
|
|
797
|
+
if (baseSegments.length > 1 && last(baseSegments)?.value === '/') {
|
|
798
|
+
baseSegments.pop();
|
|
799
|
+
}
|
|
800
|
+
baseSegments.pop();
|
|
801
|
+
} else if (toSegment.value === '.') {
|
|
802
|
+
return;
|
|
803
|
+
} else {
|
|
804
|
+
baseSegments.push(toSegment);
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
const joined = joinPaths([basepath, ...baseSegments.map(d => d.value)]);
|
|
808
|
+
return cleanPath(joined);
|
|
809
|
+
}
|
|
810
|
+
function parsePathname(pathname) {
|
|
811
|
+
if (!pathname) {
|
|
812
|
+
return [];
|
|
813
|
+
}
|
|
814
|
+
pathname = cleanPath(pathname);
|
|
815
|
+
const segments = [];
|
|
816
|
+
if (pathname.slice(0, 1) === '/') {
|
|
817
|
+
pathname = pathname.substring(1);
|
|
818
|
+
segments.push({
|
|
819
|
+
type: 'pathname',
|
|
820
|
+
value: '/'
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
if (!pathname) {
|
|
824
|
+
return segments;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// Remove empty segments and '.' segments
|
|
828
|
+
const split = pathname.split('/').filter(Boolean);
|
|
829
|
+
segments.push(...split.map(part => {
|
|
830
|
+
if (part === '$' || part === '*') {
|
|
831
|
+
return {
|
|
832
|
+
type: 'wildcard',
|
|
833
|
+
value: part
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
if (part.charAt(0) === '$') {
|
|
837
|
+
return {
|
|
838
|
+
type: 'param',
|
|
839
|
+
value: part
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
return {
|
|
843
|
+
type: 'pathname',
|
|
844
|
+
value: part
|
|
845
|
+
};
|
|
846
|
+
}));
|
|
847
|
+
if (pathname.slice(-1) === '/') {
|
|
848
|
+
pathname = pathname.substring(1);
|
|
849
|
+
segments.push({
|
|
850
|
+
type: 'pathname',
|
|
851
|
+
value: '/'
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
return segments;
|
|
855
|
+
}
|
|
856
|
+
function interpolatePath(path, params, leaveWildcards = false) {
|
|
857
|
+
const interpolatedPathSegments = parsePathname(path);
|
|
858
|
+
return joinPaths(interpolatedPathSegments.map(segment => {
|
|
859
|
+
if (segment.type === 'wildcard') {
|
|
860
|
+
const value = params[segment.value];
|
|
861
|
+
if (leaveWildcards) return `${segment.value}${value ?? ''}`;
|
|
862
|
+
return value;
|
|
863
|
+
}
|
|
864
|
+
if (segment.type === 'param') {
|
|
865
|
+
return params[segment.value.substring(1)] ?? 'undefined';
|
|
866
|
+
}
|
|
867
|
+
return segment.value;
|
|
868
|
+
}));
|
|
869
|
+
}
|
|
870
|
+
function matchPathname(basepath, currentPathname, matchLocation) {
|
|
871
|
+
const pathParams = matchByPath(basepath, currentPathname, matchLocation);
|
|
675
872
|
// const searchMatched = matchBySearch(location.search, matchLocation)
|
|
676
873
|
|
|
677
874
|
if (matchLocation.to && !pathParams) {
|
|
@@ -756,1827 +953,1684 @@ function matchByPath(basepath, from, matchLocation) {
|
|
|
756
953
|
return isMatch ? params : undefined;
|
|
757
954
|
}
|
|
758
955
|
|
|
759
|
-
|
|
760
|
-
return useRouterState({
|
|
761
|
-
select: state => {
|
|
762
|
-
const params = last(getRenderedMatches(state))?.params;
|
|
763
|
-
return opts?.select ? opts.select(params) : params;
|
|
764
|
-
}
|
|
765
|
-
});
|
|
766
|
-
}
|
|
956
|
+
// Detect if we're in the DOM
|
|
767
957
|
|
|
768
|
-
function
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
958
|
+
function redirect(opts) {
|
|
959
|
+
opts.isRedirect = true;
|
|
960
|
+
if (opts.throw) {
|
|
961
|
+
throw opts;
|
|
962
|
+
}
|
|
963
|
+
return opts;
|
|
964
|
+
}
|
|
965
|
+
function isRedirect(obj) {
|
|
966
|
+
return !!obj?.isRedirect;
|
|
775
967
|
}
|
|
776
968
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
// The parse type here allows a zod schema to be passed directly to the validator
|
|
780
|
-
|
|
781
|
-
// TODO: This is part of a future APi to move away from classes and
|
|
782
|
-
// towards a more functional API. It's not ready yet.
|
|
783
|
-
|
|
784
|
-
// type RouteApiInstance<
|
|
785
|
-
// TId extends RouteIds<RegisteredRouter['routeTree']>,
|
|
786
|
-
// TRoute extends AnyRoute = RouteById<RegisteredRouter['routeTree'], TId>,
|
|
787
|
-
// TFullSearchSchema extends Record<
|
|
788
|
-
// string,
|
|
789
|
-
// any
|
|
790
|
-
// > = TRoute['types']['fullSearchSchema'],
|
|
791
|
-
// TAllParams extends AnyPathParams = TRoute['types']['allParams'],
|
|
792
|
-
// TAllContext extends Record<string, any> = TRoute['types']['allContext'],
|
|
793
|
-
// TLoaderDeps extends Record<string, any> = TRoute['types']['loaderDeps'],
|
|
794
|
-
// TLoaderData extends any = TRoute['types']['loaderData'],
|
|
795
|
-
// > = {
|
|
796
|
-
// id: TId
|
|
797
|
-
// useMatch: <TSelected = TAllContext>(opts?: {
|
|
798
|
-
// select?: (s: TAllContext) => TSelected
|
|
799
|
-
// }) => TSelected
|
|
800
|
-
|
|
801
|
-
// useRouteContext: <TSelected = TAllContext>(opts?: {
|
|
802
|
-
// select?: (s: TAllContext) => TSelected
|
|
803
|
-
// }) => TSelected
|
|
804
|
-
|
|
805
|
-
// useSearch: <TSelected = TFullSearchSchema>(opts?: {
|
|
806
|
-
// select?: (s: TFullSearchSchema) => TSelected
|
|
807
|
-
// }) => TSelected
|
|
808
|
-
|
|
809
|
-
// useParams: <TSelected = TAllParams>(opts?: {
|
|
810
|
-
// select?: (s: TAllParams) => TSelected
|
|
811
|
-
// }) => TSelected
|
|
812
|
-
|
|
813
|
-
// useLoaderDeps: <TSelected = TLoaderDeps>(opts?: {
|
|
814
|
-
// select?: (s: TLoaderDeps) => TSelected
|
|
815
|
-
// }) => TSelected
|
|
816
|
-
|
|
817
|
-
// useLoaderData: <TSelected = TLoaderData>(opts?: {
|
|
818
|
-
// select?: (s: TLoaderData) => TSelected
|
|
819
|
-
// }) => TSelected
|
|
820
|
-
// }
|
|
821
|
-
|
|
822
|
-
// export function RouteApi_v2<
|
|
823
|
-
// TId extends RouteIds<RegisteredRouter['routeTree']>,
|
|
824
|
-
// TRoute extends AnyRoute = RouteById<RegisteredRouter['routeTree'], TId>,
|
|
825
|
-
// TFullSearchSchema extends Record<
|
|
826
|
-
// string,
|
|
827
|
-
// any
|
|
828
|
-
// > = TRoute['types']['fullSearchSchema'],
|
|
829
|
-
// TAllParams extends AnyPathParams = TRoute['types']['allParams'],
|
|
830
|
-
// TAllContext extends Record<string, any> = TRoute['types']['allContext'],
|
|
831
|
-
// TLoaderDeps extends Record<string, any> = TRoute['types']['loaderDeps'],
|
|
832
|
-
// TLoaderData extends any = TRoute['types']['loaderData'],
|
|
833
|
-
// >({
|
|
834
|
-
// id,
|
|
835
|
-
// }: {
|
|
836
|
-
// id: TId
|
|
837
|
-
// }): RouteApiInstance<
|
|
838
|
-
// TId,
|
|
839
|
-
// TRoute,
|
|
840
|
-
// TFullSearchSchema,
|
|
841
|
-
// TAllParams,
|
|
842
|
-
// TAllContext,
|
|
843
|
-
// TLoaderDeps,
|
|
844
|
-
// TLoaderData
|
|
845
|
-
// > {
|
|
846
|
-
// return {
|
|
847
|
-
// id,
|
|
848
|
-
|
|
849
|
-
// useMatch: (opts) => {
|
|
850
|
-
// return useMatch({ ...opts, from: id })
|
|
851
|
-
// },
|
|
852
|
-
|
|
853
|
-
// useRouteContext: (opts) => {
|
|
854
|
-
// return useMatch({
|
|
855
|
-
// ...opts,
|
|
856
|
-
// from: id,
|
|
857
|
-
// select: (d: any) => (opts?.select ? opts.select(d.context) : d.context),
|
|
858
|
-
// } as any)
|
|
859
|
-
// },
|
|
860
|
-
|
|
861
|
-
// useSearch: (opts) => {
|
|
862
|
-
// return useSearch({ ...opts, from: id } as any)
|
|
863
|
-
// },
|
|
864
|
-
|
|
865
|
-
// useParams: (opts) => {
|
|
866
|
-
// return useParams({ ...opts, from: id } as any)
|
|
867
|
-
// },
|
|
868
|
-
|
|
869
|
-
// useLoaderDeps: (opts) => {
|
|
870
|
-
// return useLoaderDeps({ ...opts, from: id } as any) as any
|
|
871
|
-
// },
|
|
872
|
-
|
|
873
|
-
// useLoaderData: (opts) => {
|
|
874
|
-
// return useLoaderData({ ...opts, from: id } as any) as any
|
|
875
|
-
// },
|
|
876
|
-
// }
|
|
877
|
-
// }
|
|
969
|
+
// import warning from 'tiny-warning'
|
|
878
970
|
|
|
879
|
-
|
|
880
|
-
constructor({
|
|
881
|
-
id
|
|
882
|
-
}) {
|
|
883
|
-
this.id = id;
|
|
884
|
-
}
|
|
885
|
-
useMatch = opts => {
|
|
886
|
-
return useMatch({
|
|
887
|
-
select: opts?.select,
|
|
888
|
-
from: this.id
|
|
889
|
-
});
|
|
890
|
-
};
|
|
891
|
-
useRouteContext = opts => {
|
|
892
|
-
return useMatch({
|
|
893
|
-
from: this.id,
|
|
894
|
-
select: d => opts?.select ? opts.select(d.context) : d.context
|
|
895
|
-
});
|
|
896
|
-
};
|
|
897
|
-
useSearch = opts => {
|
|
898
|
-
return useSearch({
|
|
899
|
-
...opts,
|
|
900
|
-
from: this.id
|
|
901
|
-
});
|
|
902
|
-
};
|
|
903
|
-
useParams = opts => {
|
|
904
|
-
return useParams({
|
|
905
|
-
...opts,
|
|
906
|
-
from: this.id
|
|
907
|
-
});
|
|
908
|
-
};
|
|
909
|
-
useLoaderDeps = opts => {
|
|
910
|
-
return useLoaderDeps({
|
|
911
|
-
...opts,
|
|
912
|
-
from: this.id
|
|
913
|
-
});
|
|
914
|
-
};
|
|
915
|
-
useLoaderData = opts => {
|
|
916
|
-
return useLoaderData({
|
|
917
|
-
...opts,
|
|
918
|
-
from: this.id
|
|
919
|
-
});
|
|
920
|
-
};
|
|
921
|
-
}
|
|
922
|
-
class Route {
|
|
923
|
-
// Set up in this.init()
|
|
971
|
+
//
|
|
924
972
|
|
|
925
|
-
|
|
973
|
+
const componentTypes = ['component', 'errorComponent', 'pendingComponent'];
|
|
974
|
+
class Router {
|
|
975
|
+
// Option-independent properties
|
|
976
|
+
tempLocationKey = `${Math.round(Math.random() * 10000000)}`;
|
|
977
|
+
resetNextScroll = true;
|
|
978
|
+
navigateTimeout = null;
|
|
979
|
+
latestLoadPromise = Promise.resolve();
|
|
980
|
+
subscribers = new Set();
|
|
981
|
+
injectedHtml = [];
|
|
926
982
|
|
|
927
|
-
//
|
|
983
|
+
// Must build in constructor
|
|
928
984
|
|
|
929
985
|
constructor(options) {
|
|
930
|
-
this.
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
986
|
+
this.update({
|
|
987
|
+
defaultPreloadDelay: 50,
|
|
988
|
+
defaultPendingMs: 1000,
|
|
989
|
+
defaultPendingMinMs: 500,
|
|
990
|
+
context: undefined,
|
|
991
|
+
...options,
|
|
992
|
+
stringifySearch: options?.stringifySearch ?? defaultStringifySearch,
|
|
993
|
+
parseSearch: options?.parseSearch ?? defaultParseSearch
|
|
994
|
+
});
|
|
934
995
|
}
|
|
935
|
-
init = opts => {
|
|
936
|
-
this.originalIndex = opts.originalIndex;
|
|
937
|
-
const options = this.options;
|
|
938
|
-
const isRoot = !options?.path && !options?.id;
|
|
939
|
-
this.parentRoute = this.options?.getParentRoute?.();
|
|
940
|
-
if (isRoot) {
|
|
941
|
-
this.path = rootRouteId;
|
|
942
|
-
} else {
|
|
943
|
-
invariant(this.parentRoute, `Child Route instances must pass a 'getParentRoute: () => ParentRoute' option that returns a Route instance.`);
|
|
944
|
-
}
|
|
945
|
-
let path = isRoot ? rootRouteId : options.path;
|
|
946
996
|
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
997
|
+
// These are default implementations that can optionally be overridden
|
|
998
|
+
// by the router provider once rendered. We provide these so that the
|
|
999
|
+
// router can be used in a non-react environment if necessary
|
|
1000
|
+
startReactTransition = fn => fn();
|
|
1001
|
+
update = newOptions => {
|
|
1002
|
+
const previousOptions = this.options;
|
|
1003
|
+
this.options = {
|
|
1004
|
+
...this.options,
|
|
1005
|
+
...newOptions
|
|
1006
|
+
};
|
|
1007
|
+
if (!this.basepath || newOptions.basepath && newOptions.basepath !== previousOptions.basepath) {
|
|
1008
|
+
if (newOptions.basepath === undefined || newOptions.basepath === '' || newOptions.basepath === '/') {
|
|
1009
|
+
this.basepath = '/';
|
|
1010
|
+
} else {
|
|
1011
|
+
this.basepath = `/${trimPath(newOptions.basepath)}`;
|
|
1012
|
+
}
|
|
950
1013
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
path = '/';
|
|
1014
|
+
if (!this.history || this.options.history && this.options.history !== this.history) {
|
|
1015
|
+
this.history = this.options.history ?? (typeof document !== 'undefined' ? createBrowserHistory() : createMemoryHistory({
|
|
1016
|
+
initialEntries: [this.options.basepath || '/']
|
|
1017
|
+
}));
|
|
1018
|
+
this.latestLocation = this.parseLocation();
|
|
957
1019
|
}
|
|
958
|
-
if (
|
|
959
|
-
|
|
1020
|
+
if (this.options.routeTree !== this.routeTree) {
|
|
1021
|
+
this.routeTree = this.options.routeTree;
|
|
1022
|
+
this.buildRouteTree();
|
|
1023
|
+
}
|
|
1024
|
+
if (!this.__store) {
|
|
1025
|
+
this.__store = new Store(getInitialRouterState(this.latestLocation), {
|
|
1026
|
+
onUpdate: () => {
|
|
1027
|
+
this.__store.state = {
|
|
1028
|
+
...this.state,
|
|
1029
|
+
status: this.state.isTransitioning || this.state.isLoading ? 'pending' : 'idle'
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
});
|
|
960
1033
|
}
|
|
961
|
-
const fullPath = id === rootRouteId ? '/' : joinPaths([this.parentRoute.fullPath, path]);
|
|
962
|
-
this.path = path;
|
|
963
|
-
this.id = id;
|
|
964
|
-
// this.customId = customId as TCustomId
|
|
965
|
-
this.fullPath = fullPath;
|
|
966
|
-
this.to = fullPath;
|
|
967
|
-
};
|
|
968
|
-
addChildren = children => {
|
|
969
|
-
this.children = children;
|
|
970
|
-
return this;
|
|
971
|
-
};
|
|
972
|
-
updateLoader = options => {
|
|
973
|
-
Object.assign(this.options, options);
|
|
974
|
-
return this;
|
|
975
1034
|
};
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1035
|
+
get state() {
|
|
1036
|
+
return this.__store.state;
|
|
1037
|
+
}
|
|
1038
|
+
buildRouteTree = () => {
|
|
1039
|
+
this.routesById = {};
|
|
1040
|
+
this.routesByPath = {};
|
|
1041
|
+
const notFoundRoute = this.options.notFoundRoute;
|
|
1042
|
+
if (notFoundRoute) {
|
|
1043
|
+
notFoundRoute.init({
|
|
1044
|
+
originalIndex: 99999999999
|
|
1045
|
+
});
|
|
1046
|
+
this.routesById[notFoundRoute.id] = notFoundRoute;
|
|
1047
|
+
}
|
|
1048
|
+
const recurseRoutes = childRoutes => {
|
|
1049
|
+
childRoutes.forEach((childRoute, i) => {
|
|
1050
|
+
childRoute.init({
|
|
1051
|
+
originalIndex: i
|
|
1052
|
+
});
|
|
1053
|
+
const existingRoute = this.routesById[childRoute.id];
|
|
1054
|
+
invariant(!existingRoute, `Duplicate routes found with id: ${String(childRoute.id)}`);
|
|
1055
|
+
this.routesById[childRoute.id] = childRoute;
|
|
1056
|
+
if (!childRoute.isRoot && childRoute.path) {
|
|
1057
|
+
const trimmedFullPath = trimPathRight(childRoute.fullPath);
|
|
1058
|
+
if (!this.routesByPath[trimmedFullPath] || childRoute.fullPath.endsWith('/')) {
|
|
1059
|
+
this.routesByPath[trimmedFullPath] = childRoute;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
const children = childRoute.children;
|
|
1063
|
+
if (children?.length) {
|
|
1064
|
+
recurseRoutes(children);
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
};
|
|
1068
|
+
recurseRoutes([this.routeTree]);
|
|
1069
|
+
const scoredRoutes = [];
|
|
1070
|
+
Object.values(this.routesById).forEach((d, i) => {
|
|
1071
|
+
if (d.isRoot || !d.path) {
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
const trimmed = trimPathLeft(d.fullPath);
|
|
1075
|
+
const parsed = parsePathname(trimmed);
|
|
1076
|
+
while (parsed.length > 1 && parsed[0]?.value === '/') {
|
|
1077
|
+
parsed.shift();
|
|
1078
|
+
}
|
|
1079
|
+
const scores = parsed.map(d => {
|
|
1080
|
+
if (d.value === '/') {
|
|
1081
|
+
return 0.75;
|
|
1082
|
+
}
|
|
1083
|
+
if (d.type === 'param') {
|
|
1084
|
+
return 0.5;
|
|
1085
|
+
}
|
|
1086
|
+
if (d.type === 'wildcard') {
|
|
1087
|
+
return 0.25;
|
|
1088
|
+
}
|
|
1089
|
+
return 1;
|
|
1090
|
+
});
|
|
1091
|
+
scoredRoutes.push({
|
|
1092
|
+
child: d,
|
|
1093
|
+
trimmed,
|
|
1094
|
+
parsed,
|
|
1095
|
+
index: i,
|
|
1096
|
+
scores
|
|
1097
|
+
});
|
|
984
1098
|
});
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1099
|
+
this.flatRoutes = scoredRoutes.sort((a, b) => {
|
|
1100
|
+
const minLength = Math.min(a.scores.length, b.scores.length);
|
|
1101
|
+
|
|
1102
|
+
// Sort by min available score
|
|
1103
|
+
for (let i = 0; i < minLength; i++) {
|
|
1104
|
+
if (a.scores[i] !== b.scores[i]) {
|
|
1105
|
+
return b.scores[i] - a.scores[i];
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// Sort by length of score
|
|
1110
|
+
if (a.scores.length !== b.scores.length) {
|
|
1111
|
+
return b.scores.length - a.scores.length;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// Sort by min available parsed value
|
|
1115
|
+
for (let i = 0; i < minLength; i++) {
|
|
1116
|
+
if (a.parsed[i].value !== b.parsed[i].value) {
|
|
1117
|
+
return a.parsed[i].value > b.parsed[i].value ? 1 : -1;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// Sort by original index
|
|
1122
|
+
return a.index - b.index;
|
|
1123
|
+
}).map((d, i) => {
|
|
1124
|
+
d.child.rank = i;
|
|
1125
|
+
return d.child;
|
|
991
1126
|
});
|
|
992
1127
|
};
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
}
|
|
1128
|
+
subscribe = (eventType, fn) => {
|
|
1129
|
+
const listener = {
|
|
1130
|
+
eventType,
|
|
1131
|
+
fn
|
|
1132
|
+
};
|
|
1133
|
+
this.subscribers.add(listener);
|
|
1134
|
+
return () => {
|
|
1135
|
+
this.subscribers.delete(listener);
|
|
1136
|
+
};
|
|
998
1137
|
};
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1138
|
+
emit = routerEvent => {
|
|
1139
|
+
this.subscribers.forEach(listener => {
|
|
1140
|
+
if (listener.eventType === routerEvent.type) {
|
|
1141
|
+
listener.fn(routerEvent);
|
|
1142
|
+
}
|
|
1003
1143
|
});
|
|
1004
1144
|
};
|
|
1005
|
-
|
|
1006
|
-
return
|
|
1007
|
-
...opts,
|
|
1008
|
-
from: this.id
|
|
1009
|
-
});
|
|
1145
|
+
checkLatest = promise => {
|
|
1146
|
+
return this.latestLoadPromise !== promise ? this.latestLoadPromise : undefined;
|
|
1010
1147
|
};
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1148
|
+
parseLocation = previousLocation => {
|
|
1149
|
+
const parse = ({
|
|
1150
|
+
pathname,
|
|
1151
|
+
search,
|
|
1152
|
+
hash,
|
|
1153
|
+
state
|
|
1154
|
+
}) => {
|
|
1155
|
+
const parsedSearch = this.options.parseSearch(search);
|
|
1156
|
+
return {
|
|
1157
|
+
pathname: pathname,
|
|
1158
|
+
searchStr: search,
|
|
1159
|
+
search: replaceEqualDeep(previousLocation?.search, parsedSearch),
|
|
1160
|
+
hash: hash.split('#').reverse()[0] ?? '',
|
|
1161
|
+
href: `${pathname}${search}${hash}`,
|
|
1162
|
+
state: replaceEqualDeep(previousLocation?.state, state)
|
|
1163
|
+
};
|
|
1164
|
+
};
|
|
1165
|
+
const location = parse(this.history.location);
|
|
1166
|
+
let {
|
|
1167
|
+
__tempLocation,
|
|
1168
|
+
__tempKey
|
|
1169
|
+
} = location.state;
|
|
1170
|
+
if (__tempLocation && (!__tempKey || __tempKey === this.tempLocationKey)) {
|
|
1171
|
+
// Sync up the location keys
|
|
1172
|
+
const parsedTempLocation = parse(__tempLocation);
|
|
1173
|
+
parsedTempLocation.state.key = location.state.key;
|
|
1174
|
+
delete parsedTempLocation.state.__tempLocation;
|
|
1175
|
+
return {
|
|
1176
|
+
...parsedTempLocation,
|
|
1177
|
+
maskedLocation: location
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
return location;
|
|
1016
1181
|
};
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
return options => {
|
|
1020
|
-
return new RootRoute(options);
|
|
1182
|
+
resolvePathWithBase = (from, path) => {
|
|
1183
|
+
return resolvePath(this.basepath, from, cleanPath(path));
|
|
1021
1184
|
};
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
constructor(options) {
|
|
1025
|
-
super(options);
|
|
1185
|
+
get looseRoutesById() {
|
|
1186
|
+
return this.routesById;
|
|
1026
1187
|
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1188
|
+
matchRoutes = (pathname, locationSearch, opts) => {
|
|
1189
|
+
let routeParams = {};
|
|
1190
|
+
let foundRoute = this.flatRoutes.find(route => {
|
|
1191
|
+
const matchedParams = matchPathname(this.basepath, trimPathRight(pathname), {
|
|
1192
|
+
to: route.fullPath,
|
|
1193
|
+
caseSensitive: route.options.caseSensitive ?? this.options.caseSensitive,
|
|
1194
|
+
fuzzy: true
|
|
1195
|
+
});
|
|
1196
|
+
if (matchedParams) {
|
|
1197
|
+
routeParams = matchedParams;
|
|
1198
|
+
return true;
|
|
1199
|
+
}
|
|
1200
|
+
return false;
|
|
1039
1201
|
});
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
class FileRoute {
|
|
1044
|
-
constructor(path) {
|
|
1045
|
-
this.path = path;
|
|
1046
|
-
}
|
|
1047
|
-
createRoute = options => {
|
|
1048
|
-
const route = new Route(options);
|
|
1049
|
-
route.isRoot = false;
|
|
1050
|
-
return route;
|
|
1051
|
-
};
|
|
1052
|
-
}
|
|
1053
|
-
function FileRouteLoader(_path) {
|
|
1054
|
-
return loaderFn => loaderFn;
|
|
1055
|
-
}
|
|
1202
|
+
let routeCursor = foundRoute || this.routesById['__root__'];
|
|
1203
|
+
let matchedRoutes = [routeCursor];
|
|
1056
1204
|
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1205
|
+
// Check to see if the route needs a 404 entry
|
|
1206
|
+
if (
|
|
1207
|
+
// If we found a route, and it's not an index route and we have left over path
|
|
1208
|
+
(foundRoute ? foundRoute.path !== '/' && routeParams['**'] :
|
|
1209
|
+
// Or if we didn't find a route and we have left over path
|
|
1210
|
+
trimPathRight(pathname)) &&
|
|
1211
|
+
// And we have a 404 route configured
|
|
1212
|
+
this.options.notFoundRoute) {
|
|
1213
|
+
matchedRoutes.push(this.options.notFoundRoute);
|
|
1214
|
+
}
|
|
1215
|
+
while (routeCursor?.parentRoute) {
|
|
1216
|
+
routeCursor = routeCursor.parentRoute;
|
|
1217
|
+
if (routeCursor) matchedRoutes.unshift(routeCursor);
|
|
1062
1218
|
}
|
|
1063
|
-
return loadPromise;
|
|
1064
|
-
};
|
|
1065
|
-
const lazyComp = /*#__PURE__*/React.lazy(async () => {
|
|
1066
|
-
const moduleExports = await load();
|
|
1067
|
-
const comp = moduleExports[exportName ?? 'default'];
|
|
1068
|
-
return {
|
|
1069
|
-
default: comp
|
|
1070
|
-
};
|
|
1071
|
-
});
|
|
1072
|
-
lazyComp.preload = load;
|
|
1073
|
-
return lazyComp;
|
|
1074
|
-
}
|
|
1075
1219
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1220
|
+
// Existing matches are matches that are already loaded along with
|
|
1221
|
+
// pending matches that are still loading
|
|
1222
|
+
|
|
1223
|
+
const parseErrors = matchedRoutes.map(route => {
|
|
1224
|
+
let parsedParamsError;
|
|
1225
|
+
if (route.options.parseParams) {
|
|
1226
|
+
try {
|
|
1227
|
+
const parsedParams = route.options.parseParams(routeParams);
|
|
1228
|
+
// Add the parsed params to the accumulated params bag
|
|
1229
|
+
Object.assign(routeParams, parsedParams);
|
|
1230
|
+
} catch (err) {
|
|
1231
|
+
parsedParamsError = new PathParamError(err.message, {
|
|
1232
|
+
cause: err
|
|
1233
|
+
});
|
|
1234
|
+
if (opts?.throwOnError) {
|
|
1235
|
+
throw parsedParamsError;
|
|
1236
|
+
}
|
|
1237
|
+
return parsedParamsError;
|
|
1083
1238
|
}
|
|
1084
1239
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
strict: false,
|
|
1096
|
-
select: s => s.pathname
|
|
1097
|
-
});
|
|
1098
|
-
const {
|
|
1099
|
-
// custom props
|
|
1100
|
-
children,
|
|
1101
|
-
target,
|
|
1102
|
-
activeProps = () => ({
|
|
1103
|
-
className: 'active'
|
|
1104
|
-
}),
|
|
1105
|
-
inactiveProps = () => ({}),
|
|
1106
|
-
activeOptions,
|
|
1107
|
-
disabled,
|
|
1108
|
-
hash,
|
|
1109
|
-
search,
|
|
1110
|
-
params,
|
|
1111
|
-
to,
|
|
1112
|
-
state,
|
|
1113
|
-
mask,
|
|
1114
|
-
preload: userPreload,
|
|
1115
|
-
preloadDelay: userPreloadDelay,
|
|
1116
|
-
replace,
|
|
1117
|
-
startTransition,
|
|
1118
|
-
resetScroll,
|
|
1119
|
-
// element props
|
|
1120
|
-
style,
|
|
1121
|
-
className,
|
|
1122
|
-
onClick,
|
|
1123
|
-
onFocus,
|
|
1124
|
-
onMouseEnter,
|
|
1125
|
-
onMouseLeave,
|
|
1126
|
-
onTouchStart,
|
|
1127
|
-
...rest
|
|
1128
|
-
} = options;
|
|
1129
|
-
|
|
1130
|
-
// If this link simply reloads the current route,
|
|
1131
|
-
// make sure it has a new key so it will trigger a data refresh
|
|
1240
|
+
return;
|
|
1241
|
+
});
|
|
1242
|
+
const matches = [];
|
|
1243
|
+
matchedRoutes.forEach((route, index) => {
|
|
1244
|
+
// Take each matched route and resolve + validate its search params
|
|
1245
|
+
// This has to happen serially because each route's search params
|
|
1246
|
+
// can depend on the parent route's search params
|
|
1247
|
+
// It must also happen before we create the match so that we can
|
|
1248
|
+
// pass the search params to the route's potential key function
|
|
1249
|
+
// which is used to uniquely identify the route match in state
|
|
1132
1250
|
|
|
1133
|
-
|
|
1134
|
-
|
|
1251
|
+
const parentMatch = matches[index - 1];
|
|
1252
|
+
const [preMatchSearch, searchError] = (() => {
|
|
1253
|
+
// Validate the search params and stabilize them
|
|
1254
|
+
const parentSearch = parentMatch?.search ?? locationSearch;
|
|
1255
|
+
try {
|
|
1256
|
+
const validator = typeof route.options.validateSearch === 'object' ? route.options.validateSearch.parse : route.options.validateSearch;
|
|
1257
|
+
let search = validator?.(parentSearch) ?? {};
|
|
1258
|
+
return [{
|
|
1259
|
+
...parentSearch,
|
|
1260
|
+
...search
|
|
1261
|
+
}, undefined];
|
|
1262
|
+
} catch (err) {
|
|
1263
|
+
const searchError = new SearchParamError(err.message, {
|
|
1264
|
+
cause: err
|
|
1265
|
+
});
|
|
1266
|
+
if (opts?.throwOnError) {
|
|
1267
|
+
throw searchError;
|
|
1268
|
+
}
|
|
1269
|
+
return [parentSearch, searchError];
|
|
1270
|
+
}
|
|
1271
|
+
})();
|
|
1135
1272
|
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
let type = 'internal';
|
|
1141
|
-
try {
|
|
1142
|
-
new URL(`${to}`);
|
|
1143
|
-
type = 'external';
|
|
1144
|
-
} catch {}
|
|
1145
|
-
if (type === 'external') {
|
|
1146
|
-
return {
|
|
1147
|
-
href: to
|
|
1148
|
-
};
|
|
1149
|
-
}
|
|
1150
|
-
const next = router.buildLocation(dest);
|
|
1151
|
-
const preload = userPreload ?? router.options.defaultPreload;
|
|
1152
|
-
const preloadDelay = userPreloadDelay ?? router.options.defaultPreloadDelay ?? 0;
|
|
1153
|
-
const isActive = useRouterState({
|
|
1154
|
-
select: s => {
|
|
1155
|
-
// Compare path/hash for matches
|
|
1156
|
-
const currentPathSplit = s.location.pathname.split('/');
|
|
1157
|
-
const nextPathSplit = next.pathname.split('/');
|
|
1158
|
-
const pathIsFuzzyEqual = nextPathSplit.every((d, i) => d === currentPathSplit[i]);
|
|
1159
|
-
// Combine the matches based on user router.options
|
|
1160
|
-
const pathTest = activeOptions?.exact ? s.location.pathname === next.pathname : pathIsFuzzyEqual;
|
|
1161
|
-
const hashTest = activeOptions?.includeHash ? s.location.hash === next.hash : true;
|
|
1162
|
-
const searchTest = activeOptions?.includeSearch ?? true ? deepEqual(s.location.search, next.search, !activeOptions?.exact) : true;
|
|
1273
|
+
// This is where we need to call route.options.loaderDeps() to get any additional
|
|
1274
|
+
// deps that the route's loader function might need to run. We need to do this
|
|
1275
|
+
// before we create the match so that we can pass the deps to the route's
|
|
1276
|
+
// potential key function which is used to uniquely identify the route match in state
|
|
1163
1277
|
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1278
|
+
const loaderDeps = route.options.loaderDeps?.({
|
|
1279
|
+
search: preMatchSearch
|
|
1280
|
+
}) ?? '';
|
|
1281
|
+
const loaderDepsHash = loaderDeps ? JSON.stringify(loaderDeps) : '';
|
|
1282
|
+
const interpolatedPath = interpolatePath(route.fullPath, routeParams);
|
|
1283
|
+
const matchId = interpolatePath(route.id, routeParams, true) + loaderDepsHash;
|
|
1168
1284
|
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1285
|
+
// Waste not, want not. If we already have a match for this route,
|
|
1286
|
+
// reuse it. This is important for layout routes, which might stick
|
|
1287
|
+
// around between navigation actions that only change leaf routes.
|
|
1288
|
+
const existingMatch = getRouteMatch(this.state, matchId);
|
|
1289
|
+
const cause = this.state.matches.find(d => d.id === matchId) ? 'stay' : 'enter';
|
|
1173
1290
|
|
|
1174
|
-
//
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1291
|
+
// Create a fresh route match
|
|
1292
|
+
const hasLoaders = !!(route.options.loader || componentTypes.some(d => route.options[d]?.preload));
|
|
1293
|
+
const match = existingMatch ? {
|
|
1294
|
+
...existingMatch,
|
|
1295
|
+
cause
|
|
1296
|
+
} : {
|
|
1297
|
+
id: matchId,
|
|
1298
|
+
routeId: route.id,
|
|
1299
|
+
params: routeParams,
|
|
1300
|
+
pathname: joinPaths([this.basepath, interpolatedPath]),
|
|
1301
|
+
updatedAt: Date.now(),
|
|
1302
|
+
search: {},
|
|
1303
|
+
searchError: undefined,
|
|
1304
|
+
status: hasLoaders ? 'pending' : 'success',
|
|
1305
|
+
showPending: false,
|
|
1306
|
+
isFetching: false,
|
|
1307
|
+
error: undefined,
|
|
1308
|
+
paramsError: parseErrors[index],
|
|
1309
|
+
loadPromise: Promise.resolve(),
|
|
1310
|
+
routeContext: undefined,
|
|
1311
|
+
context: undefined,
|
|
1312
|
+
abortController: new AbortController(),
|
|
1313
|
+
fetchCount: 0,
|
|
1314
|
+
cause,
|
|
1315
|
+
loaderDeps,
|
|
1316
|
+
invalid: false,
|
|
1317
|
+
preload: false
|
|
1318
|
+
};
|
|
1183
1319
|
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
};
|
|
1193
|
-
const handleTouchStart = e => {
|
|
1194
|
-
if (preload) {
|
|
1195
|
-
router.preloadRoute(dest).catch(err => {
|
|
1196
|
-
console.warn(err);
|
|
1197
|
-
console.warn(preloadWarning);
|
|
1198
|
-
});
|
|
1199
|
-
}
|
|
1200
|
-
};
|
|
1201
|
-
const handleEnter = e => {
|
|
1202
|
-
const target = e.target || {};
|
|
1203
|
-
if (preload) {
|
|
1204
|
-
if (target.preloadTimeout) {
|
|
1205
|
-
return;
|
|
1206
|
-
}
|
|
1207
|
-
target.preloadTimeout = setTimeout(() => {
|
|
1208
|
-
target.preloadTimeout = null;
|
|
1209
|
-
router.preloadRoute(dest).catch(err => {
|
|
1210
|
-
console.warn(err);
|
|
1211
|
-
console.warn(preloadWarning);
|
|
1212
|
-
});
|
|
1213
|
-
}, preloadDelay);
|
|
1214
|
-
}
|
|
1320
|
+
// Regardless of whether we're reusing an existing match or creating
|
|
1321
|
+
// a new one, we need to update the match's search params
|
|
1322
|
+
match.search = replaceEqualDeep(match.search, preMatchSearch);
|
|
1323
|
+
// And also update the searchError if there is one
|
|
1324
|
+
match.searchError = searchError;
|
|
1325
|
+
matches.push(match);
|
|
1326
|
+
});
|
|
1327
|
+
return matches;
|
|
1215
1328
|
};
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
if (target.preloadTimeout) {
|
|
1219
|
-
clearTimeout(target.preloadTimeout);
|
|
1220
|
-
target.preloadTimeout = null;
|
|
1221
|
-
}
|
|
1329
|
+
cancelMatch = id => {
|
|
1330
|
+
getRouteMatch(this.state, id)?.abortController?.abort();
|
|
1222
1331
|
};
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
if (e.defaultPrevented) return;
|
|
1227
|
-
handler(e);
|
|
1332
|
+
cancelMatches = () => {
|
|
1333
|
+
this.state.pendingMatches?.forEach(match => {
|
|
1334
|
+
this.cancelMatch(match.id);
|
|
1228
1335
|
});
|
|
1229
1336
|
};
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
...style,
|
|
1249
|
-
...resolvedActiveProps.style,
|
|
1250
|
-
...resolvedInactiveProps.style
|
|
1251
|
-
},
|
|
1252
|
-
className: [className, resolvedActiveProps.className, resolvedInactiveProps.className].filter(Boolean).join(' ') || undefined,
|
|
1253
|
-
...(disabled ? {
|
|
1254
|
-
role: 'link',
|
|
1255
|
-
'aria-disabled': true
|
|
1256
|
-
} : undefined),
|
|
1257
|
-
['data-status']: isActive ? 'active' : undefined
|
|
1258
|
-
};
|
|
1259
|
-
}
|
|
1260
|
-
const Link = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
1261
|
-
const linkProps = useLinkProps(props);
|
|
1262
|
-
return /*#__PURE__*/React.createElement("a", _extends({
|
|
1263
|
-
ref: ref
|
|
1264
|
-
}, linkProps, {
|
|
1265
|
-
children: typeof props.children === 'function' ? props.children({
|
|
1266
|
-
isActive: linkProps['data-status'] === 'active'
|
|
1267
|
-
}) : props.children
|
|
1268
|
-
}));
|
|
1269
|
-
});
|
|
1270
|
-
function isCtrlEvent(e) {
|
|
1271
|
-
return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey);
|
|
1272
|
-
}
|
|
1273
|
-
|
|
1274
|
-
// @ts-nocheck
|
|
1275
|
-
|
|
1276
|
-
// qss has been slightly modified and inlined here for our use cases (and compression's sake). We've included it as a hard dependency for MIT license attribution.
|
|
1277
|
-
|
|
1278
|
-
function encode(obj, pfx) {
|
|
1279
|
-
var k,
|
|
1280
|
-
i,
|
|
1281
|
-
tmp,
|
|
1282
|
-
str = '';
|
|
1283
|
-
for (k in obj) {
|
|
1284
|
-
if ((tmp = obj[k]) !== void 0) {
|
|
1285
|
-
if (Array.isArray(tmp)) {
|
|
1286
|
-
for (i = 0; i < tmp.length; i++) {
|
|
1287
|
-
str && (str += '&');
|
|
1288
|
-
str += encodeURIComponent(k) + '=' + encodeURIComponent(tmp[i]);
|
|
1289
|
-
}
|
|
1290
|
-
} else {
|
|
1291
|
-
str && (str += '&');
|
|
1292
|
-
str += encodeURIComponent(k) + '=' + encodeURIComponent(tmp);
|
|
1337
|
+
buildLocation = opts => {
|
|
1338
|
+
const build = (dest = {}, matches) => {
|
|
1339
|
+
const relevantMatches = this.state.pendingMatches || this.state.matches;
|
|
1340
|
+
const fromSearch = relevantMatches[relevantMatches.length - 1]?.search || this.latestLocation.search;
|
|
1341
|
+
let pathname = this.resolvePathWithBase(dest.from ?? this.latestLocation.pathname, `${dest.to ?? ''}`);
|
|
1342
|
+
const fromMatches = this.matchRoutes(this.latestLocation.pathname, fromSearch);
|
|
1343
|
+
const stayingMatches = matches?.filter(d => fromMatches?.find(e => e.routeId === d.routeId));
|
|
1344
|
+
const prevParams = {
|
|
1345
|
+
...last(fromMatches)?.params
|
|
1346
|
+
};
|
|
1347
|
+
let nextParams = (dest.params ?? true) === true ? prevParams : functionalUpdate(dest.params, prevParams);
|
|
1348
|
+
if (nextParams) {
|
|
1349
|
+
matches?.map(d => this.looseRoutesById[d.routeId].options.stringifyParams).filter(Boolean).forEach(fn => {
|
|
1350
|
+
nextParams = {
|
|
1351
|
+
...nextParams,
|
|
1352
|
+
...fn(nextParams)
|
|
1353
|
+
};
|
|
1354
|
+
});
|
|
1293
1355
|
}
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
}
|
|
1298
|
-
function toValue(mix) {
|
|
1299
|
-
if (!mix) return '';
|
|
1300
|
-
var str = decodeURIComponent(mix);
|
|
1301
|
-
if (str === 'false') return false;
|
|
1302
|
-
if (str === 'true') return true;
|
|
1303
|
-
return +str * 0 === 0 && +str + '' === str ? +str : str;
|
|
1304
|
-
}
|
|
1305
|
-
function decode(str) {
|
|
1306
|
-
var tmp,
|
|
1307
|
-
k,
|
|
1308
|
-
out = {},
|
|
1309
|
-
arr = str.split('&');
|
|
1310
|
-
while (tmp = arr.shift()) {
|
|
1311
|
-
tmp = tmp.split('=');
|
|
1312
|
-
k = tmp.shift();
|
|
1313
|
-
if (out[k] !== void 0) {
|
|
1314
|
-
out[k] = [].concat(out[k], toValue(tmp.shift()));
|
|
1315
|
-
} else {
|
|
1316
|
-
out[k] = toValue(tmp.shift());
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
return out;
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
// Detect if we're in the DOM
|
|
1356
|
+
pathname = interpolatePath(pathname, nextParams ?? {});
|
|
1357
|
+
const preSearchFilters = stayingMatches?.map(match => this.looseRoutesById[match.routeId].options.preSearchFilters ?? []).flat().filter(Boolean) ?? [];
|
|
1358
|
+
const postSearchFilters = stayingMatches?.map(match => this.looseRoutesById[match.routeId].options.postSearchFilters ?? []).flat().filter(Boolean) ?? [];
|
|
1323
1359
|
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
if (opts.throw) {
|
|
1327
|
-
throw opts;
|
|
1328
|
-
}
|
|
1329
|
-
return opts;
|
|
1330
|
-
}
|
|
1331
|
-
function isRedirect(obj) {
|
|
1332
|
-
return !!obj?.isRedirect;
|
|
1333
|
-
}
|
|
1360
|
+
// Pre filters first
|
|
1361
|
+
const preFilteredSearch = preSearchFilters?.length ? preSearchFilters?.reduce((prev, next) => next(prev), fromSearch) : fromSearch;
|
|
1334
1362
|
|
|
1335
|
-
|
|
1336
|
-
const
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
searchStr = searchStr.substring(1);
|
|
1341
|
-
}
|
|
1342
|
-
let query = decode(searchStr);
|
|
1363
|
+
// Then the link/navigate function
|
|
1364
|
+
const destSearch = dest.search === true ? preFilteredSearch // Preserve resolvedFrom true
|
|
1365
|
+
: dest.search ? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
|
|
1366
|
+
: preSearchFilters?.length ? preFilteredSearch // Preserve resolvedFrom filters
|
|
1367
|
+
: {};
|
|
1343
1368
|
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
const
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1369
|
+
// Then post filters
|
|
1370
|
+
const postFilteredSearch = postSearchFilters?.length ? postSearchFilters.reduce((prev, next) => next(prev), destSearch) : destSearch;
|
|
1371
|
+
const search = replaceEqualDeep(fromSearch, postFilteredSearch);
|
|
1372
|
+
const searchStr = this.options.stringifySearch(search);
|
|
1373
|
+
const hash = dest.hash === true ? this.latestLocation.hash : dest.hash ? functionalUpdate(dest.hash, this.latestLocation.hash) : undefined;
|
|
1374
|
+
const hashStr = hash ? `#${hash}` : '';
|
|
1375
|
+
let nextState = dest.state === true ? this.latestLocation.state : dest.state ? functionalUpdate(dest.state, this.latestLocation.state) : this.latestLocation.state;
|
|
1376
|
+
nextState = replaceEqualDeep(this.latestLocation.state, nextState);
|
|
1377
|
+
return {
|
|
1378
|
+
pathname,
|
|
1379
|
+
search,
|
|
1380
|
+
searchStr,
|
|
1381
|
+
state: nextState,
|
|
1382
|
+
hash: hash ?? '',
|
|
1383
|
+
href: `${pathname}${searchStr}${hashStr}`,
|
|
1384
|
+
unmaskOnReload: dest.unmaskOnReload
|
|
1385
|
+
};
|
|
1386
|
+
};
|
|
1387
|
+
const buildWithMatches = (dest = {}, maskedDest) => {
|
|
1388
|
+
let next = build(dest);
|
|
1389
|
+
let maskedNext = maskedDest ? build(maskedDest) : undefined;
|
|
1390
|
+
if (!maskedNext) {
|
|
1391
|
+
let params = {};
|
|
1392
|
+
let foundMask = this.options.routeMasks?.find(d => {
|
|
1393
|
+
const match = matchPathname(this.basepath, next.pathname, {
|
|
1394
|
+
to: d.from,
|
|
1395
|
+
caseSensitive: false,
|
|
1396
|
+
fuzzy: false
|
|
1397
|
+
});
|
|
1398
|
+
if (match) {
|
|
1399
|
+
params = match;
|
|
1400
|
+
return true;
|
|
1401
|
+
}
|
|
1402
|
+
return false;
|
|
1403
|
+
});
|
|
1404
|
+
if (foundMask) {
|
|
1405
|
+
maskedDest = {
|
|
1406
|
+
...pick(opts, ['from']),
|
|
1407
|
+
...foundMask,
|
|
1408
|
+
params
|
|
1409
|
+
};
|
|
1410
|
+
maskedNext = build(maskedDest);
|
|
1352
1411
|
}
|
|
1353
1412
|
}
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
if (typeof val === 'object' && val !== null) {
|
|
1361
|
-
try {
|
|
1362
|
-
return stringify(val);
|
|
1363
|
-
} catch (err) {
|
|
1364
|
-
// silent
|
|
1365
|
-
}
|
|
1366
|
-
} else if (typeof val === 'string' && typeof parser === 'function') {
|
|
1367
|
-
try {
|
|
1368
|
-
// Check if it's a valid parseable string.
|
|
1369
|
-
// If it is, then stringify it again.
|
|
1370
|
-
parser(val);
|
|
1371
|
-
return stringify(val);
|
|
1372
|
-
} catch (err) {
|
|
1373
|
-
// silent
|
|
1413
|
+
const nextMatches = this.matchRoutes(next.pathname, next.search);
|
|
1414
|
+
const maskedMatches = maskedNext ? this.matchRoutes(maskedNext.pathname, maskedNext.search) : undefined;
|
|
1415
|
+
const maskedFinal = maskedNext ? build(maskedDest, maskedMatches) : undefined;
|
|
1416
|
+
const final = build(dest, nextMatches);
|
|
1417
|
+
if (maskedFinal) {
|
|
1418
|
+
final.maskedLocation = maskedFinal;
|
|
1374
1419
|
}
|
|
1375
|
-
|
|
1376
|
-
return val;
|
|
1377
|
-
}
|
|
1378
|
-
return search => {
|
|
1379
|
-
search = {
|
|
1380
|
-
...search
|
|
1420
|
+
return final;
|
|
1381
1421
|
};
|
|
1382
|
-
if (
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
delete search[key];
|
|
1387
|
-
} else {
|
|
1388
|
-
search[key] = stringifyValue(val);
|
|
1389
|
-
}
|
|
1422
|
+
if (opts.mask) {
|
|
1423
|
+
return buildWithMatches(opts, {
|
|
1424
|
+
...pick(opts, ['from']),
|
|
1425
|
+
...opts.mask
|
|
1390
1426
|
});
|
|
1391
1427
|
}
|
|
1392
|
-
|
|
1393
|
-
return searchStr ? `?${searchStr}` : '';
|
|
1428
|
+
return buildWithMatches(opts);
|
|
1394
1429
|
};
|
|
1395
|
-
|
|
1430
|
+
commitLocation = async ({
|
|
1431
|
+
startTransition,
|
|
1432
|
+
...next
|
|
1433
|
+
}) => {
|
|
1434
|
+
if (this.navigateTimeout) clearTimeout(this.navigateTimeout);
|
|
1435
|
+
const isSameUrl = this.latestLocation.href === next.href;
|
|
1396
1436
|
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
const routerState = useRouterState({
|
|
1426
|
-
select: s => pick(s, ['isLoading', 'location', 'resolvedLocation', 'isTransitioning'])
|
|
1427
|
-
});
|
|
1428
|
-
const [isTransitioning, startReactTransition] = useTransition();
|
|
1429
|
-
router.startReactTransition = startReactTransition;
|
|
1430
|
-
React.useEffect(() => {
|
|
1431
|
-
if (isTransitioning) {
|
|
1432
|
-
router.__store.setState(s => ({
|
|
1433
|
-
...s,
|
|
1434
|
-
isTransitioning
|
|
1435
|
-
}));
|
|
1436
|
-
}
|
|
1437
|
-
}, [isTransitioning]);
|
|
1438
|
-
const tryLoad = () => {
|
|
1439
|
-
const apply = cb => {
|
|
1440
|
-
if (!routerState.isTransitioning) {
|
|
1441
|
-
startReactTransition(() => cb());
|
|
1442
|
-
} else {
|
|
1443
|
-
cb();
|
|
1437
|
+
// If the next urls are the same and we're not replacing,
|
|
1438
|
+
// do nothing
|
|
1439
|
+
if (!isSameUrl || !next.replace) {
|
|
1440
|
+
let {
|
|
1441
|
+
maskedLocation,
|
|
1442
|
+
...nextHistory
|
|
1443
|
+
} = next;
|
|
1444
|
+
if (maskedLocation) {
|
|
1445
|
+
nextHistory = {
|
|
1446
|
+
...maskedLocation,
|
|
1447
|
+
state: {
|
|
1448
|
+
...maskedLocation.state,
|
|
1449
|
+
__tempKey: undefined,
|
|
1450
|
+
__tempLocation: {
|
|
1451
|
+
...nextHistory,
|
|
1452
|
+
search: nextHistory.searchStr,
|
|
1453
|
+
state: {
|
|
1454
|
+
...nextHistory.state,
|
|
1455
|
+
__tempKey: undefined,
|
|
1456
|
+
__tempLocation: undefined,
|
|
1457
|
+
key: undefined
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
};
|
|
1462
|
+
if (nextHistory.unmaskOnReload ?? this.options.unmaskOnReload ?? false) {
|
|
1463
|
+
nextHistory.state.__tempKey = this.tempLocationKey;
|
|
1464
|
+
}
|
|
1444
1465
|
}
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1466
|
+
const apply = () => {
|
|
1467
|
+
this.history[next.replace ? 'replace' : 'push'](nextHistory.href, nextHistory.state);
|
|
1468
|
+
};
|
|
1469
|
+
if (startTransition ?? true) {
|
|
1470
|
+
this.startReactTransition(apply);
|
|
1471
|
+
} else {
|
|
1472
|
+
apply();
|
|
1451
1473
|
}
|
|
1452
|
-
}
|
|
1474
|
+
}
|
|
1475
|
+
this.resetNextScroll = next.resetScroll ?? true;
|
|
1476
|
+
return this.latestLoadPromise;
|
|
1453
1477
|
};
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1478
|
+
buildAndCommitLocation = ({
|
|
1479
|
+
replace,
|
|
1480
|
+
resetScroll,
|
|
1481
|
+
startTransition,
|
|
1482
|
+
...rest
|
|
1483
|
+
} = {}) => {
|
|
1484
|
+
const location = this.buildLocation(rest);
|
|
1485
|
+
return this.commitLocation({
|
|
1486
|
+
...location,
|
|
1487
|
+
startTransition,
|
|
1488
|
+
replace,
|
|
1489
|
+
resetScroll
|
|
1460
1490
|
});
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1491
|
+
};
|
|
1492
|
+
navigate = ({
|
|
1493
|
+
from,
|
|
1494
|
+
to,
|
|
1495
|
+
...rest
|
|
1496
|
+
}) => {
|
|
1497
|
+
// If this link simply reloads the current route,
|
|
1498
|
+
// make sure it has a new key so it will trigger a data refresh
|
|
1499
|
+
|
|
1500
|
+
// If this `to` is a valid external URL, return
|
|
1501
|
+
// null for LinkUtils
|
|
1502
|
+
const toString = String(to);
|
|
1503
|
+
// const fromString = from !== undefined ? String(from) : from
|
|
1504
|
+
let isExternal;
|
|
1505
|
+
try {
|
|
1506
|
+
new URL(`${toString}`);
|
|
1507
|
+
isExternal = true;
|
|
1508
|
+
} catch (e) {}
|
|
1509
|
+
invariant(!isExternal, 'Attempting to navigate to external url with this.navigate!');
|
|
1510
|
+
return this.buildAndCommitLocation({
|
|
1511
|
+
...rest,
|
|
1512
|
+
from,
|
|
1513
|
+
to
|
|
1514
|
+
// to: toString,
|
|
1466
1515
|
});
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1516
|
+
};
|
|
1517
|
+
loadMatches = async ({
|
|
1518
|
+
checkLatest,
|
|
1519
|
+
matches,
|
|
1520
|
+
preload
|
|
1521
|
+
}) => {
|
|
1522
|
+
let latestPromise;
|
|
1523
|
+
let firstBadMatchIndex;
|
|
1524
|
+
const updateMatch = match => {
|
|
1525
|
+
// const isPreload = this.state.cachedMatches.find((d) => d.id === match.id)
|
|
1526
|
+
const isPending = this.state.pendingMatches?.find(d => d.id === match.id);
|
|
1527
|
+
const isMatched = this.state.matches.find(d => d.id === match.id);
|
|
1528
|
+
const matchesKey = isPending ? 'pendingMatches' : isMatched ? 'matches' : 'cachedMatches';
|
|
1529
|
+
this.__store.setState(s => ({
|
|
1530
|
+
...s,
|
|
1531
|
+
[matchesKey]: s[matchesKey]?.map(d => d.id === match.id ? match : d)
|
|
1532
|
+
}));
|
|
1475
1533
|
};
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1534
|
+
|
|
1535
|
+
// Check each match middleware to see if the route can be accessed
|
|
1536
|
+
try {
|
|
1537
|
+
for (let [index, match] of matches.entries()) {
|
|
1538
|
+
const parentMatch = matches[index - 1];
|
|
1539
|
+
const route = this.looseRoutesById[match.routeId];
|
|
1540
|
+
const abortController = new AbortController();
|
|
1541
|
+
const handleErrorAndRedirect = (err, code) => {
|
|
1542
|
+
err.routerCode = code;
|
|
1543
|
+
firstBadMatchIndex = firstBadMatchIndex ?? index;
|
|
1544
|
+
if (isRedirect(err)) {
|
|
1545
|
+
throw err;
|
|
1546
|
+
}
|
|
1547
|
+
try {
|
|
1548
|
+
route.options.onError?.(err);
|
|
1549
|
+
} catch (errorHandlerErr) {
|
|
1550
|
+
err = errorHandlerErr;
|
|
1551
|
+
if (isRedirect(errorHandlerErr)) {
|
|
1552
|
+
throw errorHandlerErr;
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
matches[index] = match = {
|
|
1556
|
+
...match,
|
|
1557
|
+
error: err,
|
|
1558
|
+
status: 'error',
|
|
1559
|
+
updatedAt: Date.now(),
|
|
1560
|
+
abortController: new AbortController()
|
|
1561
|
+
};
|
|
1562
|
+
};
|
|
1563
|
+
try {
|
|
1564
|
+
if (match.paramsError) {
|
|
1565
|
+
handleErrorAndRedirect(match.paramsError, 'PARSE_PARAMS');
|
|
1566
|
+
}
|
|
1567
|
+
if (match.searchError) {
|
|
1568
|
+
handleErrorAndRedirect(match.searchError, 'VALIDATE_SEARCH');
|
|
1569
|
+
}
|
|
1570
|
+
const parentContext = parentMatch?.context ?? this.options.context ?? {};
|
|
1571
|
+
const pendingMs = route.options.pendingMs ?? this.options.defaultPendingMs;
|
|
1572
|
+
const pendingPromise = typeof pendingMs === 'number' && pendingMs <= 0 ? Promise.resolve() : new Promise(r => setTimeout(r, pendingMs));
|
|
1573
|
+
const beforeLoadContext = (await route.options.beforeLoad?.({
|
|
1574
|
+
search: match.search,
|
|
1575
|
+
abortController,
|
|
1576
|
+
params: match.params,
|
|
1577
|
+
preload: !!preload,
|
|
1578
|
+
context: parentContext,
|
|
1579
|
+
location: this.state.location,
|
|
1580
|
+
// TOOD: just expose state and router, etc
|
|
1581
|
+
navigate: opts => this.navigate({
|
|
1582
|
+
...opts,
|
|
1583
|
+
from: match.pathname
|
|
1584
|
+
}),
|
|
1585
|
+
buildLocation: this.buildLocation,
|
|
1586
|
+
cause: preload ? 'preload' : match.cause
|
|
1587
|
+
})) ?? {};
|
|
1588
|
+
if (isRedirect(beforeLoadContext)) {
|
|
1589
|
+
throw beforeLoadContext;
|
|
1490
1590
|
}
|
|
1591
|
+
const context = {
|
|
1592
|
+
...parentContext,
|
|
1593
|
+
...beforeLoadContext
|
|
1594
|
+
};
|
|
1595
|
+
matches[index] = match = {
|
|
1596
|
+
...match,
|
|
1597
|
+
routeContext: replaceEqualDeep(match.routeContext, beforeLoadContext),
|
|
1598
|
+
context: replaceEqualDeep(match.context, context),
|
|
1599
|
+
abortController,
|
|
1600
|
+
pendingPromise
|
|
1601
|
+
};
|
|
1602
|
+
} catch (err) {
|
|
1603
|
+
handleErrorAndRedirect(err, 'BEFORE_LOAD');
|
|
1604
|
+
break;
|
|
1491
1605
|
}
|
|
1492
1606
|
}
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
}, [routerState.isTransitioning, isTransitioning, routerState.isLoading, routerState.resolvedLocation, routerState.location]);
|
|
1500
|
-
useLayoutEffect$1(() => {
|
|
1501
|
-
if (!window.__TSR_DEHYDRATED__ && !mountLoadCount.current) {
|
|
1502
|
-
mountLoadCount.current++;
|
|
1503
|
-
tryLoad();
|
|
1607
|
+
} catch (err) {
|
|
1608
|
+
if (isRedirect(err)) {
|
|
1609
|
+
if (!preload) this.navigate(err);
|
|
1610
|
+
return matches;
|
|
1611
|
+
}
|
|
1612
|
+
throw err;
|
|
1504
1613
|
}
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1614
|
+
const validResolvedMatches = matches.slice(0, firstBadMatchIndex);
|
|
1615
|
+
const matchPromises = [];
|
|
1616
|
+
validResolvedMatches.forEach((match, index) => {
|
|
1617
|
+
matchPromises.push(new Promise(async resolve => {
|
|
1618
|
+
const parentMatchPromise = matchPromises[index - 1];
|
|
1619
|
+
const route = this.looseRoutesById[match.routeId];
|
|
1620
|
+
const handleErrorAndRedirect = err => {
|
|
1621
|
+
if (isRedirect(err)) {
|
|
1622
|
+
if (!preload) {
|
|
1623
|
+
this.navigate(err);
|
|
1624
|
+
}
|
|
1625
|
+
return true;
|
|
1626
|
+
}
|
|
1627
|
+
return false;
|
|
1628
|
+
};
|
|
1629
|
+
let loadPromise;
|
|
1630
|
+
matches[index] = match = {
|
|
1631
|
+
...match,
|
|
1632
|
+
showPending: false
|
|
1633
|
+
};
|
|
1634
|
+
let didShowPending = false;
|
|
1635
|
+
const pendingMs = route.options.pendingMs ?? this.options.defaultPendingMs;
|
|
1636
|
+
const pendingMinMs = route.options.pendingMinMs ?? this.options.defaultPendingMinMs;
|
|
1637
|
+
const shouldPending = !preload && typeof pendingMs === 'number' && (route.options.pendingComponent ?? this.options.defaultPendingComponent);
|
|
1638
|
+
const loaderContext = {
|
|
1639
|
+
params: match.params,
|
|
1640
|
+
deps: match.loaderDeps,
|
|
1641
|
+
preload: !!preload,
|
|
1642
|
+
parentMatchPromise,
|
|
1643
|
+
abortController: match.abortController,
|
|
1644
|
+
context: match.context,
|
|
1645
|
+
location: this.state.location,
|
|
1646
|
+
navigate: opts => this.navigate({
|
|
1647
|
+
...opts,
|
|
1648
|
+
from: match.pathname
|
|
1649
|
+
}),
|
|
1650
|
+
cause: preload ? 'preload' : match.cause
|
|
1651
|
+
};
|
|
1652
|
+
const fetch = async () => {
|
|
1653
|
+
if (match.isFetching) {
|
|
1654
|
+
loadPromise = getRouteMatch(this.state, match.id)?.loadPromise;
|
|
1655
|
+
} else {
|
|
1656
|
+
// If the user doesn't want the route to reload, just
|
|
1657
|
+
// resolve with the existing loader data
|
|
1527
1658
|
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
defaultPendingMs: 1000,
|
|
1532
|
-
defaultPendingMinMs: 500,
|
|
1533
|
-
context: undefined,
|
|
1534
|
-
...options,
|
|
1535
|
-
stringifySearch: options?.stringifySearch ?? defaultStringifySearch,
|
|
1536
|
-
parseSearch: options?.parseSearch ?? defaultParseSearch
|
|
1537
|
-
});
|
|
1538
|
-
}
|
|
1659
|
+
if (match.fetchCount && match.status === 'success') {
|
|
1660
|
+
resolve();
|
|
1661
|
+
}
|
|
1539
1662
|
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
this.history = this.options.history ?? (typeof document !== 'undefined' ? createBrowserHistory() : createMemoryHistory({
|
|
1559
|
-
initialEntries: [this.options.basepath || '/']
|
|
1560
|
-
}));
|
|
1561
|
-
this.latestLocation = this.parseLocation();
|
|
1562
|
-
}
|
|
1563
|
-
if (this.options.routeTree !== this.routeTree) {
|
|
1564
|
-
this.routeTree = this.options.routeTree;
|
|
1565
|
-
this.buildRouteTree();
|
|
1566
|
-
}
|
|
1567
|
-
if (!this.__store) {
|
|
1568
|
-
this.__store = new Store(getInitialRouterState(this.latestLocation), {
|
|
1569
|
-
onUpdate: () => {
|
|
1570
|
-
this.__store.state = {
|
|
1571
|
-
...this.state,
|
|
1572
|
-
status: this.state.isTransitioning || this.state.isLoading ? 'pending' : 'idle'
|
|
1663
|
+
// Otherwise, load the route
|
|
1664
|
+
matches[index] = match = {
|
|
1665
|
+
...match,
|
|
1666
|
+
isFetching: true,
|
|
1667
|
+
fetchCount: match.fetchCount + 1
|
|
1668
|
+
};
|
|
1669
|
+
const componentsPromise = Promise.all(componentTypes.map(async type => {
|
|
1670
|
+
const component = route.options[type];
|
|
1671
|
+
if (component?.preload) {
|
|
1672
|
+
await component.preload();
|
|
1673
|
+
}
|
|
1674
|
+
}));
|
|
1675
|
+
const loaderPromise = route.options.loader?.(loaderContext);
|
|
1676
|
+
loadPromise = Promise.all([componentsPromise, loaderPromise]).then(d => d[1]);
|
|
1677
|
+
}
|
|
1678
|
+
matches[index] = match = {
|
|
1679
|
+
...match,
|
|
1680
|
+
loadPromise
|
|
1573
1681
|
};
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1682
|
+
updateMatch(match);
|
|
1683
|
+
try {
|
|
1684
|
+
const loaderData = await loadPromise;
|
|
1685
|
+
if (latestPromise = checkLatest()) return await latestPromise;
|
|
1686
|
+
if (isRedirect(loaderData)) {
|
|
1687
|
+
if (handleErrorAndRedirect(loaderData)) return;
|
|
1688
|
+
}
|
|
1689
|
+
if (didShowPending && pendingMinMs) {
|
|
1690
|
+
await new Promise(r => setTimeout(r, pendingMinMs));
|
|
1691
|
+
}
|
|
1692
|
+
if (latestPromise = checkLatest()) return await latestPromise;
|
|
1693
|
+
matches[index] = match = {
|
|
1694
|
+
...match,
|
|
1695
|
+
error: undefined,
|
|
1696
|
+
status: 'success',
|
|
1697
|
+
isFetching: false,
|
|
1698
|
+
updatedAt: Date.now(),
|
|
1699
|
+
loaderData,
|
|
1700
|
+
loadPromise: undefined
|
|
1701
|
+
};
|
|
1702
|
+
} catch (error) {
|
|
1703
|
+
if (latestPromise = checkLatest()) return await latestPromise;
|
|
1704
|
+
if (handleErrorAndRedirect(error)) return;
|
|
1705
|
+
try {
|
|
1706
|
+
route.options.onError?.(error);
|
|
1707
|
+
} catch (onErrorError) {
|
|
1708
|
+
error = onErrorError;
|
|
1709
|
+
if (handleErrorAndRedirect(onErrorError)) return;
|
|
1710
|
+
}
|
|
1711
|
+
matches[index] = match = {
|
|
1712
|
+
...match,
|
|
1713
|
+
error,
|
|
1714
|
+
status: 'error',
|
|
1715
|
+
isFetching: false
|
|
1716
|
+
};
|
|
1603
1717
|
}
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
if (children?.length) {
|
|
1607
|
-
recurseRoutes(children);
|
|
1608
|
-
}
|
|
1609
|
-
});
|
|
1610
|
-
};
|
|
1611
|
-
recurseRoutes([this.routeTree]);
|
|
1612
|
-
const scoredRoutes = [];
|
|
1613
|
-
Object.values(this.routesById).forEach((d, i) => {
|
|
1614
|
-
if (d.isRoot || !d.path) {
|
|
1615
|
-
return;
|
|
1616
|
-
}
|
|
1617
|
-
const trimmed = trimPathLeft(d.fullPath);
|
|
1618
|
-
const parsed = parsePathname(trimmed);
|
|
1619
|
-
while (parsed.length > 1 && parsed[0]?.value === '/') {
|
|
1620
|
-
parsed.shift();
|
|
1621
|
-
}
|
|
1622
|
-
const scores = parsed.map(d => {
|
|
1623
|
-
if (d.value === '/') {
|
|
1624
|
-
return 0.75;
|
|
1625
|
-
}
|
|
1626
|
-
if (d.type === 'param') {
|
|
1627
|
-
return 0.5;
|
|
1628
|
-
}
|
|
1629
|
-
if (d.type === 'wildcard') {
|
|
1630
|
-
return 0.25;
|
|
1631
|
-
}
|
|
1632
|
-
return 1;
|
|
1633
|
-
});
|
|
1634
|
-
scoredRoutes.push({
|
|
1635
|
-
child: d,
|
|
1636
|
-
trimmed,
|
|
1637
|
-
parsed,
|
|
1638
|
-
index: i,
|
|
1639
|
-
scores
|
|
1640
|
-
});
|
|
1641
|
-
});
|
|
1642
|
-
this.flatRoutes = scoredRoutes.sort((a, b) => {
|
|
1643
|
-
const minLength = Math.min(a.scores.length, b.scores.length);
|
|
1644
|
-
|
|
1645
|
-
// Sort by min available score
|
|
1646
|
-
for (let i = 0; i < minLength; i++) {
|
|
1647
|
-
if (a.scores[i] !== b.scores[i]) {
|
|
1648
|
-
return b.scores[i] - a.scores[i];
|
|
1649
|
-
}
|
|
1650
|
-
}
|
|
1718
|
+
updateMatch(match);
|
|
1719
|
+
};
|
|
1651
1720
|
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1721
|
+
// This is where all of the stale-while-revalidate magic happens
|
|
1722
|
+
const age = Date.now() - match.updatedAt;
|
|
1723
|
+
let staleAge = preload ? route.options.preloadStaleTime ?? this.options.defaultPreloadStaleTime ?? 30_000 // 30 seconds for preloads by default
|
|
1724
|
+
: route.options.staleTime ?? this.options.defaultStaleTime ?? 0;
|
|
1656
1725
|
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
return a.parsed[i].value > b.parsed[i].value ? 1 : -1;
|
|
1661
|
-
}
|
|
1662
|
-
}
|
|
1726
|
+
// Default to reloading the route all the time
|
|
1727
|
+
let shouldReload;
|
|
1728
|
+
const shouldReloadOption = route.options.shouldReload;
|
|
1663
1729
|
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1730
|
+
// Allow shouldReload to get the last say,
|
|
1731
|
+
// if provided.
|
|
1732
|
+
shouldReload = typeof shouldReloadOption === 'function' ? shouldReloadOption(loaderContext) : shouldReloadOption;
|
|
1733
|
+
matches[index] = match = {
|
|
1734
|
+
...match,
|
|
1735
|
+
preload: !!preload && !this.state.matches.find(d => d.id === match.id)
|
|
1736
|
+
};
|
|
1737
|
+
if (match.status !== 'success') {
|
|
1738
|
+
// If we need to potentially show the pending component,
|
|
1739
|
+
// start a timer to show it after the pendingMs
|
|
1740
|
+
if (shouldPending) {
|
|
1741
|
+
match.pendingPromise?.then(async () => {
|
|
1742
|
+
if (latestPromise = checkLatest()) return latestPromise;
|
|
1743
|
+
didShowPending = true;
|
|
1744
|
+
matches[index] = match = {
|
|
1745
|
+
...match,
|
|
1746
|
+
showPending: true
|
|
1747
|
+
};
|
|
1748
|
+
updateMatch(match);
|
|
1749
|
+
resolve();
|
|
1750
|
+
});
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
// Critical Fetching, we need to await
|
|
1754
|
+
await fetch();
|
|
1755
|
+
} else if (match.invalid || (shouldReload ?? age > staleAge)) {
|
|
1756
|
+
// Background Fetching, no need to wait
|
|
1757
|
+
fetch();
|
|
1758
|
+
}
|
|
1759
|
+
resolve();
|
|
1760
|
+
}));
|
|
1686
1761
|
});
|
|
1762
|
+
await Promise.all(matchPromises);
|
|
1763
|
+
return matches;
|
|
1687
1764
|
};
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
const parse = ({
|
|
1693
|
-
pathname,
|
|
1694
|
-
search,
|
|
1695
|
-
hash,
|
|
1696
|
-
state
|
|
1697
|
-
}) => {
|
|
1698
|
-
const parsedSearch = this.options.parseSearch(search);
|
|
1699
|
-
return {
|
|
1700
|
-
pathname: pathname,
|
|
1701
|
-
searchStr: search,
|
|
1702
|
-
search: replaceEqualDeep(previousLocation?.search, parsedSearch),
|
|
1703
|
-
hash: hash.split('#').reverse()[0] ?? '',
|
|
1704
|
-
href: `${pathname}${search}${hash}`,
|
|
1705
|
-
state: replaceEqualDeep(previousLocation?.state, state)
|
|
1706
|
-
};
|
|
1707
|
-
};
|
|
1708
|
-
const location = parse(this.history.location);
|
|
1709
|
-
let {
|
|
1710
|
-
__tempLocation,
|
|
1711
|
-
__tempKey
|
|
1712
|
-
} = location.state;
|
|
1713
|
-
if (__tempLocation && (!__tempKey || __tempKey === this.tempLocationKey)) {
|
|
1714
|
-
// Sync up the location keys
|
|
1715
|
-
const parsedTempLocation = parse(__tempLocation);
|
|
1716
|
-
parsedTempLocation.state.key = location.state.key;
|
|
1717
|
-
delete parsedTempLocation.state.__tempLocation;
|
|
1718
|
-
return {
|
|
1719
|
-
...parsedTempLocation,
|
|
1720
|
-
maskedLocation: location
|
|
1721
|
-
};
|
|
1722
|
-
}
|
|
1723
|
-
return location;
|
|
1724
|
-
};
|
|
1725
|
-
resolvePathWithBase = (from, path) => {
|
|
1726
|
-
return resolvePath(this.basepath, from, cleanPath(path));
|
|
1727
|
-
};
|
|
1728
|
-
get looseRoutesById() {
|
|
1729
|
-
return this.routesById;
|
|
1730
|
-
}
|
|
1731
|
-
matchRoutes = (pathname, locationSearch, opts) => {
|
|
1732
|
-
let routeParams = {};
|
|
1733
|
-
let foundRoute = this.flatRoutes.find(route => {
|
|
1734
|
-
const matchedParams = matchPathname(this.basepath, trimPathRight(pathname), {
|
|
1735
|
-
to: route.fullPath,
|
|
1736
|
-
caseSensitive: route.options.caseSensitive ?? this.options.caseSensitive,
|
|
1737
|
-
fuzzy: true
|
|
1738
|
-
});
|
|
1739
|
-
if (matchedParams) {
|
|
1740
|
-
routeParams = matchedParams;
|
|
1741
|
-
return true;
|
|
1742
|
-
}
|
|
1743
|
-
return false;
|
|
1765
|
+
invalidate = () => {
|
|
1766
|
+
const invalidate = d => ({
|
|
1767
|
+
...d,
|
|
1768
|
+
invalid: true
|
|
1744
1769
|
});
|
|
1745
|
-
|
|
1746
|
-
|
|
1770
|
+
this.__store.setState(s => ({
|
|
1771
|
+
...s,
|
|
1772
|
+
matches: s.matches.map(invalidate),
|
|
1773
|
+
cachedMatches: s.cachedMatches.map(invalidate),
|
|
1774
|
+
pendingMatches: s.pendingMatches?.map(invalidate)
|
|
1775
|
+
}));
|
|
1776
|
+
this.load();
|
|
1777
|
+
};
|
|
1778
|
+
load = async () => {
|
|
1779
|
+
const promise = new Promise(async (resolve, reject) => {
|
|
1780
|
+
const next = this.latestLocation;
|
|
1781
|
+
const prevLocation = this.state.resolvedLocation;
|
|
1782
|
+
const pathDidChange = prevLocation.href !== next.href;
|
|
1783
|
+
let latestPromise;
|
|
1747
1784
|
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
if (routeCursor) matchedRoutes.unshift(routeCursor);
|
|
1761
|
-
}
|
|
1785
|
+
// Cancel any pending matches
|
|
1786
|
+
this.cancelMatches();
|
|
1787
|
+
this.emit({
|
|
1788
|
+
type: 'onBeforeLoad',
|
|
1789
|
+
fromLocation: prevLocation,
|
|
1790
|
+
toLocation: next,
|
|
1791
|
+
pathChanged: pathDidChange
|
|
1792
|
+
});
|
|
1793
|
+
let pendingMatches;
|
|
1794
|
+
const previousMatches = this.state.matches;
|
|
1795
|
+
this.__store.batch(() => {
|
|
1796
|
+
this.cleanCache();
|
|
1762
1797
|
|
|
1763
|
-
|
|
1764
|
-
|
|
1798
|
+
// Match the routes
|
|
1799
|
+
pendingMatches = this.matchRoutes(next.pathname, next.search, {
|
|
1800
|
+
debug: true
|
|
1801
|
+
});
|
|
1765
1802
|
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1803
|
+
// Ingest the new matches
|
|
1804
|
+
// If a cached moved to pendingMatches, remove it from cachedMatches
|
|
1805
|
+
this.__store.setState(s => ({
|
|
1806
|
+
...s,
|
|
1807
|
+
isLoading: true,
|
|
1808
|
+
location: next,
|
|
1809
|
+
pendingMatches,
|
|
1810
|
+
cachedMatches: s.cachedMatches.filter(d => {
|
|
1811
|
+
return !pendingMatches.find(e => e.id === d.id);
|
|
1812
|
+
})
|
|
1813
|
+
}));
|
|
1814
|
+
});
|
|
1815
|
+
try {
|
|
1769
1816
|
try {
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
parsedParamsError = new PathParamError(err.message, {
|
|
1775
|
-
cause: err
|
|
1817
|
+
// Load the matches
|
|
1818
|
+
await this.loadMatches({
|
|
1819
|
+
matches: pendingMatches,
|
|
1820
|
+
checkLatest: () => this.checkLatest(promise)
|
|
1776
1821
|
});
|
|
1777
|
-
if (opts?.throwOnError) {
|
|
1778
|
-
throw parsedParamsError;
|
|
1779
|
-
}
|
|
1780
|
-
return parsedParamsError;
|
|
1781
|
-
}
|
|
1782
|
-
}
|
|
1783
|
-
return;
|
|
1784
|
-
});
|
|
1785
|
-
const matches = [];
|
|
1786
|
-
matchedRoutes.forEach((route, index) => {
|
|
1787
|
-
// Take each matched route and resolve + validate its search params
|
|
1788
|
-
// This has to happen serially because each route's search params
|
|
1789
|
-
// can depend on the parent route's search params
|
|
1790
|
-
// It must also happen before we create the match so that we can
|
|
1791
|
-
// pass the search params to the route's potential key function
|
|
1792
|
-
// which is used to uniquely identify the route match in state
|
|
1793
|
-
|
|
1794
|
-
const parentMatch = matches[index - 1];
|
|
1795
|
-
const [preMatchSearch, searchError] = (() => {
|
|
1796
|
-
// Validate the search params and stabilize them
|
|
1797
|
-
const parentSearch = parentMatch?.search ?? locationSearch;
|
|
1798
|
-
try {
|
|
1799
|
-
const validator = typeof route.options.validateSearch === 'object' ? route.options.validateSearch.parse : route.options.validateSearch;
|
|
1800
|
-
let search = validator?.(parentSearch) ?? {};
|
|
1801
|
-
return [{
|
|
1802
|
-
...parentSearch,
|
|
1803
|
-
...search
|
|
1804
|
-
}, undefined];
|
|
1805
1822
|
} catch (err) {
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
});
|
|
1809
|
-
if (opts?.throwOnError) {
|
|
1810
|
-
throw searchError;
|
|
1811
|
-
}
|
|
1812
|
-
return [parentSearch, searchError];
|
|
1823
|
+
// swallow this error, since we'll display the
|
|
1824
|
+
// errors on the route components
|
|
1813
1825
|
}
|
|
1814
|
-
})();
|
|
1815
|
-
|
|
1816
|
-
// This is where we need to call route.options.loaderDeps() to get any additional
|
|
1817
|
-
// deps that the route's loader function might need to run. We need to do this
|
|
1818
|
-
// before we create the match so that we can pass the deps to the route's
|
|
1819
|
-
// potential key function which is used to uniquely identify the route match in state
|
|
1820
1826
|
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
+
// Only apply the latest transition
|
|
1828
|
+
if (latestPromise = this.checkLatest(promise)) {
|
|
1829
|
+
return latestPromise;
|
|
1830
|
+
}
|
|
1831
|
+
const exitingMatches = previousMatches.filter(match => !pendingMatches.find(d => d.id === match.id));
|
|
1832
|
+
const enteringMatches = pendingMatches.filter(match => !previousMatches.find(d => d.id === match.id));
|
|
1833
|
+
const stayingMatches = previousMatches.filter(match => pendingMatches.find(d => d.id === match.id));
|
|
1827
1834
|
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1835
|
+
// Commit the pending matches. If a previous match was
|
|
1836
|
+
// removed, place it in the cachedMatches
|
|
1837
|
+
this.__store.batch(() => {
|
|
1838
|
+
this.__store.setState(s => ({
|
|
1839
|
+
...s,
|
|
1840
|
+
isLoading: false,
|
|
1841
|
+
matches: s.pendingMatches,
|
|
1842
|
+
pendingMatches: undefined,
|
|
1843
|
+
cachedMatches: [...s.cachedMatches, ...exitingMatches.filter(d => d.status !== 'error')]
|
|
1844
|
+
}));
|
|
1845
|
+
this.cleanCache();
|
|
1846
|
+
})
|
|
1833
1847
|
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
} : {
|
|
1840
|
-
id: matchId,
|
|
1841
|
-
routeId: route.id,
|
|
1842
|
-
params: routeParams,
|
|
1843
|
-
pathname: joinPaths([this.basepath, interpolatedPath]),
|
|
1844
|
-
updatedAt: Date.now(),
|
|
1845
|
-
search: {},
|
|
1846
|
-
searchError: undefined,
|
|
1847
|
-
status: hasLoaders ? 'pending' : 'success',
|
|
1848
|
-
showPending: false,
|
|
1849
|
-
isFetching: false,
|
|
1850
|
-
error: undefined,
|
|
1851
|
-
paramsError: parseErrors[index],
|
|
1852
|
-
loadPromise: Promise.resolve(),
|
|
1853
|
-
routeContext: undefined,
|
|
1854
|
-
context: undefined,
|
|
1855
|
-
abortController: new AbortController(),
|
|
1856
|
-
fetchCount: 0,
|
|
1857
|
-
cause,
|
|
1858
|
-
loaderDeps,
|
|
1859
|
-
invalid: false,
|
|
1860
|
-
preload: false
|
|
1861
|
-
};
|
|
1862
|
-
|
|
1863
|
-
// Regardless of whether we're reusing an existing match or creating
|
|
1864
|
-
// a new one, we need to update the match's search params
|
|
1865
|
-
match.search = replaceEqualDeep(match.search, preMatchSearch);
|
|
1866
|
-
// And also update the searchError if there is one
|
|
1867
|
-
match.searchError = searchError;
|
|
1868
|
-
matches.push(match);
|
|
1869
|
-
});
|
|
1870
|
-
return matches;
|
|
1871
|
-
};
|
|
1872
|
-
cancelMatch = id => {
|
|
1873
|
-
getRouteMatch(this.state, id)?.abortController?.abort();
|
|
1874
|
-
};
|
|
1875
|
-
cancelMatches = () => {
|
|
1876
|
-
this.state.pendingMatches?.forEach(match => {
|
|
1877
|
-
this.cancelMatch(match.id);
|
|
1878
|
-
});
|
|
1879
|
-
};
|
|
1880
|
-
buildLocation = opts => {
|
|
1881
|
-
const build = (dest = {}, matches) => {
|
|
1882
|
-
const relevantMatches = this.state.pendingMatches || this.state.matches;
|
|
1883
|
-
const fromSearch = relevantMatches[relevantMatches.length - 1]?.search || this.latestLocation.search;
|
|
1884
|
-
let pathname = this.resolvePathWithBase(dest.from ?? this.latestLocation.pathname, `${dest.to ?? ''}`);
|
|
1885
|
-
const fromMatches = this.matchRoutes(this.latestLocation.pathname, fromSearch);
|
|
1886
|
-
const stayingMatches = matches?.filter(d => fromMatches?.find(e => e.routeId === d.routeId));
|
|
1887
|
-
const prevParams = {
|
|
1888
|
-
...last(fromMatches)?.params
|
|
1889
|
-
};
|
|
1890
|
-
let nextParams = (dest.params ?? true) === true ? prevParams : functionalUpdate(dest.params, prevParams);
|
|
1891
|
-
if (nextParams) {
|
|
1892
|
-
matches?.map(d => this.looseRoutesById[d.routeId].options.stringifyParams).filter(Boolean).forEach(fn => {
|
|
1893
|
-
nextParams = {
|
|
1894
|
-
...nextParams,
|
|
1895
|
-
...fn(nextParams)
|
|
1896
|
-
};
|
|
1897
|
-
});
|
|
1898
|
-
}
|
|
1899
|
-
pathname = interpolatePath(pathname, nextParams ?? {});
|
|
1900
|
-
const preSearchFilters = stayingMatches?.map(match => this.looseRoutesById[match.routeId].options.preSearchFilters ?? []).flat().filter(Boolean) ?? [];
|
|
1901
|
-
const postSearchFilters = stayingMatches?.map(match => this.looseRoutesById[match.routeId].options.postSearchFilters ?? []).flat().filter(Boolean) ?? [];
|
|
1902
|
-
|
|
1903
|
-
// Pre filters first
|
|
1904
|
-
const preFilteredSearch = preSearchFilters?.length ? preSearchFilters?.reduce((prev, next) => next(prev), fromSearch) : fromSearch;
|
|
1905
|
-
|
|
1906
|
-
// Then the link/navigate function
|
|
1907
|
-
const destSearch = dest.search === true ? preFilteredSearch // Preserve resolvedFrom true
|
|
1908
|
-
: dest.search ? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
|
|
1909
|
-
: preSearchFilters?.length ? preFilteredSearch // Preserve resolvedFrom filters
|
|
1910
|
-
: {};
|
|
1911
|
-
|
|
1912
|
-
// Then post filters
|
|
1913
|
-
const postFilteredSearch = postSearchFilters?.length ? postSearchFilters.reduce((prev, next) => next(prev), destSearch) : destSearch;
|
|
1914
|
-
const search = replaceEqualDeep(fromSearch, postFilteredSearch);
|
|
1915
|
-
const searchStr = this.options.stringifySearch(search);
|
|
1916
|
-
const hash = dest.hash === true ? this.latestLocation.hash : dest.hash ? functionalUpdate(dest.hash, this.latestLocation.hash) : undefined;
|
|
1917
|
-
const hashStr = hash ? `#${hash}` : '';
|
|
1918
|
-
let nextState = dest.state === true ? this.latestLocation.state : dest.state ? functionalUpdate(dest.state, this.latestLocation.state) : this.latestLocation.state;
|
|
1919
|
-
nextState = replaceEqualDeep(this.latestLocation.state, nextState);
|
|
1920
|
-
return {
|
|
1921
|
-
pathname,
|
|
1922
|
-
search,
|
|
1923
|
-
searchStr,
|
|
1924
|
-
state: nextState,
|
|
1925
|
-
hash: hash ?? '',
|
|
1926
|
-
href: `${pathname}${searchStr}${hashStr}`,
|
|
1927
|
-
unmaskOnReload: dest.unmaskOnReload
|
|
1928
|
-
};
|
|
1929
|
-
};
|
|
1930
|
-
const buildWithMatches = (dest = {}, maskedDest) => {
|
|
1931
|
-
let next = build(dest);
|
|
1932
|
-
let maskedNext = maskedDest ? build(maskedDest) : undefined;
|
|
1933
|
-
if (!maskedNext) {
|
|
1934
|
-
let params = {};
|
|
1935
|
-
let foundMask = this.options.routeMasks?.find(d => {
|
|
1936
|
-
const match = matchPathname(this.basepath, next.pathname, {
|
|
1937
|
-
to: d.from,
|
|
1938
|
-
caseSensitive: false,
|
|
1939
|
-
fuzzy: false
|
|
1848
|
+
//
|
|
1849
|
+
;
|
|
1850
|
+
[[exitingMatches, 'onLeave'], [enteringMatches, 'onEnter'], [stayingMatches, 'onStay']].forEach(([matches, hook]) => {
|
|
1851
|
+
matches.forEach(match => {
|
|
1852
|
+
this.looseRoutesById[match.routeId].options[hook]?.(match);
|
|
1940
1853
|
});
|
|
1941
|
-
if (match) {
|
|
1942
|
-
params = match;
|
|
1943
|
-
return true;
|
|
1944
|
-
}
|
|
1945
|
-
return false;
|
|
1946
1854
|
});
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1855
|
+
this.emit({
|
|
1856
|
+
type: 'onLoad',
|
|
1857
|
+
fromLocation: prevLocation,
|
|
1858
|
+
toLocation: next,
|
|
1859
|
+
pathChanged: pathDidChange
|
|
1860
|
+
});
|
|
1861
|
+
resolve();
|
|
1862
|
+
} catch (err) {
|
|
1863
|
+
// Only apply the latest transition
|
|
1864
|
+
if (latestPromise = this.checkLatest(promise)) {
|
|
1865
|
+
return latestPromise;
|
|
1954
1866
|
}
|
|
1867
|
+
reject(err);
|
|
1955
1868
|
}
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
const final = build(dest, nextMatches);
|
|
1960
|
-
if (maskedFinal) {
|
|
1961
|
-
final.maskedLocation = maskedFinal;
|
|
1962
|
-
}
|
|
1963
|
-
return final;
|
|
1964
|
-
};
|
|
1965
|
-
if (opts.mask) {
|
|
1966
|
-
return buildWithMatches(opts, {
|
|
1967
|
-
...pick(opts, ['from']),
|
|
1968
|
-
...opts.mask
|
|
1969
|
-
});
|
|
1970
|
-
}
|
|
1971
|
-
return buildWithMatches(opts);
|
|
1869
|
+
});
|
|
1870
|
+
this.latestLoadPromise = promise;
|
|
1871
|
+
return this.latestLoadPromise;
|
|
1972
1872
|
};
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
if (!isSameUrl || !next.replace) {
|
|
1983
|
-
let {
|
|
1984
|
-
maskedLocation,
|
|
1985
|
-
...nextHistory
|
|
1986
|
-
} = next;
|
|
1987
|
-
if (maskedLocation) {
|
|
1988
|
-
nextHistory = {
|
|
1989
|
-
...maskedLocation,
|
|
1990
|
-
state: {
|
|
1991
|
-
...maskedLocation.state,
|
|
1992
|
-
__tempKey: undefined,
|
|
1993
|
-
__tempLocation: {
|
|
1994
|
-
...nextHistory,
|
|
1995
|
-
search: nextHistory.searchStr,
|
|
1996
|
-
state: {
|
|
1997
|
-
...nextHistory.state,
|
|
1998
|
-
__tempKey: undefined,
|
|
1999
|
-
__tempLocation: undefined,
|
|
2000
|
-
key: undefined
|
|
2001
|
-
}
|
|
2002
|
-
}
|
|
1873
|
+
cleanCache = () => {
|
|
1874
|
+
// This is where all of the garbage collection magic happens
|
|
1875
|
+
this.__store.setState(s => {
|
|
1876
|
+
return {
|
|
1877
|
+
...s,
|
|
1878
|
+
cachedMatches: s.cachedMatches.filter(d => {
|
|
1879
|
+
const route = this.looseRoutesById[d.routeId];
|
|
1880
|
+
if (!route.options.loader) {
|
|
1881
|
+
return false;
|
|
2003
1882
|
}
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
this.history[next.replace ? 'replace' : 'push'](nextHistory.href, nextHistory.state);
|
|
1883
|
+
|
|
1884
|
+
// If the route was preloaded, use the preloadGcTime
|
|
1885
|
+
// otherwise, use the gcTime
|
|
1886
|
+
const gcTime = (d.preload ? route.options.preloadGcTime ?? this.options.defaultPreloadGcTime : route.options.gcTime ?? this.options.defaultGcTime) ?? 5 * 60 * 1000;
|
|
1887
|
+
return d.status !== 'error' && Date.now() - d.updatedAt < gcTime;
|
|
1888
|
+
})
|
|
2011
1889
|
};
|
|
2012
|
-
if (startTransition ?? true) {
|
|
2013
|
-
this.startReactTransition(apply);
|
|
2014
|
-
} else {
|
|
2015
|
-
apply();
|
|
2016
|
-
}
|
|
2017
|
-
}
|
|
2018
|
-
this.resetNextScroll = next.resetScroll ?? true;
|
|
2019
|
-
return this.latestLoadPromise;
|
|
2020
|
-
};
|
|
2021
|
-
buildAndCommitLocation = ({
|
|
2022
|
-
replace,
|
|
2023
|
-
resetScroll,
|
|
2024
|
-
startTransition,
|
|
2025
|
-
...rest
|
|
2026
|
-
} = {}) => {
|
|
2027
|
-
const location = this.buildLocation(rest);
|
|
2028
|
-
return this.commitLocation({
|
|
2029
|
-
...location,
|
|
2030
|
-
startTransition,
|
|
2031
|
-
replace,
|
|
2032
|
-
resetScroll
|
|
2033
1890
|
});
|
|
2034
1891
|
};
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
}) => {
|
|
2040
|
-
// If this link simply reloads the current route,
|
|
2041
|
-
// make sure it has a new key so it will trigger a data refresh
|
|
2042
|
-
|
|
2043
|
-
// If this `to` is a valid external URL, return
|
|
2044
|
-
// null for LinkUtils
|
|
2045
|
-
const toString = String(to);
|
|
2046
|
-
// const fromString = from !== undefined ? String(from) : from
|
|
2047
|
-
let isExternal;
|
|
2048
|
-
try {
|
|
2049
|
-
new URL(`${toString}`);
|
|
2050
|
-
isExternal = true;
|
|
2051
|
-
} catch (e) {}
|
|
2052
|
-
invariant(!isExternal, 'Attempting to navigate to external url with this.navigate!');
|
|
2053
|
-
return this.buildAndCommitLocation({
|
|
2054
|
-
...rest,
|
|
2055
|
-
from,
|
|
2056
|
-
to
|
|
2057
|
-
// to: toString,
|
|
1892
|
+
preloadRoute = async (navigateOpts = this.state.location) => {
|
|
1893
|
+
let next = this.buildLocation(navigateOpts);
|
|
1894
|
+
let matches = this.matchRoutes(next.pathname, next.search, {
|
|
1895
|
+
throwOnError: true
|
|
2058
1896
|
});
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
const updateMatch = match => {
|
|
2068
|
-
// const isPreload = this.state.cachedMatches.find((d) => d.id === match.id)
|
|
2069
|
-
const isPending = this.state.pendingMatches?.find(d => d.id === match.id);
|
|
2070
|
-
const isMatched = this.state.matches.find(d => d.id === match.id);
|
|
2071
|
-
const matchesKey = isPending ? 'pendingMatches' : isMatched ? 'matches' : 'cachedMatches';
|
|
2072
|
-
this.__store.setState(s => ({
|
|
2073
|
-
...s,
|
|
2074
|
-
[matchesKey]: s[matchesKey]?.map(d => d.id === match.id ? match : d)
|
|
2075
|
-
}));
|
|
2076
|
-
};
|
|
2077
|
-
|
|
2078
|
-
// Check each match middleware to see if the route can be accessed
|
|
2079
|
-
try {
|
|
2080
|
-
for (let [index, match] of matches.entries()) {
|
|
2081
|
-
const parentMatch = matches[index - 1];
|
|
2082
|
-
const route = this.looseRoutesById[match.routeId];
|
|
2083
|
-
const abortController = new AbortController();
|
|
2084
|
-
const handleErrorAndRedirect = (err, code) => {
|
|
2085
|
-
err.routerCode = code;
|
|
2086
|
-
firstBadMatchIndex = firstBadMatchIndex ?? index;
|
|
2087
|
-
if (isRedirect(err)) {
|
|
2088
|
-
throw err;
|
|
2089
|
-
}
|
|
2090
|
-
try {
|
|
2091
|
-
route.options.onError?.(err);
|
|
2092
|
-
} catch (errorHandlerErr) {
|
|
2093
|
-
err = errorHandlerErr;
|
|
2094
|
-
if (isRedirect(errorHandlerErr)) {
|
|
2095
|
-
throw errorHandlerErr;
|
|
2096
|
-
}
|
|
2097
|
-
}
|
|
2098
|
-
matches[index] = match = {
|
|
2099
|
-
...match,
|
|
2100
|
-
error: err,
|
|
2101
|
-
status: 'error',
|
|
2102
|
-
updatedAt: Date.now(),
|
|
2103
|
-
abortController: new AbortController()
|
|
2104
|
-
};
|
|
2105
|
-
};
|
|
2106
|
-
try {
|
|
2107
|
-
if (match.paramsError) {
|
|
2108
|
-
handleErrorAndRedirect(match.paramsError, 'PARSE_PARAMS');
|
|
2109
|
-
}
|
|
2110
|
-
if (match.searchError) {
|
|
2111
|
-
handleErrorAndRedirect(match.searchError, 'VALIDATE_SEARCH');
|
|
2112
|
-
}
|
|
2113
|
-
const parentContext = parentMatch?.context ?? this.options.context ?? {};
|
|
2114
|
-
const pendingMs = route.options.pendingMs ?? this.options.defaultPendingMs;
|
|
2115
|
-
const pendingPromise = typeof pendingMs === 'number' && pendingMs <= 0 ? Promise.resolve() : new Promise(r => setTimeout(r, pendingMs));
|
|
2116
|
-
const beforeLoadContext = (await route.options.beforeLoad?.({
|
|
2117
|
-
search: match.search,
|
|
2118
|
-
abortController,
|
|
2119
|
-
params: match.params,
|
|
2120
|
-
preload: !!preload,
|
|
2121
|
-
context: parentContext,
|
|
2122
|
-
location: this.state.location,
|
|
2123
|
-
// TOOD: just expose state and router, etc
|
|
2124
|
-
navigate: opts => this.navigate({
|
|
2125
|
-
...opts,
|
|
2126
|
-
from: match.pathname
|
|
2127
|
-
}),
|
|
2128
|
-
buildLocation: this.buildLocation,
|
|
2129
|
-
cause: preload ? 'preload' : match.cause
|
|
2130
|
-
})) ?? {};
|
|
2131
|
-
if (isRedirect(beforeLoadContext)) {
|
|
2132
|
-
throw beforeLoadContext;
|
|
2133
|
-
}
|
|
2134
|
-
const context = {
|
|
2135
|
-
...parentContext,
|
|
2136
|
-
...beforeLoadContext
|
|
2137
|
-
};
|
|
2138
|
-
matches[index] = match = {
|
|
2139
|
-
...match,
|
|
2140
|
-
routeContext: replaceEqualDeep(match.routeContext, beforeLoadContext),
|
|
2141
|
-
context: replaceEqualDeep(match.context, context),
|
|
2142
|
-
abortController,
|
|
2143
|
-
pendingPromise
|
|
2144
|
-
};
|
|
2145
|
-
} catch (err) {
|
|
2146
|
-
handleErrorAndRedirect(err, 'BEFORE_LOAD');
|
|
2147
|
-
break;
|
|
1897
|
+
const loadedMatchIds = Object.fromEntries([...this.state.matches, ...(this.state.pendingMatches ?? []), ...this.state.cachedMatches]?.map(d => [d.id, true]));
|
|
1898
|
+
this.__store.batch(() => {
|
|
1899
|
+
matches.forEach(match => {
|
|
1900
|
+
if (!loadedMatchIds[match.id]) {
|
|
1901
|
+
this.__store.setState(s => ({
|
|
1902
|
+
...s,
|
|
1903
|
+
cachedMatches: [...s.cachedMatches, match]
|
|
1904
|
+
}));
|
|
2148
1905
|
}
|
|
1906
|
+
});
|
|
1907
|
+
});
|
|
1908
|
+
matches = await this.loadMatches({
|
|
1909
|
+
matches,
|
|
1910
|
+
preload: true,
|
|
1911
|
+
checkLatest: () => undefined
|
|
1912
|
+
});
|
|
1913
|
+
return matches;
|
|
1914
|
+
};
|
|
1915
|
+
matchRoute = (location, opts) => {
|
|
1916
|
+
location = {
|
|
1917
|
+
...location,
|
|
1918
|
+
to: location.to ? this.resolvePathWithBase(location.from || '', location.to) : undefined
|
|
1919
|
+
};
|
|
1920
|
+
const next = this.buildLocation(location);
|
|
1921
|
+
if (opts?.pending && this.state.status !== 'pending') {
|
|
1922
|
+
return false;
|
|
1923
|
+
}
|
|
1924
|
+
const baseLocation = opts?.pending ? this.latestLocation : this.state.resolvedLocation;
|
|
1925
|
+
if (!baseLocation) {
|
|
1926
|
+
return false;
|
|
1927
|
+
}
|
|
1928
|
+
const match = matchPathname(this.basepath, baseLocation.pathname, {
|
|
1929
|
+
...opts,
|
|
1930
|
+
to: next.pathname
|
|
1931
|
+
});
|
|
1932
|
+
if (!match) {
|
|
1933
|
+
return false;
|
|
1934
|
+
}
|
|
1935
|
+
if (match && (opts?.includeSearch ?? true)) {
|
|
1936
|
+
return deepEqual(baseLocation.search, next.search, true) ? match : false;
|
|
1937
|
+
}
|
|
1938
|
+
return match;
|
|
1939
|
+
};
|
|
1940
|
+
injectHtml = async html => {
|
|
1941
|
+
this.injectedHtml.push(html);
|
|
1942
|
+
};
|
|
1943
|
+
dehydrateData = (key, getData) => {
|
|
1944
|
+
if (typeof document === 'undefined') {
|
|
1945
|
+
const strKey = typeof key === 'string' ? key : JSON.stringify(key);
|
|
1946
|
+
this.injectHtml(async () => {
|
|
1947
|
+
const id = `__TSR_DEHYDRATED__${strKey}`;
|
|
1948
|
+
const data = typeof getData === 'function' ? await getData() : getData;
|
|
1949
|
+
return `<script id='${id}' suppressHydrationWarning>window["__TSR_DEHYDRATED__${escapeJSON(strKey)}"] = ${JSON.stringify(data)}
|
|
1950
|
+
;(() => {
|
|
1951
|
+
var el = document.getElementById('${id}')
|
|
1952
|
+
el.parentElement.removeChild(el)
|
|
1953
|
+
})()
|
|
1954
|
+
</script>`;
|
|
1955
|
+
});
|
|
1956
|
+
return () => this.hydrateData(key);
|
|
1957
|
+
}
|
|
1958
|
+
return () => undefined;
|
|
1959
|
+
};
|
|
1960
|
+
hydrateData = key => {
|
|
1961
|
+
if (typeof document !== 'undefined') {
|
|
1962
|
+
const strKey = typeof key === 'string' ? key : JSON.stringify(key);
|
|
1963
|
+
return window[`__TSR_DEHYDRATED__${strKey}`];
|
|
1964
|
+
}
|
|
1965
|
+
return undefined;
|
|
1966
|
+
};
|
|
1967
|
+
dehydrate = () => {
|
|
1968
|
+
const pickError = this.options.errorSerializer?.serialize ?? defaultSerializeError;
|
|
1969
|
+
return {
|
|
1970
|
+
state: {
|
|
1971
|
+
dehydratedMatches: this.state.matches.map(d => ({
|
|
1972
|
+
...pick(d, ['id', 'status', 'updatedAt', 'loaderData']),
|
|
1973
|
+
// If an error occurs server-side during SSRing,
|
|
1974
|
+
// send a small subset of the error to the client
|
|
1975
|
+
error: d.error ? {
|
|
1976
|
+
data: pickError(d.error),
|
|
1977
|
+
__isServerError: true
|
|
1978
|
+
} : undefined
|
|
1979
|
+
}))
|
|
2149
1980
|
}
|
|
2150
|
-
}
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
1981
|
+
};
|
|
1982
|
+
};
|
|
1983
|
+
hydrate = async __do_not_use_server_ctx => {
|
|
1984
|
+
let _ctx = __do_not_use_server_ctx;
|
|
1985
|
+
// Client hydrates from window
|
|
1986
|
+
if (typeof document !== 'undefined') {
|
|
1987
|
+
_ctx = window.__TSR_DEHYDRATED__;
|
|
2156
1988
|
}
|
|
2157
|
-
|
|
2158
|
-
const
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
}
|
|
2168
|
-
return true;
|
|
2169
|
-
}
|
|
2170
|
-
return false;
|
|
2171
|
-
};
|
|
2172
|
-
let loadPromise;
|
|
2173
|
-
matches[index] = match = {
|
|
1989
|
+
invariant(_ctx, 'Expected to find a __TSR_DEHYDRATED__ property on window... but we did not. Did you forget to render <DehydrateRouter /> in your app?');
|
|
1990
|
+
const ctx = _ctx;
|
|
1991
|
+
this.dehydratedData = ctx.payload;
|
|
1992
|
+
this.options.hydrate?.(ctx.payload);
|
|
1993
|
+
const dehydratedState = ctx.router.state;
|
|
1994
|
+
let matches = this.matchRoutes(this.state.location.pathname, this.state.location.search).map(match => {
|
|
1995
|
+
const dehydratedMatch = dehydratedState.dehydratedMatches.find(d => d.id === match.id);
|
|
1996
|
+
invariant(dehydratedMatch, `Could not find a client-side match for dehydrated match with id: ${match.id}!`);
|
|
1997
|
+
if (dehydratedMatch) {
|
|
1998
|
+
return {
|
|
2174
1999
|
...match,
|
|
2175
|
-
|
|
2176
|
-
};
|
|
2177
|
-
let didShowPending = false;
|
|
2178
|
-
const pendingMs = route.options.pendingMs ?? this.options.defaultPendingMs;
|
|
2179
|
-
const pendingMinMs = route.options.pendingMinMs ?? this.options.defaultPendingMinMs;
|
|
2180
|
-
const shouldPending = !preload && typeof pendingMs === 'number' && (route.options.pendingComponent ?? this.options.defaultPendingComponent);
|
|
2181
|
-
const loaderContext = {
|
|
2182
|
-
params: match.params,
|
|
2183
|
-
deps: match.loaderDeps,
|
|
2184
|
-
preload: !!preload,
|
|
2185
|
-
parentMatchPromise,
|
|
2186
|
-
abortController: match.abortController,
|
|
2187
|
-
context: match.context,
|
|
2188
|
-
location: this.state.location,
|
|
2189
|
-
navigate: opts => this.navigate({
|
|
2190
|
-
...opts,
|
|
2191
|
-
from: match.pathname
|
|
2192
|
-
}),
|
|
2193
|
-
cause: preload ? 'preload' : match.cause
|
|
2000
|
+
...dehydratedMatch
|
|
2194
2001
|
};
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2002
|
+
}
|
|
2003
|
+
return match;
|
|
2004
|
+
});
|
|
2005
|
+
this.__store.setState(s => {
|
|
2006
|
+
return {
|
|
2007
|
+
...s,
|
|
2008
|
+
matches: matches
|
|
2009
|
+
};
|
|
2010
|
+
});
|
|
2011
|
+
};
|
|
2201
2012
|
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2013
|
+
// resolveMatchPromise = (matchId: string, key: string, value: any) => {
|
|
2014
|
+
// state.matches
|
|
2015
|
+
// .find((d) => d.id === matchId)
|
|
2016
|
+
// ?.__promisesByKey[key]?.resolve(value)
|
|
2017
|
+
// }
|
|
2018
|
+
}
|
|
2205
2019
|
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
updatedAt: Date.now(),
|
|
2242
|
-
loaderData,
|
|
2243
|
-
loadPromise: undefined
|
|
2244
|
-
};
|
|
2245
|
-
} catch (error) {
|
|
2246
|
-
if (latestPromise = checkLatest()) return await latestPromise;
|
|
2247
|
-
if (handleErrorAndRedirect(error)) return;
|
|
2248
|
-
try {
|
|
2249
|
-
route.options.onError?.(error);
|
|
2250
|
-
} catch (onErrorError) {
|
|
2251
|
-
error = onErrorError;
|
|
2252
|
-
if (handleErrorAndRedirect(onErrorError)) return;
|
|
2253
|
-
}
|
|
2254
|
-
matches[index] = match = {
|
|
2255
|
-
...match,
|
|
2256
|
-
error,
|
|
2257
|
-
status: 'error',
|
|
2258
|
-
isFetching: false
|
|
2259
|
-
};
|
|
2260
|
-
}
|
|
2261
|
-
updateMatch(match);
|
|
2262
|
-
};
|
|
2020
|
+
// A function that takes an import() argument which is a function and returns a new function that will
|
|
2021
|
+
// proxy arguments from the caller to the imported function, retaining all type
|
|
2022
|
+
// information along the way
|
|
2023
|
+
function lazyFn(fn, key) {
|
|
2024
|
+
return async (...args) => {
|
|
2025
|
+
const imported = await fn();
|
|
2026
|
+
return imported[key || 'default'](...args);
|
|
2027
|
+
};
|
|
2028
|
+
}
|
|
2029
|
+
class SearchParamError extends Error {}
|
|
2030
|
+
class PathParamError extends Error {}
|
|
2031
|
+
function getInitialRouterState(location) {
|
|
2032
|
+
return {
|
|
2033
|
+
isLoading: false,
|
|
2034
|
+
isTransitioning: false,
|
|
2035
|
+
status: 'idle',
|
|
2036
|
+
resolvedLocation: {
|
|
2037
|
+
...location
|
|
2038
|
+
},
|
|
2039
|
+
location,
|
|
2040
|
+
matches: [],
|
|
2041
|
+
pendingMatches: [],
|
|
2042
|
+
cachedMatches: [],
|
|
2043
|
+
lastUpdated: Date.now()
|
|
2044
|
+
};
|
|
2045
|
+
}
|
|
2046
|
+
function defaultSerializeError(err) {
|
|
2047
|
+
if (err instanceof Error) return {
|
|
2048
|
+
name: err.name,
|
|
2049
|
+
message: err.message
|
|
2050
|
+
};
|
|
2051
|
+
return {
|
|
2052
|
+
data: err
|
|
2053
|
+
};
|
|
2054
|
+
}
|
|
2263
2055
|
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2056
|
+
function defer(_promise, options) {
|
|
2057
|
+
const promise = _promise;
|
|
2058
|
+
if (!promise.__deferredState) {
|
|
2059
|
+
promise.__deferredState = {
|
|
2060
|
+
uid: Math.random().toString(36).slice(2),
|
|
2061
|
+
status: 'pending'
|
|
2062
|
+
};
|
|
2063
|
+
const state = promise.__deferredState;
|
|
2064
|
+
promise.then(data => {
|
|
2065
|
+
state.status = 'success';
|
|
2066
|
+
state.data = data;
|
|
2067
|
+
}).catch(error => {
|
|
2068
|
+
state.status = 'error';
|
|
2069
|
+
state.error = {
|
|
2070
|
+
data: (options?.serializeError ?? defaultSerializeError)(error),
|
|
2071
|
+
__isServerError: true
|
|
2072
|
+
};
|
|
2073
|
+
});
|
|
2074
|
+
}
|
|
2075
|
+
return promise;
|
|
2076
|
+
}
|
|
2077
|
+
function isDehydratedDeferred(obj) {
|
|
2078
|
+
return typeof obj === 'object' && obj !== null && !(obj instanceof Promise) && !obj.then && '__deferredState' in obj;
|
|
2079
|
+
}
|
|
2268
2080
|
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2081
|
+
function useAwaited({
|
|
2082
|
+
promise
|
|
2083
|
+
}) {
|
|
2084
|
+
const router = useRouter();
|
|
2085
|
+
let state = promise.__deferredState;
|
|
2086
|
+
const key = `__TSR__DEFERRED__${state.uid}`;
|
|
2087
|
+
if (isDehydratedDeferred(promise)) {
|
|
2088
|
+
state = router.hydrateData(key);
|
|
2089
|
+
if (!state) throw new Error('Could not find dehydrated data');
|
|
2090
|
+
promise = Promise.resolve(state.data);
|
|
2091
|
+
promise.__deferredState = state;
|
|
2092
|
+
}
|
|
2093
|
+
if (state.status === 'pending') {
|
|
2094
|
+
throw promise;
|
|
2095
|
+
}
|
|
2096
|
+
if (state.status === 'error') {
|
|
2097
|
+
if (typeof document !== 'undefined') {
|
|
2098
|
+
if (isServerSideError(state.error)) {
|
|
2099
|
+
throw (router.options.errorSerializer?.deserialize ?? defaultDeserializeError)(state.error.data);
|
|
2100
|
+
} else {
|
|
2101
|
+
warning(false, "Encountered a server-side error that doesn't fit the expected shape");
|
|
2102
|
+
throw state.error;
|
|
2103
|
+
}
|
|
2104
|
+
} else {
|
|
2105
|
+
router.dehydrateData(key, state);
|
|
2106
|
+
throw {
|
|
2107
|
+
data: (router.options.errorSerializer?.serialize ?? defaultSerializeError)(state.error),
|
|
2108
|
+
__isServerError: true
|
|
2109
|
+
};
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
router.dehydrateData(key, state);
|
|
2113
|
+
return [state.data];
|
|
2114
|
+
}
|
|
2115
|
+
function Await(props) {
|
|
2116
|
+
const awaited = useAwaited(props);
|
|
2117
|
+
return props.children(...awaited);
|
|
2118
|
+
}
|
|
2272
2119
|
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
// If we need to potentially show the pending component,
|
|
2282
|
-
// start a timer to show it after the pendingMs
|
|
2283
|
-
if (shouldPending) {
|
|
2284
|
-
match.pendingPromise?.then(async () => {
|
|
2285
|
-
if (latestPromise = checkLatest()) return latestPromise;
|
|
2286
|
-
didShowPending = true;
|
|
2287
|
-
matches[index] = match = {
|
|
2288
|
-
...match,
|
|
2289
|
-
showPending: true
|
|
2290
|
-
};
|
|
2291
|
-
updateMatch(match);
|
|
2292
|
-
resolve();
|
|
2293
|
-
});
|
|
2294
|
-
}
|
|
2120
|
+
function useParams(opts) {
|
|
2121
|
+
return useRouterState({
|
|
2122
|
+
select: state => {
|
|
2123
|
+
const params = last(getRenderedMatches(state))?.params;
|
|
2124
|
+
return opts?.select ? opts.select(params) : params;
|
|
2125
|
+
}
|
|
2126
|
+
});
|
|
2127
|
+
}
|
|
2295
2128
|
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2129
|
+
function useSearch(opts) {
|
|
2130
|
+
return useMatch({
|
|
2131
|
+
...opts,
|
|
2132
|
+
select: match => {
|
|
2133
|
+
return opts?.select ? opts.select(match.search) : match.search;
|
|
2134
|
+
}
|
|
2135
|
+
});
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
const rootRouteId = '__root__';
|
|
2139
|
+
|
|
2140
|
+
// The parse type here allows a zod schema to be passed directly to the validator
|
|
2141
|
+
|
|
2142
|
+
// TODO: This is part of a future APi to move away from classes and
|
|
2143
|
+
// towards a more functional API. It's not ready yet.
|
|
2144
|
+
|
|
2145
|
+
// type RouteApiInstance<
|
|
2146
|
+
// TId extends RouteIds<RegisteredRouter['routeTree']>,
|
|
2147
|
+
// TRoute extends AnyRoute = RouteById<RegisteredRouter['routeTree'], TId>,
|
|
2148
|
+
// TFullSearchSchema extends Record<
|
|
2149
|
+
// string,
|
|
2150
|
+
// any
|
|
2151
|
+
// > = TRoute['types']['fullSearchSchema'],
|
|
2152
|
+
// TAllParams extends AnyPathParams = TRoute['types']['allParams'],
|
|
2153
|
+
// TAllContext extends Record<string, any> = TRoute['types']['allContext'],
|
|
2154
|
+
// TLoaderDeps extends Record<string, any> = TRoute['types']['loaderDeps'],
|
|
2155
|
+
// TLoaderData extends any = TRoute['types']['loaderData'],
|
|
2156
|
+
// > = {
|
|
2157
|
+
// id: TId
|
|
2158
|
+
// useMatch: <TSelected = TAllContext>(opts?: {
|
|
2159
|
+
// select?: (s: TAllContext) => TSelected
|
|
2160
|
+
// }) => TSelected
|
|
2161
|
+
|
|
2162
|
+
// useRouteContext: <TSelected = TAllContext>(opts?: {
|
|
2163
|
+
// select?: (s: TAllContext) => TSelected
|
|
2164
|
+
// }) => TSelected
|
|
2165
|
+
|
|
2166
|
+
// useSearch: <TSelected = TFullSearchSchema>(opts?: {
|
|
2167
|
+
// select?: (s: TFullSearchSchema) => TSelected
|
|
2168
|
+
// }) => TSelected
|
|
2169
|
+
|
|
2170
|
+
// useParams: <TSelected = TAllParams>(opts?: {
|
|
2171
|
+
// select?: (s: TAllParams) => TSelected
|
|
2172
|
+
// }) => TSelected
|
|
2173
|
+
|
|
2174
|
+
// useLoaderDeps: <TSelected = TLoaderDeps>(opts?: {
|
|
2175
|
+
// select?: (s: TLoaderDeps) => TSelected
|
|
2176
|
+
// }) => TSelected
|
|
2177
|
+
|
|
2178
|
+
// useLoaderData: <TSelected = TLoaderData>(opts?: {
|
|
2179
|
+
// select?: (s: TLoaderData) => TSelected
|
|
2180
|
+
// }) => TSelected
|
|
2181
|
+
// }
|
|
2182
|
+
|
|
2183
|
+
// export function RouteApi_v2<
|
|
2184
|
+
// TId extends RouteIds<RegisteredRouter['routeTree']>,
|
|
2185
|
+
// TRoute extends AnyRoute = RouteById<RegisteredRouter['routeTree'], TId>,
|
|
2186
|
+
// TFullSearchSchema extends Record<
|
|
2187
|
+
// string,
|
|
2188
|
+
// any
|
|
2189
|
+
// > = TRoute['types']['fullSearchSchema'],
|
|
2190
|
+
// TAllParams extends AnyPathParams = TRoute['types']['allParams'],
|
|
2191
|
+
// TAllContext extends Record<string, any> = TRoute['types']['allContext'],
|
|
2192
|
+
// TLoaderDeps extends Record<string, any> = TRoute['types']['loaderDeps'],
|
|
2193
|
+
// TLoaderData extends any = TRoute['types']['loaderData'],
|
|
2194
|
+
// >({
|
|
2195
|
+
// id,
|
|
2196
|
+
// }: {
|
|
2197
|
+
// id: TId
|
|
2198
|
+
// }): RouteApiInstance<
|
|
2199
|
+
// TId,
|
|
2200
|
+
// TRoute,
|
|
2201
|
+
// TFullSearchSchema,
|
|
2202
|
+
// TAllParams,
|
|
2203
|
+
// TAllContext,
|
|
2204
|
+
// TLoaderDeps,
|
|
2205
|
+
// TLoaderData
|
|
2206
|
+
// > {
|
|
2207
|
+
// return {
|
|
2208
|
+
// id,
|
|
2209
|
+
|
|
2210
|
+
// useMatch: (opts) => {
|
|
2211
|
+
// return useMatch({ ...opts, from: id })
|
|
2212
|
+
// },
|
|
2213
|
+
|
|
2214
|
+
// useRouteContext: (opts) => {
|
|
2215
|
+
// return useMatch({
|
|
2216
|
+
// ...opts,
|
|
2217
|
+
// from: id,
|
|
2218
|
+
// select: (d: any) => (opts?.select ? opts.select(d.context) : d.context),
|
|
2219
|
+
// } as any)
|
|
2220
|
+
// },
|
|
2221
|
+
|
|
2222
|
+
// useSearch: (opts) => {
|
|
2223
|
+
// return useSearch({ ...opts, from: id } as any)
|
|
2224
|
+
// },
|
|
2225
|
+
|
|
2226
|
+
// useParams: (opts) => {
|
|
2227
|
+
// return useParams({ ...opts, from: id } as any)
|
|
2228
|
+
// },
|
|
2229
|
+
|
|
2230
|
+
// useLoaderDeps: (opts) => {
|
|
2231
|
+
// return useLoaderDeps({ ...opts, from: id } as any) as any
|
|
2232
|
+
// },
|
|
2233
|
+
|
|
2234
|
+
// useLoaderData: (opts) => {
|
|
2235
|
+
// return useLoaderData({ ...opts, from: id } as any) as any
|
|
2236
|
+
// },
|
|
2237
|
+
// }
|
|
2238
|
+
// }
|
|
2239
|
+
|
|
2240
|
+
class RouteApi {
|
|
2241
|
+
constructor({
|
|
2242
|
+
id
|
|
2243
|
+
}) {
|
|
2244
|
+
this.id = id;
|
|
2245
|
+
}
|
|
2246
|
+
useMatch = opts => {
|
|
2247
|
+
return useMatch({
|
|
2248
|
+
select: opts?.select,
|
|
2249
|
+
from: this.id
|
|
2250
|
+
});
|
|
2251
|
+
};
|
|
2252
|
+
useRouteContext = opts => {
|
|
2253
|
+
return useMatch({
|
|
2254
|
+
from: this.id,
|
|
2255
|
+
select: d => opts?.select ? opts.select(d.context) : d.context
|
|
2256
|
+
});
|
|
2257
|
+
};
|
|
2258
|
+
useSearch = opts => {
|
|
2259
|
+
return useSearch({
|
|
2260
|
+
...opts,
|
|
2261
|
+
from: this.id
|
|
2262
|
+
});
|
|
2263
|
+
};
|
|
2264
|
+
useParams = opts => {
|
|
2265
|
+
return useParams({
|
|
2266
|
+
...opts,
|
|
2267
|
+
from: this.id
|
|
2268
|
+
});
|
|
2269
|
+
};
|
|
2270
|
+
useLoaderDeps = opts => {
|
|
2271
|
+
return useLoaderDeps({
|
|
2272
|
+
...opts,
|
|
2273
|
+
from: this.id
|
|
2274
|
+
});
|
|
2275
|
+
};
|
|
2276
|
+
useLoaderData = opts => {
|
|
2277
|
+
return useLoaderData({
|
|
2278
|
+
...opts,
|
|
2279
|
+
from: this.id
|
|
2280
|
+
});
|
|
2281
|
+
};
|
|
2282
|
+
}
|
|
2283
|
+
class Route {
|
|
2284
|
+
// Set up in this.init()
|
|
2285
|
+
|
|
2286
|
+
// customId!: TCustomId
|
|
2287
|
+
|
|
2288
|
+
// Optional
|
|
2289
|
+
|
|
2290
|
+
constructor(options) {
|
|
2291
|
+
this.options = options || {};
|
|
2292
|
+
this.isRoot = !options?.getParentRoute;
|
|
2293
|
+
invariant(!(options?.id && options?.path), `Route cannot have both an 'id' and a 'path' option.`);
|
|
2294
|
+
this.$$typeof = Symbol.for('react.memo');
|
|
2295
|
+
}
|
|
2296
|
+
init = opts => {
|
|
2297
|
+
this.originalIndex = opts.originalIndex;
|
|
2298
|
+
const options = this.options;
|
|
2299
|
+
const isRoot = !options?.path && !options?.id;
|
|
2300
|
+
this.parentRoute = this.options?.getParentRoute?.();
|
|
2301
|
+
if (isRoot) {
|
|
2302
|
+
this.path = rootRouteId;
|
|
2303
|
+
} else {
|
|
2304
|
+
invariant(this.parentRoute, `Child Route instances must pass a 'getParentRoute: () => ParentRoute' option that returns a Route instance.`);
|
|
2305
|
+
}
|
|
2306
|
+
let path = isRoot ? rootRouteId : options.path;
|
|
2307
|
+
|
|
2308
|
+
// If the path is anything other than an index path, trim it up
|
|
2309
|
+
if (path && path !== '/') {
|
|
2310
|
+
path = trimPath(path);
|
|
2311
|
+
}
|
|
2312
|
+
const customId = options?.id || path;
|
|
2313
|
+
|
|
2314
|
+
// Strip the parentId prefix from the first level of children
|
|
2315
|
+
let id = isRoot ? rootRouteId : joinPaths([this.parentRoute.id === rootRouteId ? '' : this.parentRoute.id, customId]);
|
|
2316
|
+
if (path === rootRouteId) {
|
|
2317
|
+
path = '/';
|
|
2318
|
+
}
|
|
2319
|
+
if (id !== rootRouteId) {
|
|
2320
|
+
id = joinPaths(['/', id]);
|
|
2321
|
+
}
|
|
2322
|
+
const fullPath = id === rootRouteId ? '/' : joinPaths([this.parentRoute.fullPath, path]);
|
|
2323
|
+
this.path = path;
|
|
2324
|
+
this.id = id;
|
|
2325
|
+
// this.customId = customId as TCustomId
|
|
2326
|
+
this.fullPath = fullPath;
|
|
2327
|
+
this.to = fullPath;
|
|
2328
|
+
};
|
|
2329
|
+
addChildren = children => {
|
|
2330
|
+
this.children = children;
|
|
2331
|
+
return this;
|
|
2332
|
+
};
|
|
2333
|
+
updateLoader = options => {
|
|
2334
|
+
Object.assign(this.options, options);
|
|
2335
|
+
return this;
|
|
2336
|
+
};
|
|
2337
|
+
update = options => {
|
|
2338
|
+
Object.assign(this.options, options);
|
|
2339
|
+
return this;
|
|
2340
|
+
};
|
|
2341
|
+
useMatch = opts => {
|
|
2342
|
+
return useMatch({
|
|
2343
|
+
...opts,
|
|
2344
|
+
from: this.id
|
|
2304
2345
|
});
|
|
2305
|
-
await Promise.all(matchPromises);
|
|
2306
|
-
return matches;
|
|
2307
2346
|
};
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
...
|
|
2311
|
-
|
|
2347
|
+
useRouteContext = opts => {
|
|
2348
|
+
return useMatch({
|
|
2349
|
+
...opts,
|
|
2350
|
+
from: this.id,
|
|
2351
|
+
select: d => opts?.select ? opts.select(d.context) : d.context
|
|
2312
2352
|
});
|
|
2313
|
-
this.__store.setState(s => ({
|
|
2314
|
-
...s,
|
|
2315
|
-
matches: s.matches.map(invalidate),
|
|
2316
|
-
cachedMatches: s.cachedMatches.map(invalidate),
|
|
2317
|
-
pendingMatches: s.pendingMatches?.map(invalidate)
|
|
2318
|
-
}));
|
|
2319
|
-
this.load();
|
|
2320
2353
|
};
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
this.
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2354
|
+
useSearch = opts => {
|
|
2355
|
+
return useSearch({
|
|
2356
|
+
...opts,
|
|
2357
|
+
from: this.id
|
|
2358
|
+
});
|
|
2359
|
+
};
|
|
2360
|
+
useParams = opts => {
|
|
2361
|
+
return useParams({
|
|
2362
|
+
...opts,
|
|
2363
|
+
from: this.id
|
|
2364
|
+
});
|
|
2365
|
+
};
|
|
2366
|
+
useLoaderDeps = opts => {
|
|
2367
|
+
return useLoaderDeps({
|
|
2368
|
+
...opts,
|
|
2369
|
+
from: this.id
|
|
2370
|
+
});
|
|
2371
|
+
};
|
|
2372
|
+
useLoaderData = opts => {
|
|
2373
|
+
return useLoaderData({
|
|
2374
|
+
...opts,
|
|
2375
|
+
from: this.id
|
|
2376
|
+
});
|
|
2377
|
+
};
|
|
2378
|
+
}
|
|
2379
|
+
function rootRouteWithContext() {
|
|
2380
|
+
return options => {
|
|
2381
|
+
return new RootRoute(options);
|
|
2382
|
+
};
|
|
2383
|
+
}
|
|
2384
|
+
class RootRoute extends Route {
|
|
2385
|
+
constructor(options) {
|
|
2386
|
+
super(options);
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
function createRouteMask(opts) {
|
|
2390
|
+
return opts;
|
|
2391
|
+
}
|
|
2340
2392
|
|
|
2341
|
-
|
|
2342
|
-
pendingMatches = this.matchRoutes(next.pathname, next.search, {
|
|
2343
|
-
debug: true
|
|
2344
|
-
});
|
|
2393
|
+
//
|
|
2345
2394
|
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
return !pendingMatches.find(e => e.id === d.id);
|
|
2355
|
-
})
|
|
2356
|
-
}));
|
|
2357
|
-
});
|
|
2358
|
-
try {
|
|
2359
|
-
try {
|
|
2360
|
-
// Load the matches
|
|
2361
|
-
await this.loadMatches({
|
|
2362
|
-
matches: pendingMatches,
|
|
2363
|
-
checkLatest: () => this.checkLatest(promise)
|
|
2364
|
-
});
|
|
2365
|
-
} catch (err) {
|
|
2366
|
-
// swallow this error, since we'll display the
|
|
2367
|
-
// errors on the route components
|
|
2368
|
-
}
|
|
2395
|
+
class NotFoundRoute extends Route {
|
|
2396
|
+
constructor(options) {
|
|
2397
|
+
super({
|
|
2398
|
+
...options,
|
|
2399
|
+
id: '404'
|
|
2400
|
+
});
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2369
2403
|
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2404
|
+
class FileRoute {
|
|
2405
|
+
constructor(path) {
|
|
2406
|
+
this.path = path;
|
|
2407
|
+
}
|
|
2408
|
+
createRoute = options => {
|
|
2409
|
+
const route = new Route(options);
|
|
2410
|
+
route.isRoot = false;
|
|
2411
|
+
return route;
|
|
2412
|
+
};
|
|
2413
|
+
}
|
|
2414
|
+
function FileRouteLoader(_path) {
|
|
2415
|
+
return loaderFn => loaderFn;
|
|
2416
|
+
}
|
|
2377
2417
|
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2418
|
+
function lazyRouteComponent(importer, exportName) {
|
|
2419
|
+
let loadPromise;
|
|
2420
|
+
const load = () => {
|
|
2421
|
+
if (!loadPromise) {
|
|
2422
|
+
loadPromise = importer();
|
|
2423
|
+
}
|
|
2424
|
+
return loadPromise;
|
|
2425
|
+
};
|
|
2426
|
+
const lazyComp = /*#__PURE__*/React.lazy(async () => {
|
|
2427
|
+
const moduleExports = await load();
|
|
2428
|
+
const comp = moduleExports[exportName ?? 'default'];
|
|
2429
|
+
return {
|
|
2430
|
+
default: comp
|
|
2431
|
+
};
|
|
2432
|
+
});
|
|
2433
|
+
lazyComp.preload = load;
|
|
2434
|
+
return lazyComp;
|
|
2435
|
+
}
|
|
2390
2436
|
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
this.emit({
|
|
2399
|
-
type: 'onLoad',
|
|
2400
|
-
fromLocation: prevLocation,
|
|
2401
|
-
toLocation: next,
|
|
2402
|
-
pathChanged: pathDidChange
|
|
2403
|
-
});
|
|
2404
|
-
resolve();
|
|
2405
|
-
} catch (err) {
|
|
2406
|
-
// Only apply the latest transition
|
|
2407
|
-
if (latestPromise = this.checkLatest(promise)) {
|
|
2408
|
-
return latestPromise;
|
|
2437
|
+
function _extends() {
|
|
2438
|
+
_extends = Object.assign ? Object.assign.bind() : function (target) {
|
|
2439
|
+
for (var i = 1; i < arguments.length; i++) {
|
|
2440
|
+
var source = arguments[i];
|
|
2441
|
+
for (var key in source) {
|
|
2442
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
2443
|
+
target[key] = source[key];
|
|
2409
2444
|
}
|
|
2410
|
-
reject(err);
|
|
2411
2445
|
}
|
|
2412
|
-
}
|
|
2413
|
-
|
|
2414
|
-
return this.latestLoadPromise;
|
|
2446
|
+
}
|
|
2447
|
+
return target;
|
|
2415
2448
|
};
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2449
|
+
return _extends.apply(this, arguments);
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
const preloadWarning = 'Error preloading route! ☝️';
|
|
2453
|
+
function useLinkProps(options) {
|
|
2454
|
+
const router = useRouter();
|
|
2455
|
+
const matchPathname = useMatch({
|
|
2456
|
+
strict: false,
|
|
2457
|
+
select: s => s.pathname
|
|
2458
|
+
});
|
|
2459
|
+
const {
|
|
2460
|
+
// custom props
|
|
2461
|
+
children,
|
|
2462
|
+
target,
|
|
2463
|
+
activeProps = () => ({
|
|
2464
|
+
className: 'active'
|
|
2465
|
+
}),
|
|
2466
|
+
inactiveProps = () => ({}),
|
|
2467
|
+
activeOptions,
|
|
2468
|
+
disabled,
|
|
2469
|
+
hash,
|
|
2470
|
+
search,
|
|
2471
|
+
params,
|
|
2472
|
+
to,
|
|
2473
|
+
state,
|
|
2474
|
+
mask,
|
|
2475
|
+
preload: userPreload,
|
|
2476
|
+
preloadDelay: userPreloadDelay,
|
|
2477
|
+
replace,
|
|
2478
|
+
startTransition,
|
|
2479
|
+
resetScroll,
|
|
2480
|
+
// element props
|
|
2481
|
+
style,
|
|
2482
|
+
className,
|
|
2483
|
+
onClick,
|
|
2484
|
+
onFocus,
|
|
2485
|
+
onMouseEnter,
|
|
2486
|
+
onMouseLeave,
|
|
2487
|
+
onTouchStart,
|
|
2488
|
+
...rest
|
|
2489
|
+
} = options;
|
|
2426
2490
|
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
let next = this.buildLocation(navigateOpts);
|
|
2437
|
-
let matches = this.matchRoutes(next.pathname, next.search, {
|
|
2438
|
-
throwOnError: true
|
|
2439
|
-
});
|
|
2440
|
-
const loadedMatchIds = Object.fromEntries([...this.state.matches, ...(this.state.pendingMatches ?? []), ...this.state.cachedMatches]?.map(d => [d.id, true]));
|
|
2441
|
-
this.__store.batch(() => {
|
|
2442
|
-
matches.forEach(match => {
|
|
2443
|
-
if (!loadedMatchIds[match.id]) {
|
|
2444
|
-
this.__store.setState(s => ({
|
|
2445
|
-
...s,
|
|
2446
|
-
cachedMatches: [...s.cachedMatches, match]
|
|
2447
|
-
}));
|
|
2448
|
-
}
|
|
2449
|
-
});
|
|
2450
|
-
});
|
|
2451
|
-
matches = await this.loadMatches({
|
|
2452
|
-
matches,
|
|
2453
|
-
preload: true,
|
|
2454
|
-
checkLatest: () => undefined
|
|
2455
|
-
});
|
|
2456
|
-
return matches;
|
|
2491
|
+
// If this link simply reloads the current route,
|
|
2492
|
+
// make sure it has a new key so it will trigger a data refresh
|
|
2493
|
+
|
|
2494
|
+
// If this `to` is a valid external URL, return
|
|
2495
|
+
// null for LinkUtils
|
|
2496
|
+
|
|
2497
|
+
const dest = {
|
|
2498
|
+
from: options.to ? matchPathname : undefined,
|
|
2499
|
+
...options
|
|
2457
2500
|
};
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2501
|
+
let type = 'internal';
|
|
2502
|
+
try {
|
|
2503
|
+
new URL(`${to}`);
|
|
2504
|
+
type = 'external';
|
|
2505
|
+
} catch {}
|
|
2506
|
+
if (type === 'external') {
|
|
2507
|
+
return {
|
|
2508
|
+
href: to
|
|
2462
2509
|
};
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2510
|
+
}
|
|
2511
|
+
const next = router.buildLocation(dest);
|
|
2512
|
+
const preload = userPreload ?? router.options.defaultPreload;
|
|
2513
|
+
const preloadDelay = userPreloadDelay ?? router.options.defaultPreloadDelay ?? 0;
|
|
2514
|
+
const isActive = useRouterState({
|
|
2515
|
+
select: s => {
|
|
2516
|
+
// Compare path/hash for matches
|
|
2517
|
+
const currentPathSplit = s.location.pathname.split('/');
|
|
2518
|
+
const nextPathSplit = next.pathname.split('/');
|
|
2519
|
+
const pathIsFuzzyEqual = nextPathSplit.every((d, i) => d === currentPathSplit[i]);
|
|
2520
|
+
// Combine the matches based on user router.options
|
|
2521
|
+
const pathTest = activeOptions?.exact ? s.location.pathname === next.pathname : pathIsFuzzyEqual;
|
|
2522
|
+
const hashTest = activeOptions?.includeHash ? s.location.hash === next.hash : true;
|
|
2523
|
+
const searchTest = activeOptions?.includeSearch ?? true ? deepEqual(s.location.search, next.search, !activeOptions?.exact) : true;
|
|
2524
|
+
|
|
2525
|
+
// The final "active" test
|
|
2526
|
+
return pathTest && hashTest && searchTest;
|
|
2477
2527
|
}
|
|
2478
|
-
|
|
2479
|
-
|
|
2528
|
+
});
|
|
2529
|
+
|
|
2530
|
+
// The click handler
|
|
2531
|
+
const handleClick = e => {
|
|
2532
|
+
if (!disabled && !isCtrlEvent(e) && !e.defaultPrevented && (!target || target === '_self') && e.button === 0) {
|
|
2533
|
+
e.preventDefault();
|
|
2534
|
+
|
|
2535
|
+
// All is well? Navigate!
|
|
2536
|
+
router.commitLocation({
|
|
2537
|
+
...next,
|
|
2538
|
+
replace,
|
|
2539
|
+
resetScroll,
|
|
2540
|
+
startTransition
|
|
2541
|
+
});
|
|
2480
2542
|
}
|
|
2481
|
-
return match;
|
|
2482
|
-
};
|
|
2483
|
-
injectHtml = async html => {
|
|
2484
|
-
this.injectedHtml.push(html);
|
|
2485
2543
|
};
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
;(() => {
|
|
2494
|
-
var el = document.getElementById('${id}')
|
|
2495
|
-
el.parentElement.removeChild(el)
|
|
2496
|
-
})()
|
|
2497
|
-
</script>`;
|
|
2544
|
+
|
|
2545
|
+
// The click handler
|
|
2546
|
+
const handleFocus = e => {
|
|
2547
|
+
if (preload) {
|
|
2548
|
+
router.preloadRoute(dest).catch(err => {
|
|
2549
|
+
console.warn(err);
|
|
2550
|
+
console.warn(preloadWarning);
|
|
2498
2551
|
});
|
|
2499
|
-
return () => this.hydrateData(key);
|
|
2500
2552
|
}
|
|
2501
|
-
return () => undefined;
|
|
2502
2553
|
};
|
|
2503
|
-
|
|
2504
|
-
if (
|
|
2505
|
-
|
|
2506
|
-
|
|
2554
|
+
const handleTouchStart = e => {
|
|
2555
|
+
if (preload) {
|
|
2556
|
+
router.preloadRoute(dest).catch(err => {
|
|
2557
|
+
console.warn(err);
|
|
2558
|
+
console.warn(preloadWarning);
|
|
2559
|
+
});
|
|
2507
2560
|
}
|
|
2508
|
-
return undefined;
|
|
2509
2561
|
};
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2562
|
+
const handleEnter = e => {
|
|
2563
|
+
const target = e.target || {};
|
|
2564
|
+
if (preload) {
|
|
2565
|
+
if (target.preloadTimeout) {
|
|
2566
|
+
return;
|
|
2514
2567
|
}
|
|
2515
|
-
|
|
2568
|
+
target.preloadTimeout = setTimeout(() => {
|
|
2569
|
+
target.preloadTimeout = null;
|
|
2570
|
+
router.preloadRoute(dest).catch(err => {
|
|
2571
|
+
console.warn(err);
|
|
2572
|
+
console.warn(preloadWarning);
|
|
2573
|
+
});
|
|
2574
|
+
}, preloadDelay);
|
|
2575
|
+
}
|
|
2516
2576
|
};
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2577
|
+
const handleLeave = e => {
|
|
2578
|
+
const target = e.target || {};
|
|
2579
|
+
if (target.preloadTimeout) {
|
|
2580
|
+
clearTimeout(target.preloadTimeout);
|
|
2581
|
+
target.preloadTimeout = null;
|
|
2522
2582
|
}
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
const dehydratedMatch = dehydratedState.dehydratedMatches.find(d => d.id === match.id);
|
|
2530
|
-
invariant(dehydratedMatch, `Could not find a client-side match for dehydrated match with id: ${match.id}!`);
|
|
2531
|
-
if (dehydratedMatch) {
|
|
2532
|
-
return {
|
|
2533
|
-
...match,
|
|
2534
|
-
...dehydratedMatch
|
|
2535
|
-
};
|
|
2536
|
-
}
|
|
2537
|
-
return match;
|
|
2538
|
-
});
|
|
2539
|
-
this.__store.setState(s => {
|
|
2540
|
-
return {
|
|
2541
|
-
...s,
|
|
2542
|
-
matches: matches
|
|
2543
|
-
};
|
|
2583
|
+
};
|
|
2584
|
+
const composeHandlers = handlers => e => {
|
|
2585
|
+
if (e.persist) e.persist();
|
|
2586
|
+
handlers.filter(Boolean).forEach(handler => {
|
|
2587
|
+
if (e.defaultPrevented) return;
|
|
2588
|
+
handler(e);
|
|
2544
2589
|
});
|
|
2545
2590
|
};
|
|
2546
2591
|
|
|
2547
|
-
//
|
|
2548
|
-
|
|
2549
|
-
// .find((d) => d.id === matchId)
|
|
2550
|
-
// ?.__promisesByKey[key]?.resolve(value)
|
|
2551
|
-
// }
|
|
2552
|
-
}
|
|
2592
|
+
// Get the active props
|
|
2593
|
+
const resolvedActiveProps = isActive ? functionalUpdate(activeProps, {}) ?? {} : {};
|
|
2553
2594
|
|
|
2554
|
-
//
|
|
2555
|
-
|
|
2556
|
-
// information along the way
|
|
2557
|
-
function lazyFn(fn, key) {
|
|
2558
|
-
return async (...args) => {
|
|
2559
|
-
const imported = await fn();
|
|
2560
|
-
return imported[key || 'default'](...args);
|
|
2561
|
-
};
|
|
2562
|
-
}
|
|
2563
|
-
class SearchParamError extends Error {}
|
|
2564
|
-
class PathParamError extends Error {}
|
|
2565
|
-
function getInitialRouterState(location) {
|
|
2595
|
+
// Get the inactive props
|
|
2596
|
+
const resolvedInactiveProps = isActive ? {} : functionalUpdate(inactiveProps, {}) ?? {};
|
|
2566
2597
|
return {
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2598
|
+
...resolvedActiveProps,
|
|
2599
|
+
...resolvedInactiveProps,
|
|
2600
|
+
...rest,
|
|
2601
|
+
href: disabled ? undefined : next.maskedLocation ? next.maskedLocation.href : next.href,
|
|
2602
|
+
onClick: composeHandlers([onClick, handleClick]),
|
|
2603
|
+
onFocus: composeHandlers([onFocus, handleFocus]),
|
|
2604
|
+
onMouseEnter: composeHandlers([onMouseEnter, handleEnter]),
|
|
2605
|
+
onMouseLeave: composeHandlers([onMouseLeave, handleLeave]),
|
|
2606
|
+
onTouchStart: composeHandlers([onTouchStart, handleTouchStart]),
|
|
2607
|
+
target,
|
|
2608
|
+
style: {
|
|
2609
|
+
...style,
|
|
2610
|
+
...resolvedActiveProps.style,
|
|
2611
|
+
...resolvedInactiveProps.style
|
|
2572
2612
|
},
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2613
|
+
className: [className, resolvedActiveProps.className, resolvedInactiveProps.className].filter(Boolean).join(' ') || undefined,
|
|
2614
|
+
...(disabled ? {
|
|
2615
|
+
role: 'link',
|
|
2616
|
+
'aria-disabled': true
|
|
2617
|
+
} : undefined),
|
|
2618
|
+
['data-status']: isActive ? 'active' : undefined
|
|
2578
2619
|
};
|
|
2579
2620
|
}
|
|
2621
|
+
const Link = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
2622
|
+
const linkProps = useLinkProps(props);
|
|
2623
|
+
return /*#__PURE__*/React.createElement("a", _extends({
|
|
2624
|
+
ref: ref
|
|
2625
|
+
}, linkProps, {
|
|
2626
|
+
children: typeof props.children === 'function' ? props.children({
|
|
2627
|
+
isActive: linkProps['data-status'] === 'active'
|
|
2628
|
+
}) : props.children
|
|
2629
|
+
}));
|
|
2630
|
+
});
|
|
2631
|
+
function isCtrlEvent(e) {
|
|
2632
|
+
return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey);
|
|
2633
|
+
}
|
|
2580
2634
|
|
|
2581
2635
|
const useLayoutEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
|
|
2582
2636
|
const windowKey = 'window';
|
|
@@ -2816,5 +2870,5 @@ function useRouteContext(opts) {
|
|
|
2816
2870
|
});
|
|
2817
2871
|
}
|
|
2818
2872
|
|
|
2819
|
-
export { Await, Block, CatchBoundary, CatchBoundaryImpl, ErrorComponent, FileRoute, FileRouteLoader, Link, Match, MatchRoute, Matches, Navigate, NotFoundRoute, Outlet, PathParamError, RootRoute, Route, RouteApi, Router, RouterProvider, ScrollRestoration, SearchParamError, cleanPath, componentTypes, createRouteMask, decode, deepEqual, defaultParseSearch, defaultStringifySearch, defer, encode, escapeJSON, functionalUpdate, getInitialRouterState, getRenderedMatches, getRouteMatch, interpolatePath, isDehydratedDeferred, isPlainArray, isPlainObject, isRedirect, isServer, joinPaths, last, lazyFn, lazyRouteComponent, matchByPath, matchContext, matchPathname, parsePathname, parseSearchWith, pick, redirect, removeBasepath, replaceEqualDeep, resolvePath, rootRouteId, rootRouteWithContext, routerContext, shallow, stringifySearchWith, trimPath, trimPathLeft, trimPathRight, useAwaited, useBlocker, useElementScrollRestoration, useLayoutEffect$1 as useLayoutEffect, useLinkProps, useLoaderData, useLoaderDeps, useMatch, useMatchRoute, useMatches, useNavigate, useParams, useParentMatches, useRouteContext, useRouter, useRouterState, useScrollRestoration, useSearch, useStableCallback };
|
|
2873
|
+
export { Await, Block, CatchBoundary, CatchBoundaryImpl, ErrorComponent, FileRoute, FileRouteLoader, Link, Match, MatchRoute, Matches, Navigate, NotFoundRoute, Outlet, PathParamError, RootRoute, Route, RouteApi, Router, RouterProvider, ScrollRestoration, SearchParamError, cleanPath, componentTypes, createRouteMask, decode, deepEqual, defaultDeserializeError, defaultParseSearch, defaultSerializeError, defaultStringifySearch, defer, encode, escapeJSON, functionalUpdate, getInitialRouterState, getRenderedMatches, getRouteMatch, interpolatePath, isDehydratedDeferred, isPlainArray, isPlainObject, isRedirect, isServer, isServerSideError, joinPaths, last, lazyFn, lazyRouteComponent, matchByPath, matchContext, matchPathname, parsePathname, parseSearchWith, pick, redirect, removeBasepath, replaceEqualDeep, resolvePath, rootRouteId, rootRouteWithContext, routerContext, shallow, stringifySearchWith, trimPath, trimPathLeft, trimPathRight, useAwaited, useBlocker, useElementScrollRestoration, useLayoutEffect$1 as useLayoutEffect, useLinkProps, useLoaderData, useLoaderDeps, useMatch, useMatchRoute, useMatches, useNavigate, useParams, useParentMatches, useRouteContext, useRouter, useRouterState, useScrollRestoration, useSearch, useStableCallback };
|
|
2820
2874
|
//# sourceMappingURL=index.js.map
|